Flutter Light and Dark mode not updating provider - flutter

I have a controller for theme changing like this
class ThemeProvider extends ChangeNotifier {
ThemeMode systemThemeMode = preferences.getBool(Keys.isDarkMode) == null
? ThemeMode.light
: preferences.getBool(Keys.isDarkMode) == true
? ThemeMode.dark
: ThemeMode.light;
bool isDarkMode =
SchedulerBinding.instance.window.platformBrightness.name == "dark"
? true
: false;
toggleTheme(bool isPro) {
if (isPro) {
isDarkMode = !isDarkMode;
preferences.setBool(Keys.isDarkMode, isDarkMode);
systemThemeMode = isDarkMode ? ThemeMode.dark : ThemeMode.light;
notifyListeners();
}
}
}
And this is my main.dart
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserController()),
ChangeNotifierProvider(create: (context) => TeamsController()),
ChangeNotifierProvider(create: (context) => MatchListController()),
ChangeNotifierProvider(create: (context) => NewsProvider()),
ChangeNotifierProvider(create: (context) => ThemeProvider()),
],
builder: (context, _) {
final themeProvider =
Provider.of<ThemeProvider>(context, listen: false);
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: LightDarkTheme.lightTheme,
darkTheme: LightDarkTheme.darkTheme,
title: 'CricketR Plus',
themeMode: themeProvider.systemThemeMode,
home: LoginScreen(),
);
});
I am updating value by toggleTheme and I can see its updating by print but theme is going to light to dark. But If I toggle and restart my app then its working perfectly fine.

With Provider.of<ThemeProvider>(context, listen: false) you specify that you don't want to listen to changes in the provider.
Instead, use this when set themeMode within MaterialApp:
themeMode: context.watch<ThemeProvider>().systemThemeMode,

You need to use Consumer widget for that
wrap your MaterialApp in it. Then, you can call the ThemeProvider inside the Consumer and update the themeMode of the MaterialApp whenever the theme is toggled.
Here is an example
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserController()),
ChangeNotifierProvider(create: (context) => TeamsController()),
ChangeNotifierProvider(create: (context) => MatchListController()),
ChangeNotifierProvider(create: (context) => NewsProvider()),
ChangeNotifierProvider(create: (context) => ThemeProvider()),
],
builder: (context, _) {
return Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: LightDarkTheme.lightTheme,
darkTheme: LightDarkTheme.darkTheme,
title: 'CricketR Plus',
themeMode: themeProvider.systemThemeMode,
home: LoginScreen(),
);
},
);
});

Related

How can I clear the current state of my providers manually in Flutter app?

How can I clear the current state of my providers manually in my Flutter app? while I'm using MultiProvider
The use case I have is when a user signs out of my app then signs up as a new/different user the previous users' state is still stored in the providers.
it is cleared automatically when the app is restarted, however.
MultiProvider(
providers: ServiceProvider().providers,
child: Consumer<AppLocale>(builder: (BuildContext context, AppLocale locale, Widget? child) {
return MaterialApp(
title: 'Application',
localizationsDelegates: AppLocalizations.localizationsDelegates,
debugShowCheckedModeBanner: false,
navigatorKey: RouteService.navigatorKey,
scaffoldMessengerKey: RouteService.rootScaffoldMessengerKey,
supportedLocales: AppLocalizations.supportedLocales,
onGenerateRoute: Routes.generateRoute,
initialRoute: RoutePaths.SplashScreen,
onUnknownRoute: Routes.generateRoute,
navigatorObservers: <NavigatorObserver>[AppRouteObserver()],
locale: locale.appLocal,
theme: appTheme(),
// debugShowMaterialGrid: true,
// showPerformanceOverlay: true,
// checkerboardRasterCacheImages: true,
// checkerboardOffscreenLayers: true,
// showSemanticsDebugger: true,
);
}),
)
Below is my ServiceProvider class
class ServiceProvider {
final List<SingleChildWidget> _independentServices = <SingleChildWidget>[
// ignore: always_specify_types
Provider.value(value: null)
];
final List<SingleChildWidget> _dependentServices = <SingleChildWidget>[];
final List<SingleChildWidget> _uiConsumableProviders = <SingleChildWidget>[
// ignore: always_specify_types
ChangeNotifierProvider(
create: (BuildContext context) => AppLocale(),
),
// ignore: always_specify_types
ChangeNotifierProvider(
create: (BuildContext context) => UserDetailsProvider(),
),
// ignore: always_specify_types
ChangeNotifierProvider(
create: (BuildContext context) => AccountAddressProvider(),
),
// ignore: always_specify_types
ChangeNotifierProvider(
create: (BuildContext context) => ServiceProvidersProductsProvider(),
),
// ignore: always_specify_types
ChangeNotifierProvider(
create: (BuildContext context) => LookupValuesProvider(),
),
];
List<SingleChildWidget> get providers {
return <SingleChildWidget>[..._independentServices, ..._dependentServices, ..._uiConsumableProviders];
}
}

Provider trying to update my count when ever will I receive Notification?

I am unable to update my Ui
//// HERE I AM GETTING THE UPDATE DATA IN CONSOLE BUT UNABLT TO UPDATE INSIDE MY UI
My NotificationCountClass
class NotificationCount extends ChangeNotifier{
var count;
NotificationCount({
this.count =0,
});
addNotificationCount(){
count++;
notifyListeners();
print("Notification Count $count");
}
}
main : here i wrap the widget inside multiprovider so that i can use it any-where in my app
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppService()),
ChangeNotifierProvider(create: (context) => NotificationCount()),
],
child: Consumer<AppService>(
builder: (context, appService, child) {
return GetMaterialApp(
title: AppStrings.APP_TITLE,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.dartTheme,
navigatorKey: GlobalVariable.navigatorKey,
supportedLocales: [
Locale('en'),
],
localizationsDelegates: [
CountryLocalizations.delegate,
],
themeMode: appService.isDarkMode ? ThemeMode.dark : ThemeMode.light,
initialRoute: AppRouter.SPLASH_SCREEN,
onGenerateRoute: AppRouter.router.generator,
// routes: {
// "/":(context) =>Home(),
// "/AppChat" : (context) => AppChat(),
// },
debugShowCheckedModeBanner: false,
// home: AppChat(),
);
},
),
);
// Using State with Consumer widget so that only required wiget rebuild
Consumer<NotificationCount>(
builder: (context, value, child) {
var count = value.count;
print("Count of Not : $count");
return Text(
"$count",
style: TextStyle(
color: Colors.white,
),
);
},
),
getting NotificationCount class with provider but still unable to update UI
final notificationCount = Provider.of<NotificationCount>(context , listen: false);
I could reproduce your issue on my side, and could fix it just by using the builder of MultiProvider instead of child.
Instead of
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppService()),
ChangeNotifierProvider(create: (context) => NotificationCount()),
],
child: Consumer<AppService>(
write somthing like:
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppService()),
ChangeNotifierProvider(create: (context) => NotificationCount()),
],
builder: (context, _) => Consumer<AppService>(
...

Flutter Bloc State won't be yielded after change from the Page pushed on top

The AddWorkoutEvent is dispatched correctly from the PageCreateWorkout with a DbMWorkout.
This will be inserted in the correct table of the DB.
The PageCreateWorkout will be notified with the WorkoutAddedState to go to PageWorkoutDetail with the given workoutId. The routes are stacked on the PageSelectWorkout, in which the WorkoutBloc is mainly used, to show all workouts. In there the PageSelectWorkout shall be refreshed with the new WorkoutLoadedState with the newly given workoutList. (The WorkoutList contains the added Workout, which I checked in the logger; but the State won't be yielded. Note that I am extending equatable to WorkoutStates.)
else if (event is AddWorkoutEvent) {
logger.i("AddWorkoutEvent | workout: ${event.workout}");
yield WorkoutLoadingState();
try {
DbMWorkout workout = await RepositoryWorkout.repo.add(event.workout);
yield WorkoutAddedState(id: workout.id);
List<DbMWorkout> workoutList = await RepositoryWorkout.repo.getAll();
logger.i("AddWorkoutEvent | workoutListLength: ${workoutList.length}");
yield WorkoutLoadedState(workoutList: workoutList); // <-- this state
} catch (e) {
yield WorkoutErrorState(message: e.toString());
}
}
The PageSelectWorkout is the initialPage of a Navigator in an indexedStack:
IndexedStack(
sizing: StackFit.expand,
index: _currentIndex,
children: <Widget>[
Navigator(
key: _pageOverview,
initialRoute: PageOverview.routeName,
onGenerateRoute: (route) =>
RouteGenerator.generateRoute(route)),
Navigator(
key: _pageSelectWorkoutNew,
initialRoute: PageSelectWorkout.routeName,
onGenerateRoute: (route) =>
RouteGenerator.generateRoute(route)),
Navigator(
key: _pageLog,
initialRoute: PageLog.routeName,
onGenerateRoute: (route) =>
RouteGenerator.generateRoute(route)),
Navigator(
key: _pageSettings,
initialRoute: PageSettings.routeName,
onGenerateRoute: (route) =>
RouteGenerator.generateRoute(route))
]),
The named Route to the SelectWorkout is wrapped with the correct Bloc in a BlocProvider:
case PageSelectWorkout.routeName:
return CupertinoPageRoute(
settings: settings,
builder: (context) {
return BlocProvider(
create: (context) => WorkoutBloc(),
child: PageSelectWorkout());
});
Note: In other Events like DeleteWorkoutEvent , which is happening without navigating to another Page, the updated State gets yielded correctly.
I found the answer after checking out some GitHub Issues and it's rather simple:
Since I want to access the same Bloc on 2 different Pages I can not just wrap a new Instance of the Bloc to each of the Page.
Instead I should wrap the BlocProvider with the WorkoutBloc higher in the WidgetTree Hierarchy for example in the main.dart
Before:
case PageSelectWorkout.routeName:
return CupertinoPageRoute(
settings: settings,
builder: (context) => BlocProvider(
create: (context) => WorkoutBloc(), // <-- Instance of WorkoutBloc
child: PageSelectWorkout()));
case PageCreateWorkout.routeName:
return CupertinoPageRoute(
settings: settings,
builder: (context) => BlocProvider(
create: (context) => WorkoutBloc(), // <-- Instance of WorkoutBloc
child: PageCreateWorkout(
workoutIndex: arguments["workoutIndex"],
workoutPosition: arguments["workoutPosition"],
),
));
After:
case PageSelectWorkout.routeName:
return CupertinoPageRoute(
settings: settings, builder: (context) => PageSelectWorkout());
case PageCreateWorkout.routeName:
return CupertinoPageRoute(
settings: settings,
builder: (context) => PageCreateWorkout(
workoutIndex: arguments["workoutIndex"],
workoutPosition: arguments["workoutPosition"],
),
);
and higher in the Inheritance/WidgetTree Hierarchy e.g. main.dart :
return MaterialApp(
debugShowCheckedModeBanner: false,
title: Strings.appTitle,
theme: AppTheme.darkTheme,
home: MultiBlocProvider(providers: [
BlocProvider<WorkoutBloc>(
create: (context) => WorkoutBloc(), // <-- put the Instance higher
),
BlocProvider<NavigationBloc>(
create: (context) => NavigationBloc(),
),
], child: BottomNavigationController()));

MultiRepositoryProvider doesn't instantiate Bloc

I recently started developing an app in Flutter, so I'm fairly new to the area. So I've been looking into using Blocs. However when I Instantiate my Bloc and my services everything works fine. That is, until I use MultiRepositoryProvider. I have 2 code snippets. The first one:
return RepositoryProvider<AuthenticationService>(
create: (context) {
return FakeAuthenticationService();
},
// Injects the Authentication BLoC
child: BlocProvider<AuthenticationBloc>(
create: (context) {
final authService = RepositoryProvider.of<AuthenticationService>(context);
return AuthenticationBloc(authService)..add(AppLoaded());
},
child: MaterialApp(
title: 'Authentication Demo',
theme: appTheme(),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationAuthenticated) {
// show home page
return HomePage(
user: state.user,
);
}
// otherwise show login page
return StartupPage();
},
),
)
),
);
This code works fine, but the second snippet which is exactly the same, except it utilized MultiRepositoryProvider doesn't work. Second code:
return MultiRepositoryProvider(
providers: [
RepositoryProvider<AuthenticationService>(
create: (context) => FakeAuthenticationService(),
child: BlocProvider<AuthenticationBloc>(
create: (context) {
final authService = RepositoryProvider.of<AuthenticationService>(context);
return AuthenticationBloc(authService)..add(AppLoaded());
},
),
)
],
child: MaterialApp(
title: 'Authentication Demo',
theme: appTheme(),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationAuthenticated) {
// show home page
return HomePage(
user: state.user,
);
}
// otherwise show login page
return StartupPage();
},
),
),
);
Now this second code gives me the error BlocProvider.of() called with a context that does not contain a Cubit of type AuthenticationBloc.
Does anyone know why this second code doesn't work?
I'm working on the same thing and I got an error but now resolved
return MultiRepositoryProvider(
providers: [
RepositoryProvider<TranslationRepository>(
create: (context) => TranslationRepository(),
),
RepositoryProvider<WeatherRepository>(
create: (context) => WeatherRepository(),
),
],
child: MultiBlocProvider(
providers: [
BlocProvider<WeatherBloc>(
create: (context) =>
WeatherBloc(context.read<WeatherRepository>()),
),
BlocProvider<ConnectivityBloc>(
create: (context) => ConnectivityBloc(),
),
BlocProvider<TranslationBloc>(
create: (context) =>
TranslationBloc(context.read<TranslationRepository>()),
),
],
child: MaterialApp(
title: 'Material App',
onGenerateRoute: router.generateRoute,
initialRoute: '/',
)));
First, in my create function I overrided the context with "_" but I got the same error.
Now with this snippet it works perfectly, just put the same context name as my providers before

Flutter: Dynamic Initial Route

Dears,
I am using provider dart package which allows listeners to get notified on changes to models per se.
I am able to detect the change inside my main app root tree, and also able to change the string value of initial route however my screen is not updating. Kindly see below the code snippet and the comments lines:
void main() => runApp(_MyAppMain());
class _MyAppMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<UserProvider>.value(
value: UserProvider(),
),
ChangeNotifierProvider<PhoneProvider>.value(
value: PhoneProvider(),
)
],
child: Consumer<UserProvider>(
builder: (BuildContext context, userProvider, _) {
return FutureBuilder(
future: userProvider.getUser(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final User user = snapshot.data;
String initialScreen = LoginScreen.path;
// (1) I am able to get into the condition
if (user.hasActiveLogin()) {
initialScreen = HomeOneScreen.path;
}
return MaterialApp(
title: 'MyApp',
theme: ThemeData(
primarySwatch: Colors.green,
accentColor: Colors.blueGrey,
),
initialRoute: initialScreen,
// (2) here the screen is not changing...?
routes: {
'/': (context) => null,
LoginScreen.path: (context) => LoginScreen(),
RegisterScreen.path: (context) => RegisterScreen(),
HomeOneScreen.path: (context) => HomeOneScreen(),
HomeTwoScreen.path: (context) => HomeTwoScreen(),
RegisterPhoneScreen.path: (context) => RegisterPhoneScreen(),
VerifyPhoneScreen.path: (context) => VerifyPhoneScreen(),
},
);
},
);
},
),
);
}
}
Kindly Note the Below:
These are are paths static const strings
LoginScreen.path = "login"
RegisterScreen.path = "/register-screen"
HomeOneScreen.path = "home-one-screen"
HomeTwoScreen.path = "home-two-screen"
RegisterPhoneScreen.path = "/register-phone-screen"
VerifyPhoneScreen.path = "/verify-phone-screen"
What I am missing for dynamic initialRoute to work?
Many Thanks
According to this issue described on github issues it is not permissible to have initial route changes. At least this is what I understood. However what I did is that I replaced the initialRoute attribute with home attr. Thus this change mandates that initialScreen becomes a widget var.
The changes is shown below:
void main() => runApp(_MyAppMain());
class _MyAppMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<UserProvider>.value(
value: UserProvider(),
),
ChangeNotifierProvider<PhoneProvider>.value(
value: PhoneProvider(),
)
],
child: Consumer<UserProvider>(
builder: (BuildContext context, userProvider, _) {
return FutureBuilder(
future: userProvider.getUser(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final User user = snapshot.data;
// (1) This becomes a widget
Widget initialScreen = LoginScreen();
if (user.hasActiveLogin()) {
initialScreen = HomeOneScreen();
}
return MaterialApp(
title: 'MyApp',
theme: ThemeData(
primarySwatch: Colors.green,
accentColor: Colors.blueGrey,
),
home: initialScreen,
// (2) here the initial route becomes home attr.
routes: {
'/': (context) => null,
LoginScreen.path: (context) => LoginScreen(),
RegisterScreen.path: (context) => RegisterScreen(),
HomeOneScreen.path: (context) => HomeOneScreen(),
HomeTwoScreen.path: (context) => HomeTwoScreen(),
RegisterPhoneScreen.path: (context) => RegisterPhoneScreen(),
VerifyPhoneScreen.path: (context) => VerifyPhoneScreen(),
},
);
},
);
},
),
);
}
}
Also note on my RegistrationScreen on success api response I did Navigator.of(context).pop()
Thanks