I choose to use provider as my state management so I saw I have to use Multi provider.
My struggle is how to architect my code that I can initialize all the data I need when my app first run and give the providers to the multi provider.
Provider example
import 'package:cron/cron.dart';
import 'package:flutter/material.dart';
import 'package:web_app/models/fixture.dart';
import 'package:web_app/services/fixture_service.dart';
class HighlightsProvider extends ChangeNotifier {
final List<Fixture> _highlights = [];
List<Fixture> get() => _highlights;
Future<void> fetchHighlights() async {
try {
List<Fixture> highlightFixtures = [];
final response = await FixtureService().getAppHighlightFixtures();
[...response].asMap().forEach((index, element) {
highlightFixtures.add(new Fixture.fromJson(element));
});
_highlights.clear();
_highlights.addAll(highlightFixtures);
notifyListeners();
} catch (e) {
print('error');
print(e);
}
}
runJob(cron) {
cron.schedule(Schedule.parse('* * * * *'), () async {
fetchHighlights();
print('fetch highlights every one minute');
});
}
}
Let's say this class will get all my providers and initialize theme:
class InitializeApp {
final cron = Cron();
Future run(HighlightsProvider highlightsProvider) async {
return Future.wait([
initiakizeHighlights(highlightsProvider),
]);
}
Future initiakizeHighlights(HighlightsProvider highlightsProvider) async {
highlightsProvider.runJob(cron);
await highlightsProvider.fetchHighlights();
}
}
Then I have to deliver those provider to the multi provider:
void main() async {
final highlightsProvider = HighlightsProvider();
await InitializeApp().run(highlightsProvider);
print('ready');
runApp(MyApp(highlightsProvider: highlightsProvider));
}
class MyApp extends StatelessWidget {
final highlightsProvider;
const MyApp({Key key, this.highlightsProvider}) : super(key: key);
#override
Widget build(BuildContext context) {
print('build');
return MultiProvider(
providers: [
ChangeNotifierProvider<HighlightsProvider>.value(
value: highlightsProvider,
)
],
child: MaterialApp(
title: 'tech',
theme: ThemeData(
primarySwatch: Colors.amber,
brightness: Brightness.light,
),
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return MyHomePage(title: 'Flutter Demo Home Page');
}
}),
);
}
}
Normally you just wrap your MaterialApp with the MultiProvider, then you already have access to all Providers you will define.
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<RecipeStreamService>.value(value: RecipeStreamService().controllerOut)
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Home Food',
routes: {
'/register': (BuildContext context) => RegisterPage(),
'/login': (BuildContext context) => LoginPage()
},
),
home: HomePage(title: 'Home'),
),
);
}
Related
Exception is thrown on second call 'emit' inside 'firstEvent' case. I know why, but I dont know how to make it work. Problem exists when I use event.map.
_AssertionError
emit was called after an event handler completed normally.
This is usually due to an unawaited future in an event handler.
Please make sure to await all asynchronous operations with event handlers
and use emit.isDone after asynchronous operations before calling emit() to
ensure the event handler has not completed.
void main() {
runApp(const MyApp());
}
#freezed
class SampleEvent with _$SampleEvent {
const factory SampleEvent.firstEvent() = FirstEvent;
const factory SampleEvent.secondEvent() = SecondEvent;
}
#freezed
class SampleState with _$SampleState {
const factory SampleState.initialState() = InitialState;
const factory SampleState.firstState() = OneFirstState;
const factory SampleState.secondState() = SecondState;
}
class SampleBloc extends Bloc<SampleEvent, SampleState> {
SampleBloc() : super(const SampleState.initialState()) {
on<SampleEvent>(eventHandler);
}
FutureOr<void> eventHandler(
SampleEvent event,
Emitter<SampleState> emit,
) async {
event.map(
firstEvent: (value) async {
emit(const SampleState.firstState());
await Future.delayed(const Duration(seconds: 2));
// EXCEPTION!!! HERE:
emit(const SampleState.secondState());
},
secondEvent: (value) => null,
);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(create: (_) => SampleBloc(), child: const Home()),
);
}
}
class Home extends StatelessWidget {
const Home({super.key});
#override
Widget build(BuildContext context) {
return BlocConsumer<SampleBloc, SampleState>(
builder: (context1, state) {
return TextButton(
onPressed: () => context1.read<SampleBloc>().add(
const SampleEvent.firstEvent(),
),
child: const Text('Launch First Event'),
);
},
listener: (context, state) {},
);
}
}
I made a todo app using Getx package. I also created a login page and I want to display the login page only one time but when I try to do this I a got error
"HomeController" not found. You need to call Get.put(HomeController()) or Get.lazyPut(()=>HomeController()).
this is my binding
class HomeBinding implements Bindings {
#override
void dependencies() {
Get.lazyPut(() => HomeController(
taskRepository: TaskRepository(
taskProvider: TaskProvider(),
),
));
}
}
this is my main.dart
int? isViewed;
void main() async {
await GetStorage.init();
await Get.putAsync(() => StorageService().init());
WidgetsFlutterBinding.ensureInitialized();
await ScreenUtil.ensureScreenSize();
await GetStorage.init();
await Get.putAsync(() => StorageService().init());
LicenseRegistry.addLicense(() async* {
final license = await rootBundle.loadString('google_fonts/OFL.txt');
yield LicenseEntryWithLineBreaks(['google_fonts'], license);
});
SharedPreferences prefs = await SharedPreferences.getInstance();
isViewed = prefs.getInt('login');
runApp(MyApp());
}
class MyApp extends GetView<HomeController> {
const MyApp({
Key? key,
}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(statusBarColor: Colors.transparent));
return ScreenUtilInit(
designSize: const Size(360, 800),
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: Themes.light,
darkTheme: Themes.dark,
themeMode: ThemeMode.light,
home: isViewed != 0 ? Login() : Report(),
initialBinding: HomeBinding(),
builder: EasyLoading.init(),
);
});
}
}
This is the error
Try adding HomeBinding in getPages array of GetMaterialApp.
Also apply below modifications in below scope
GetMaterialApp(
// initialBinding: HomeBinding(), ====>> Remove this line
initialRoute: '/home', // ====>> Add this line
getPages: [
GetPage(
name: "/home",
page: () => const HomeScreen(),
binding: HomeBinding(),
),
// ====>> Add other pages like home
]
);
I am attempting to share a ChangeNotifierProvider to my main.dart, however the value never gets updated.
How it works
main.dart uses ChangeNotifierProvider to get an instance of the class Location()
main.dart routes to the location_login.dart page where a string in Location() class is set.
The instance of Location() should update in main.dart but it DOES NOT.
Here is the main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
ChangeNotifierProvider<Location>.value( <------ CREATE CHANGENOTIFIERPROVIDER
value: Location(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
Location location = Provider.of<Location>(context, listen: false); <----- LISTEN TO PROVIDER
return MultiProvider(
providers: [
FutureProvider<List<Report>>(
create: (context) =>
Collection<Report>(path: '${location.getLocation}/data/reports') <----- USE PROVIDER STRING IN PATH
.getUsers(),
initialData: [],
),
],
child: MaterialApp(
routes: {
'/': (context) => LocationLogin(),
'/login': (context) => LoginScreen(),
'/home': (context) => HomeScreen(),
},
// Theme
theme: ThemeData(
fontFamily: 'Nunito',
bottomAppBarTheme: BottomAppBarTheme(
color: Colors.black87,
),
// your customizations here
brightness: Brightness.dark,
buttonTheme: ButtonThemeData(),
),
),
);
}
}
Here is the location_login.dart
#override
Widget build(BuildContext context) {
Location location = Provider.of<Location>(context, listen: true);
return Scaffold(
body: TextButton(
child: Text("Submit",
style: GoogleFonts.poppins(
fontSize: 15.sp, color: Colors.white)),
onPressed: () {
location.setLocation('London'); <------- SETTING LOCATION
}),
);
}
}
Here is the location.dart
class Location with ChangeNotifier {
String place = 'none';
String get getLocation => place;
setLocation(String location) {
place = location;
notifyListeners();
}
}
To reiterate, the issue is that when I click the button in the location_login.dart page to set the location to "London"; it does not update the ChangeNotifierProvider with a new instance of the Location() class containing "London". Therefore, I can not update the path in my FurtureProvider. Any ideas of what is going wrong here? I tried to make this as clear as possible but if you don't understand please ask. Thank you
I think you have not consume the ChangeNotifierProvider.
For me below simple implementation work perfectly.
my main.dart file code is as below...
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/app_provider.dart';
import 'providers/favorites_provider.dart';
import 'providers/comments_provider.dart';
import 'providers/home_provider.dart';
import 'providers/details_provider.dart';
import 'providers/gallery_provider.dart';
import 'providers/chat_provider.dart';
import 'ui/splash.dart';
import 'helper/constants.dart';
import 'ui_user/login.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AppProvider()),
ChangeNotifierProvider(create: (_) => GalleryProvider()),
ChangeNotifierProvider(create: (_) => CommentsProvider()),
ChangeNotifierProvider(create: (_) => ChatProvider()),
ChangeNotifierProvider(create: (_) => HomeProvider()),
ChangeNotifierProvider(create: (_) => DetailsProvider()),
ChangeNotifierProvider(create: (_) => FavoritesProvider()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AppProvider>(
builder: (BuildContext context, AppProvider appProvider, Widget child) {
return MaterialApp(
key: appProvider.key,
debugShowCheckedModeBanner: false,
navigatorKey: appProvider.navigatorKey,
title: Constants.appName,
theme: appProvider.theme,
home: appProvider.isLogin == "0" ? LoginPage() : Splash(),
);
},
);
}
}
And my app_provider.dart as below...
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../helper/constants.dart';
class AppProvider extends ChangeNotifier {
AppProvider() {
checkTheme();
}
String isLogin = "0";
ThemeData theme = Constants.lightTheme;
Key key = UniqueKey();
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void setKey(value) {
key = value;
notifyListeners();
}
void setNavigatorKey(value) {
navigatorKey = value;
notifyListeners();
}
void setTheme(value, c) {
theme = value;
SharedPreferences.getInstance().then((prefs) {
prefs.setString("theme", c).then((val) {
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor:
c == "dark" ? Constants.darkPrimary : Constants.lightPrimary,
statusBarIconBrightness:
c == "dark" ? Brightness.light : Brightness.dark,
));
});
});
notifyListeners();
}
ThemeData getTheme(value) {
return theme;
}
Future<ThemeData> checkTheme() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
ThemeData t;
String r =
prefs.getString("theme") == null ? "light" : prefs.getString("theme");
isLogin = prefs.getString("isLogin") == null? "0" : prefs.getString("isLogin");
if (r == "light") {
t = Constants.lightTheme;
setTheme(Constants.lightTheme, "light");
} else {
t = Constants.darkTheme;
setTheme(Constants.darkTheme, "dark");
}
return t;
}
}
This solution is working very well for me. Hope this will help you too...
I just starting to learn flutter and want to implement login page.
So i check if i have token or not then decide if it is my home route or login route to be initialized as initialRoute, the problem is when flutter render login route, it also requesing API i declared on home route which is gonna return empty list because i dont have any token yet.
Future<void> main() async{
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var status = prefs.getString('jwt') ?? "";
if(status!="")
runApp(HomeR(initialRoute: "/"));
else
runApp(HomeR(initialRoute: "/login"));
}
class HomeR extends StatelessWidget {
static const routeName = '/';
HomeR({Key key, this.initialRoute}) : super(key: key);
String initialRoute = "/";
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'blablabal',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'blablabla'),
initialRoute: initialRoute,
routes: {
loginR.routeName: (context) => loginR(),
},
);
}
}
and use Navigator.pop(context) from login button after authenticate first but my home route doesnt refresh itself and display nothing, since it is using empty list before.
How to tell home route to reload when i pop from login route?
This is MaterialApp:
MaterialApp(
// no need for home
title: 'blablabal',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
'/': (context) => InitialApp(),
loginR.routeName: (context) => loginR(),
},
initialRoute: '/',
),
and In InitialApp:
class InitialApp extends StatefulWidget {
#override
_InitialAppState createState() => _InitialAppState();
}
class _InitialAppState extends State<InitialApp> {
void loginLogic() async
{
SharedPreferences.getInstance().then((value){
var status = value.getString('jwt') ?? "";
if(status=='')
{
Navigator.pushReplacementNamed(context, loginR.routeName);
}
else //logged in
{
Navigator.pushReplacementNamed(context, LaporanList.routeName);
}
});
}
#override
void initState() {
loginLogic();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
I am trying to implement custom logout solution for my application, where no matter where user currently is, once the Logout button is clicked, app will navigate back to Login page.
My idea was, that instead of listening on every component for state changes, I would have one single listener on a master component -> MyApp.
For the sake of simplicity, I have stripped down items to bare minimum. Here is how my Profile class could look like:
class Profile with ChangeNotifier {
bool _isAuthentificated = false;
bool get isAuthentificated => _isAuthentificated;
set isAuthentificated(bool newVal) {
_isAuthentificated = newVal;
notifyListeners();
}
}
Now, under Main, I have registered this provider as following:
void main() => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => Profile(),
)
],
child: MyApp(),
),
);
And finally MyApp component:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Consumer<Profile>(
builder: (context, profile, _) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Color.fromARGB(255, 0, 121, 107),
accentColor: Color.fromARGB(255, 255, 87, 34),
),
home: buildBasePage(context, profile),
);
},
);
}
Widget buildBasePage(BuildContext context, Profile currentProfile) {
return !currentProfile.isAuthentificated
? LoginComponent()
: MyHomePage(title: 'Flutter Demo Home Page test');
}
}
My idea was, that as MyApp component is the master, I should be able to create a consumer, which would be notified if current user is authentificated, and would respond accordingly.
What happens is, that when I am in e.g. MyHomePage component and I click Logout() method which looks like following:
void _logout() {
Provider.of<Profile>(context, listen: false).isAuthentificated = false;
}
I would be expecting that upon changing property, the initial MyApp component would react and generate LoginPage; which is not the case. I have tried changing from Consumer to Provider.of<Profile>(context, listen: false) yet with the same result.
What do I need to do in order for this concept to work? Is it even correct to do it this way?
I mean I could surely update my Profile class in a way, that I add the following method:
logout(BuildContext context) {
_isAuthentificated = false;
Navigator.push(
context, MaterialPageRoute(builder: (context) => LoginComponent()));
}
And then simply call Provider.of<Profile>(context, listen: false).logout(), however I thought that Provider package was designed for this...or am I missing something?
Any help in respect to this matter would be more than appreciated.
I don't know why it wasn't working for you. Here is a complete example I built based on your description. It works!
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Profile with ChangeNotifier {
bool _isAuthentificated = false;
bool get isAuthentificated {
return this._isAuthentificated;
}
set isAuthentificated(bool newVal) {
this._isAuthentificated = newVal;
this.notifyListeners();
}
}
void main() {
return runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<Profile>(
create: (final BuildContext context) {
return Profile();
},
)
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(final BuildContext context) {
return Consumer<Profile>(
builder: (final BuildContext context, final Profile profile, final Widget child) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: profile.isAuthentificated ? MyHomePage() : MyLoginPage(),
);
},
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(final BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Home [Auth Protected]")),
body: Center(
child: RaisedButton(
child: const Text("Logout"),
onPressed: () {
final Profile profile = Provider.of<Profile>(context, listen: false);
profile.isAuthentificated = false;
},
),
),
);
}
}
class MyLoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Login")),
body: Center(
child: RaisedButton(
child: const Text("Login"),
onPressed: () {
final Profile profile = Provider.of<Profile>(context, listen: false);
profile.isAuthentificated = true;
},
),
),
);
}
}
You don't need to pass listen:false, instead simply call
Provider.of<Profile>(context).logout()
So your Profile class would look like
class Profile with ChangeNotifier {
bool isAuthentificated = false;
logout() {
isAuthentificated = false;
notifyListeners();
}
}