I'm trying to understand multiproviders in Flutter. In my App, one Provider need to change based on a value from an other Provider.
AuthProvider gets initiated higher up in the widget tree on build. Works like a charm with automatic sign in if possible...
In a lower placed widget, I try to initiate two other Providers. One, WhatEver, is not depended on other data and gets initiated on build like it is supposed to using ChangeNotifierProvider.
ProductList however is depended on AuthProvider. If log in status is changed, the ProducList should update accordingly.
In my attempts, I've found out, ie found on SO, that ChangeNotifierProxyProvider is the right way to go. But when I run the App, it seems like the 'create'-part of ChangeNotifierProxyProvider is not initiated when the widget gets build. It seems like the ProductList provider is not initiated until it's read or written to.
What have I misunderstood using MultiProviders and ChangeNotifierProxyProvider?
return MultiProvider(
providers: [
ChangeNotifierProvider<WhatEver>(create: (context) => WhatEver()),
ChangeNotifierProxyProvider<AuthProvider, ProductList>(
create: (_) => ProductList(Provider.of<AuthProvider>(context, listen: false)),
update: (_, auth, productList) => productList..reloadList(auth)
),
],
The ProductList looks like this:
final AuthProvider _authProvider;
static const String _TAG = "Shop - product_list.dart : ";
ProductList(this._authProvider) {
print(_TAG + "ProductList Provider initiated");
reloadList(this._authProvider);
}
void reloadList(AuthProvider authProvider) {
print(_TAG + "ProductList reload started");
if (authProvider.user==null) {
print(_TAG + "ProductList: _authProvider == null");
_loadBuiltInList();
} else {
print(_TAG + "ProductList: user = " + authProvider.user.displayName);
_loadFirestoreList();
}
}
I have code that does this:
ChangeNotifierProxyProvider<AuthService, ProfileService>(
create: (ctx) => ProfileService(),
update: (ctx, authService, profileService) =>
profileService..updateAuth(authService),
),
My ProfileService() does not rely on AuthService being available when it is constructed. The code works fine :)
The ChangeNotifierProxyProvider documentation explicitly describes this approach:
Notice how MyChangeNotifier doesn't receive MyModel in its constructor
anymore. It is now passed through a custom setter/method instead.
Related
I'm using Flutter provider, and I want to access a provider from another provider without using context.
I found many documentations on the internet explaining how to use ProxyProvider, but no one of the implementations was working (I think it's related to the last update 3 months ago).
I posted this question in StackOverflow after encountering a problem in ProxyProvider, but I didn't get an answer.
So now I'm just searching for any way to use a provider inside another one without using context.
You mean a Provider or the service that the Provider encapsulates? If it's the latter, the way that I've done it is by calling a provided service inside another provided service, given that they are all under the same MultiProvider. For example, I have two provided services:
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => FirstService()
),
ChangeNotifierProvider(
create: (_) => SecondService()
),
]
)
FirstService looks like this:
class FirstService extends ChangeNotifier {
List<String> getListOfValues() {
return ['a', 'b', 'c'];
}
}
and for example, I need to access those values from the SecondService, then I do this:
class SecondService extends ChangeNotifier {
void pullDataFromService(BuildContext context) {
FirstService firstService = Provider.of<FirstService>(context, listen: false);
var values = firstService.getListOfValues();
}
}
So pretty much you pass a context to a method where you need to access a provided service and you extract it from the Provider as normal, then access its functionality from the other service where you fetch it.
For my flutter project, I am using the following multiple providers below:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<FirstProvider>(
create: (context) => FirstProvider(),
),
ChangeNotifierProvider<SecondProvider>(
create: (context) => SecondProvider(),
),
ChangeNotifierProvider<ThirdProvider>(
create: (context) => ThirdProvider(),
),
ChangeNotifierProvider<FourthProvider>(
create: (context) => FourthProvider(),
),
],
child: const MainApp(),
);
}
Because sometimes I need to either get data or call functions from different providers from another provider, I am using it like this:
//First Provider
class FirstProvider with ChangeNotifier {
void callFunctionFromSecondProvider({
required BuildContext context,
}) {
//Access the SecondProvider
final secondProvider= Provider.of<SecondProvider>(
context,
listen: false,
);
secondProvider.myFunction();
}
}
//Second Provider
class SecondProvider with ChangeNotifier {
bool _currentValue = true;
void myFunction(){
//Do something
}
}
The callFunctionFromSecondProvider()of the FirstProvider is called from a widget and it will call myFunction() successfully, most of times.
Depending on the complexity of the function, I am sometimes experiencing that I can't access the SecondProvider, presumably due to context being null, when the widget state changes.
I am reading some documents online regarding provider, and they are suggesting changenotifierproxyprovider for what I understood as 1 to 1 provider relationship.
However, in my case, one provider needs to be accessed by multiple providers and vice versa.
Question:
Is there a more appropriate way that I can approach my case where one provider can be accessed by multiple providers?
EDIT:
Accessing provider should also be able to access different variable values without creating a new instance.
Instead of passing context to the callFunctionFromSecondProvider function add the second provider as the parameter. So the function looks like the below.
Not sure this is the correct way of doing that but my context null issue was fixed this way.
void callFunctionFromSecondProvider({
required SecondProvider secondProvider,
}) {
secondProvider.myFunction();
}
}
Alright.
So it looks like Riverpod by the same author is the way to go as it addresses alot of flaws such as Provider being dependent on the widget tree, in my case, where the underlying issue came from.
—--------
For the time being, I still need to use the provider and for a quick and dirty solution, I am providing the context of not only the current widget that I am trying to access the provider, but also passing the parent context of the widget directly, so that in case a modal (for example) is closed, then any subsequent provider call can still be executed using the parent context.
Hope this helps.
I have been learning about Bloc Pattern in Flutter for a few days.
I have a page where I need to generate OTP and validate it.
There are two APIs(generateOtp, validateOtp) two implement this functionality.
In the generateOtp API response, I need to save one key i.e uniqueIdentifier.
Then I need to pass the above uniqueIdentifier and Otp value(User entered) to the validateOtp API.
I have created two separate BLoCs... generateOtpBloc, validateOtpBloc.
Using MultiBLoC Provider I am using these two BLoCs.
Navigator.of(context).push(
MaterialPageRoute<LandingPage>(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider<GenerateOtpBloc>(
create: (context) => GenerateOtpBloc(GenerateOtpInitial())
),
BlocProvider<ValidateOtpBloc>(
create: (context) => ValidateOtpBloc(ValidateOtpInitial())
)
],
child: OtpPage(),
),
),
);
I am able to invoke APIs and get the API responses in my UI page.
But how to save the uniqueIdentifier value which I get in the generateOtp and how to pass this uniqueIdentifier in the second API?
I thought of using setState() to set the state of uniqueIdentifier. But I'm receiving an error.
child: BlocBuilder<GenerateOtpBloc, GenerateOtpState>(
builder: (context, state) {
if (state is GenerateOtpLoading) {
print("**********GenerateOtpLoading*************");
return buildLoading();
} else if (state is GenerateOtpLoaded) {
print("**********GenerateOtpLoaded*************");
***//But Im getting error here.***
***setState(() {
uniqueIdentifier: state.uniqueIdentifier
});***
return buildGenerateOtpWidget(context, state.generateOtpRes);
} else {
print("**********Else*************");
print(state);
}
},
),
),
Both generateOtp and validateOtp requests and responses are completely different... that is why I used two different BLoCs.
Suggest to me the best way to handle this?
Why you try to use two blocs for handle it? you can use two events in one bloc. This is my code in the OTP login project similar to your project:
class LoginBloc extends Bloc<LoginEvent, LoginState> {
FirstApiClass _firstApi;
SecondApiClass _secondApi;
LoginBloc() : super(Loading()) {
_firstApi = FirstApiClass();
_secondApi = SecondApiClass();
}
#override
Stream<LoginState> mapEventToState(
LoginEvent event,
) async* {
if (event is GenerateOtp) {
// Use FirstApiClass
} else if (event is ValidateOtpBloc) {
// Use SecondApiClass
}
}
}
However, you can also use one Api class for this situation!
I hope it's useful for you.
I am new to Flutter development.
I am building application where once users login they are shown list of posts.
If the user is not login still they are shown some random post.
I got parts of the application various posts in the internet.
This is what I did
class TestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthBase>(
create: (context) => Auth(),
),
StreamProvider<User>.value(
value: Auth().onAuthStateChanged,
),
ProxyProvider<User, Database>(
update: (BuildContext context, User user, Database db) {
return user == null
? FirestoreDatabase(uid: null)
: FirestoreDatabase(uid: user.uid);
}),
ChangeNotifierProxyProvider<Database, PostProvider>(
create: (context) => PostProvider(),
update: (ctx, database, previousProvider) {
print("at ChangeNotifierProxyProvider $previousProvider");
return previousProvider.update(database);
},
),
],
child: MaterialApp(
title: 'Test Project',
home: LandingPage(),
),
);
}
My idea is;
if the user is logged in StreamProvider<User>.value will give that user to
ProxyProvider<User, Database> and it will create database with that user.
And that database in turn will be used by ChangeNotifierProxyProvider<Database, PostProvider> to create the provider that actually get posts. It uses the database to get the posts.
I noticed even if the user is logged in at the start I get a null value for the user then immediately i get the actual user.
In the landing page, I only have the following line and it generate an error
final provider = Provider.of<PostProvider>(context);
I noticed the cause for the error was this line;
previousProvider.update(database);
First time time Postprovider constructor and update methods get called without any problem. (here we get null user)
When the FirestoreDatabase get created with the actual user "previousProvider" is null.
This is the reason for the error.
Not sure why this is happening or how to fix it.
Is there a better way of doing this?
Initially getting a null value even when there is a logged in user may be the reason. How to prevent it?
I will try to explain your code a bit so we are on the same page
Provider<AuthBase>(
create: (context) => Auth(), //this subscribe an Auth Class to the provider
),
StreamProvider<User>.value(
value: Auth().onAuthStateChanged,
/// This subscribe a stream from another Auth Class instance
/// Unless Auth its a singleton class, this is not related to the previous Provider
/// and changes to Provider<AuthBase> won't affect this Stream
),
I noticed even if the user is logged in at the start I get a null
value for the user then immediately i get the actual user.
That happens when using Stream, while the first value is captured (sometimes is delayed a tick) its null, you can use initialData parameter if you know the first value while the stream subscription is done
Finally
ChangeNotifierProxyProvider<Database, PostProvider>(
create: (context) => PostProvider(),
update: (ctx, database, previousProvider) {
print("at ChangeNotifierProxyProvider $previousProvider");
return previousProvider.update(database);
},
),
update expects to return a value of type PostProvider, but you're returning whatever the method previousProvider.update(database) returns (which I think it just updates some inner variable or something, I believe it's some void method maybe?)
Change the update like this
update: (ctx, database, previousProvider) {
print("at ChangeNotifierProxyProvider $previousProvider");
return previousProvider..update(database);
/// It's the same as writing this
/// previousProvider..update(database);
/// return previousProvider;
},
I need the UserBloc to be accessible globally around my App because it seems like is not possible to access a BlocProvider in routes which are not directly dependent on the main widget tree.
The WidgetTree structure of my app (the relevant part) right now is:
App
├── HomePage
│ └── LessonPage
└── ProfilePage
From the LessonPage is possible to navigate to the ProfilePage but this breaks the child-parent relationship for which you can call BlocProvider.of.
So i wonder how should I handle this properly.
The solution i'm using right now is the following, but this is causing me issues since close of UserBloc is never called since App is never really disposed, leaving the Stream open when it should not.
runApp(MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>(
create: (context) =>
AuthBloc(authRepository: authRepository)..add(AppStarted()),
),
//.. other BlocProvider not relevant to this question ..
],
child: BlocProvider<UserBloc>(
create: (BuildContext context) => UserBloc(
userRepository: UserRepository(),
authBloc: BlocProvider.of<AuthBloc>(context),
),
child: App(
authRepository: authRepository,
),
)));
});
Another possible solution i'm seeing here it will be to handle this in the route handler, for instance:
//... some more code...
#override
HandlerFunc get handlerFunc => (BuildContext context, Map<String, List<String>> params) {
return BlocProvider<UserBloc>(
create: (BuildContext context) => UserBloc(
userRepository: UserRepository(),
authBloc: BlocProvider.of<AuthBloc>(context),
),child: ProfilePage(email: params[email][0])
);
};
}
But this will create several instances of the UserBloc which will have to call close as much time as they got instantiated basically performing, at best, useless operations.
I don't really like neither of the 2 solution, that's why i'm seeking for help.
*Note: for handling routes i'm using https://pub.dev/packages/fluro so that method handleFunc is taken from that, it will simply be called on Navigator.pushNamed of any registered route.
Which one is the advisable approach?
Any other solution i'm not seeing here?
Thank you in advance for your help!