StatenotifierProvider with State AND ProxyProvider? - flutter

I have been using State Notifier provider in a Flutter app to keep track of states.
This works well
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StateNotifierProvider<PreferencesNotifier, PreferencesState>(
create: (_) => PreferencesNotifier(),
),
...
But now I want my Preferences notifier to have an instance of AuthNotifier.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StateNotifierProvider<AuthNotifier, AuthState>(
create: (_) => AuthNotifier(),
),
StateNotifierProvider<PreferencesNotifier, PreferencesState>(
create: (_) => PreferencesNotifier(),
),
ProxyProvider<AuthNotifier,PreferencesNotifier>(...),
...
Is giving me two preferences notifiers.
How do I get the preferences provider have an auth provider injected AND hav its state associated?

Related

My changenotifierprovider is not updating. Not sure why

Here is my change Notifier class.
class UserChamaNotifier with ChangeNotifier {
final List<UserChama> _userChamaList = [];
UnmodifiableListView<UserChama> get userchamaListy =>
UnmodifiableListView(_userChamaList);
void addUserChama(UserChama userchama) {
_userChamaList.add(userchama);
notifyListeners();
}
}
I have created the provider in main.dart:
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => _appStateManger,
),
ChangeNotifierProvider(
create: (context) => _profileManager,
),
ChangeNotifierProvider(
create: (context) => UserChamaNotifier(),
)
],
Then I proceed to add a chama object to my list:
UserChama userChama =
UserChama(id: s['Id'], phone: s['Phone'], name: s['Name']);
print(userChama.phone);
Provider.of<UserChamaNotifier>(context).addUserChama(userChama);
Here i try to access the list through the provider:
class ChamaList extends StatelessWidget {
const ChamaList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
UserChamaNotifier userChamaNotifier =
Provider.of<UserChamaNotifier>(context, listen: true);
return Text(userChamaNotifier.userchamaListy.length.toString());
}
}
At this point, i have experimented alot and i still don't have the correct way of implementation.
While adding data, set listen:false
Provider.of<UserChamaNotifier>(context,listen:false)
.addUserChama(userChama);
Check more how listen: false works when used with Provider.of(context, listen: false).

Is this the correct way to use Bloc in flutter?

How to use Bloc in flutter. What is the best way to use it? to wrap the whole app with blocprovider?
runApp(
RepositoryProvider(
create: (context) => API(),
child: MultiBlocProvider(
providers: [
BlocProvider<GlobalViewBloc>(
lazy: false,
create: (BuildContext context) =>
GlobalViewBloc(context.read<API>()),
),
BlocProvider<CountryDetailViewBloc>(
lazy: false,
create: (BuildContext context) =>
CountryDetailViewBloc(context.read<API>()),
),
],
child: MaterialApp(
home:MyApp(),
),
),
));
there are two ways of accessing bloc:
1 - global declaration
you declare a bloc variable inside your bloc and all widgets and whole app will have access to that variable. like this:
final bloc = YourBloc();
2 - using provider
in this declaration you have to define the provider in the highest widget which you want having the access to bloc and all of its children will have access to that bloc:
class WD extends StatelessWidget {
#override
Widget build(BuildContext context) {
final bloc = YourBlocProvider.of(context);
/....
);
}
}

passing data to provider - setState() or markNeedsBuild() called during build

I'm fetching data from the local database using SQflite in my main.dart and passing it to ProvideRecords widget using FutureProvider :
Future<List<WeightRecord>> _getRecords() async {
List<WeightRecord> records = await RecordsDatabase.instance.getRecords();
return records;
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureProvider<List<WeightRecord>?>(
create: (context) {
return _getRecords();
},
initialData: [],
catchError: (_, error) => [
WeightRecord(
date: DateTime.now(), weight: 00.0, note: 'hasError: $error')
],
child: ProvideRecords(),
),
);
}
}
then in the ProvideRecords widget I pass the data again to another provider :
class ProvideRecords extends StatelessWidget {
const ProvideRecords({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<List<WeightRecord>?>(builder: (context, list, child) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => RecordsListModel()),
ChangeNotifierProvider(create: (context) => ButtonMode())
],
builder: (context, child) {
Provider.of<RecordsListModel>(context, listen: true)
.updateRecordsList(list);
return Home(list: list);
});
}
});
}
}
the code works but I'm getting setState() or markNeedsBuild() called during build. because I'm using Provider.of<RecordsListModel>(context, listen: true).updateRecordsList(list); in the builder function. However I couldn't find another way of passing the data from FutureProvider to the RecordListModel, what can I do?
Its mainly an issue of how you have structured your code
Its recommended to have your multiproviders at the apps entry point..
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
//my providers here
ChangeNotifierProvider(create: (context) => ButtonMode())
... material app
then when adding data no need to set listen to true
Provider.of<RecordsListModel>(context, listen: false)
.updateRecordsList(list);
since home depends on data from the above provider use a consumer
Home(list: list);
// consume your provider
Consumer<RecordsListModel>(
builder:
(context, RecordsListModel recordsP, child) {
return Home(list:recordsP.list);
},
)

How to have single instance of a bloc across other blocs

How can I avoid creating another instance of some bloc class?
I have two blocs: LoginBloc and AuthBloc . and LoginBloc accepts an instance of AuthBLoc and here is problem:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final UserRepository repository=UserRepository();
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => LoginBloc(
//******Extra Instance of AuthBloc in being created here because LoginBloc needs it to listen********
authBloc: AuthBloc(SInitialState(),userRepository: repository), userRepository:repository
),
),
BlocProvider(
create: (context) => AuthBloc(SInitialState(),userRepository: repository),
)
],
child: MaterialApp(...);
Thanks in advance.
You can assign AuthBloc to a variable inside build() method.
Or, just nest one BlockProvider inside another one:
BlocProvider<AuthBloc>(
create: (context) => AuthBloc(SInitialState(),userRepository: repository),
child: BlocBuilder<AuthBloc, AuthState>(
builder: (BuildContext context, AuthState authState) {
return BlocProvider(
create: (context) => LoginBloc(
authBloc: context.bloc<AuthBloc>(),
userRepository: repository,
),
child: MaterialApp(...),
)
})
),
But the best solution is Bloc to Bloc communication
if I got you question correctly, you might use GetIt
and here is a sample code
MultiBlocProvider(
providers: [
BlocProvider<SginInBloc>(
create: (BuildContext context) => sl<SginInBloc>(),
child: SginInPage(),
),....
so that you will get access to the instans of that Bloc that will be created
final sl = GetIt.instance;
sl.registerFactory(() => SginInBloc();
here I'm using registerFactory but you can use registerSingleton depends on what you want to achive
void main() {
final UserRepository repository=UserRepository();
runApp(MyApp(authBloc: AuthBloc(),userRepo : repository));
}
class MyApp extends StatelessWidget {
final AuthBloc authBloc;
final UserRepository userRepo;
MyApp({required this.authBloc,required this.userRepo});
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => LoginBloc(
//******Extra Instance of AuthBloc in being created here because LoginBloc needs it to listen********
authBloc: authBloc,userRepository: userRepo), userRepository:userRepo
),
),
BlocProvider(
create: (context) => authBloc,
)
],
child: MaterialApp(...);
Try this

How can I stop my change notifier provider from rebuilding my parent material app when I am rendering my child material app?

I have a app class that returns a MaterialApp() which has it's home set to TheSplashPage(). This app listens to the preferences notifier if any preferences are changed.
Then in TheSplashPage() I wait for some conditionals to be true and if they are I show them my nested material app.
Side Note: I use a material app here because it seems more logical since it has routes that the parent material app shouldn't have. And also once the user is unauthenticated or gets disconnected I want the entire nested app to shut down and show another page. This works great!
But my problem is the following. Both apps listen to ThePreferencesProvider() so when the theme changes they both get notified and rebuild. But this is a problem because whenever the parent material app rebuilds, it returns the splash page. So now I am back on TheSplashPage() whenever I change a setting on TheSettingsPage().
So my question is how can I stop my application from going back to the TheSplashPage() whenever I change a setting?
Main.dart
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(create: (_) => PreferencesProvider()),
ChangeNotifierProvider<ConnectionProvider>(
create: (_) => ConnectionProvider(),
),
ChangeNotifierProvider<AuthenticationProvider>(create: (_) => AuthenticationProvider()),
],
child: Consumer<PreferencesProvider>(builder: (context, preferences, _) {
return MaterialApp(
home: TheSplashPage(),
theme: preferences.isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
debugShowCheckedModeBanner: false,
);
}),
);
}
}
TheSplashPage.dart
class TheSplashPage extends StatelessWidget {
static const int fakeDelayInSeconds = 2;
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.delayed(new Duration(seconds: fakeDelayInSeconds)),
builder: (context, delaySnapshot) {
return Consumer<ConnectionProvider>(
builder: (BuildContext context, ConnectionProvider connectionProvider, _) {
if (delaySnapshot.connectionState != ConnectionState.done ||
connectionProvider.state == ConnectionStatus.uninitialized) return _buildTheSplashPage(context);
if (connectionProvider.state == ConnectionStatus.none) return TheDisconnectedPage();
return Consumer<AuthenticationProvider>(
builder: (BuildContext context, AuthenticationProvider authenticationProvider, _) {
switch (authenticationProvider.status) {
case AuthenticationStatus.unauthenticated:
return TheRegisterPage();
case AuthenticationStatus.authenticating:
return TheLoadingPage();
case AuthenticationStatus.authenticated:
return MultiProvider(
providers: [
Provider<DatabaseProvider>(create: (_) => DatabaseProvider()),
],
child: Consumer<PreferencesProvider>(
builder: (context, preferences, _) => MaterialApp(
home: TheGroupManagementPage(),
routes: <String, WidgetBuilder>{
TheGroupManagementPage.routeName: (BuildContext context) => TheGroupManagementPage(),
TheGroupCreationPage.routeName: (BuildContext context) => TheGroupCreationPage(),
TheGroupPage.routeName: (BuildContext context) => TheGroupPage(),
TheSettingsPage.routeName: (BuildContext context) => TheSettingsPage(),
TheProfilePage.routeName: (BuildContext context) => TheProfilePage(),
TheContactsPage.routeName: (BuildContext context) => TheContactsPage(),
},
theme: preferences.isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
debugShowCheckedModeBanner: false,
)),
);
}
});
});
});
}
TheSettingsPage.dart
Switch(
value: preferences.isDarkMode,
onChanged: (isDarkmode) => preferences.isDarkMode = isDarkmode,
),
You fell for the XY problem
The real problem here is not "my widget rebuilds too often", but "when my widget rebuild, my app returns to the splash page".
The solution is not to prevent rebuilds, but instead to change your build method such that it fixes the issue, which is something that I detailed previously here: How to deal with unwanted widget build?
You fell for the same issue as in the cross-linked question: You mis-used FutureBuilder.
DON'T:
#override
Widget build(BuildContext context) {
return FutureBuilder(
// BAD: will recreate the future when the widget rebuild
future: Future.delayed(new Duration(seconds: fakeDelayInSeconds)),
...
);
}
DO:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
// Cache the future in a StatefulWidget so that it is created only once
final fakeDelayInSeconds = Future<void>.delayed(const Duration(seconds: 2));
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Rebuilding the widget no longer recreates the future
future: fakeDelayInSeconds,
...
);
}
}
When using Consumer, you are forcing the widget to rebuild every time you notify listeners.
To avoid such behaviour, you can use Provider.of as stated in ian villamia's answer, as it can be used wherever you need it, and only where you need it.
The changes in your code to use Provider.of would be removing the consumer and adding Provider.of when resolving the theme as follows:
theme: Provider.of<PreferencesProvider>(context).isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
HOWEVER if you want to keep using Consumer, you can do something else:
The child property on the Consumer widget is a child that is not rebuilt. You can use this to set the TheSpashScreen there, and pass it to the materialApp through the builder.
TL:DR
Use Provider.of if you need only to tap into one variable for simplicity.
Use Consumer with its child property as the child doesn't rebuild. <= Better performance
Using Provider.of
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(create: (_) => PreferencesProvider()),
ChangeNotifierProvider<ConnectionProvider>(
create: (_) => ConnectionProvider(),
),
ChangeNotifierProvider<AuthenticationProvider>(create: (_) => AuthenticationProvider()),
],
child: Builder(
builder: (ctx) {
return MaterialApp(
home: TheSpashPage(),
theme: Provider.of<PreferencesProvider>(ctx).isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
);
}),
);
}
}
Using Consumer
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(create: (_) => PreferencesProvider()),
ChangeNotifierProvider<ConnectionProvider>(
create: (_) => ConnectionProvider(),
),
ChangeNotifierProvider<AuthenticationProvider>(create: (_) => AuthenticationProvider()),
],
child: Consumer<PreferencesProvider>(
child: TheSpashPage(),
builder: (context, preferences, child) {
return MaterialApp(
home: child,
theme: preferences.isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
debugShowCheckedModeBanner: false,
);
}),
);
}
}
I hope this is helpful for you!
basically there's 2 ways in using a provider
one it the current one you're using which is the consumer type,
is using the instance of a provider
final _preferencesProvider= Provider.of<PreferencesProvider>(context, listen: false);
you can toggle the "listen:true" if you want the widget to rebuild when notifyListeners() are called... false if otherwise
also just use _preferencesProvider.someValue like any other instance