I want to create an app that has an authentication service with different permissions and functions (e.g. messages) depending on the user role.
So I created one Provider for the user and login management and another one for the messages the user can see.
Now, I want to fetch the messages (once) when the user logs in. In Widgets, I can access the Provider via Provider.of<T>(context) and I guess that's a kind of Singleton. But how can I access it from another class (in this case another Provider)?
From version >=4.0.0, we need to do this a little differently from what #updatestage has answered.
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => Auth()),
ChangeNotifierProxyProvider<Auth, Messages>(
update: (context, auth, previousMessages) => Messages(auth),
create: (BuildContext context) => Messages(null),
),
],
child: MaterialApp(
...
),
);
Thanks for your answer. In the meanwhile, I solved it with another solution:
In the main.dart file I now use ChangeNotifierProxyProvider instead of ChangeNotifierProvider for the depending provider:
// main.dart
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => Auth()),
ChangeNotifierProxyProvider<Auth, Messages>(
builder: (context, auth, previousMessages) => Messages(auth),
initialBuilder: (BuildContext context) => Messages(null),
),
],
child: MaterialApp(
...
),
);
Now the Messages provider will be rebuilt when the login state changes and gets passed the Auth Provider:
class Messages extends ChangeNotifier {
final Auth _authProvider;
List<Message> _messages = [];
List<Message> get messages => _messages;
Messages(this._authProvider) {
if (this._authProvider != null) {
if (_authProvider.loggedIn) fetchMessages();
}
}
...
}
Passing another provider in the constructor of the ChangeNotifierProxyProvider may cause you losing the state, in that case you should try the following.
ChangeNotifierProxyProvider<MyModel, MyChangeNotifier>(
create: (_) => MyChangeNotifier(),
update: (_, myModel, myNotifier) => myNotifier
..update(myModel),
);
class MyChangeNotifier with ChangeNotifier {
MyModel _myModel;
void update(MyModel myModel) {
_myModel = myModel;
}
}
It's simple: the first Provider provides an instance of a class, for example: LoginManager. The other Provides MessageFetcher. In MessageFetcher, whatever method you have, just add the Context parameter to it and call it by providing a fresh context.
Perhaps your code could look something like this:
MessageFetcher messageFetcher = Provider.of<ValueNotifier<MessageFetcher>>(context).value;
String message = await messageFetcher.fetchMessage(context);
And in MessageFetcher you can have:
class MessageFetcher {
Future<String> fetchMessage(BuildContext context) {
LoginManager loginManager = Provider.of<ValueNotifier<LoginManager>>(context).value;
loginManager.ensureLoggedIn();
///...
}
}
Seems like this would be a lot easier with Riverpod, especially the idea of passing a parameter into a .family builder to use the provider class as a cookie cutter for many different versions.
Related
I want to create a change app theme mode and I saw a way of creating it with Provider but I'm new to Provider. For Example, I want to add some codes like this
(the highlighted code)
in my main which consists of many routes
You want to change the theme of the app, then you need to move provider up so it can cover the widget (App in this case) state,
You could do something like this in your main method :
runApp(ChangeNotifierProvider(
create: (context) => ThemeProvider(),
child:MyApp()
);
now in the case of children you could simply call provider in the build method like this
Widget build(){
var themeProvider = Provider.of<ThemeProvider>(context);
}
or you could use the consumer widget
Consumer<ThemeProvider>(
builder: (context, provider, child) {
//return something
}
)
I suggest you to move your ChangeNotifierProvider to your runApp() method
runApp(
ChangeNotifierProvider<ThemeProvider>(
create: (_) => ThemeProvider(),
child: MyApp(),
),
),
Where your MyApp() is just all of your app extracted to its own widget.
Then you can actually easily access it as you wish with a Consumer widget on your build method.
return Consumer<ThemeProvider>(
builder: (BuildContext context, ThemeProvider provider, _) {
return MaterialApp(
theme: provider.myTheme,
...
);
}
)
not sure why my ChangeNotifier isn't working.
This is my Class:
class LoadingProv with ChangeNotifier {
bool globalLoading;
void setGlobalLoading(bool truefalse) {
if (truefalse == true) {
globalLoading = true;
} else {
globalLoading = false;
}
notifyListeners();
}
bool get getGlobalLoadingState {
return globalLoading;
}
}
This is my Multiprovider in main.dart:
MultiProvider(
providers: [
ChangeNotifierProvider<MapData>(create: (ctx) => MapData()),
ChangeNotifierProvider<LoadingProv>(create: (ctx) => LoadingProv()),
],
child: MaterialApp(
This is my code in the main.dart Widget build(BuildContext context):
Consumer<LoadingProv>(builder: (context, loadingState, child) {
return Text(loadingState.getGlobalLoadingState.toString());
}),
And this is how I call setGlobalLoading:
final loadingProv = LoadingProv();
loadingProv.setGlobalLoading(true);
Unfortunately my loadingState.getGlobalLoadingState is always printed as false. But I can debug that it becomes actually true.
From my understanding, you are creating 2 LoadingProv object.
One is when initialising the Provider
ChangeNotifierProvider<LoadingProv>(create: (ctx) => LoadingProv()),
One is when some places you call
final loadingProv = LoadingProv();
So the one you updating is not the one inherit on the widget, then you cannot see the value updating the Consumer.
(1) if you want to keep create along with the create method, you should call setGlobalLoading via
Provider.of<LoadingProv>(context).setGlobalLoading(true);
(2) Or if you want to directly access the value like loadingProv.setGlobalLoading(true), you should initialise your provider like this
final loadingProv = LoadingProv();
MultiProvider(
providers: [
ChangeNotifierProvider<MapData>(create: (ctx) => MapData()),
ChangeNotifierProvider<LoadingProv>.value(value: loadingProv),
],
you can use this code to read data when change it automatically refresh the Text widget
Text(context.watch<LoadingProv>().getGlobalLoadingState.toString());
on for calling the void you can use this
context.read<LoadingProv>().setGlobalLoading(true);
I have a problem with synchronize Providers creation.
I'm new to flutter, I'll try to explain better as i can.
In my main.dart i have two providers one for the user auth and one for another widget into the code, it simply have a list to display.
I use ChangeNotifierProxyProvider because all other provider needs to access to user auth tokens and details. All methods for user to store,read tokens are in the userProvider.
When UserProvder.init() is called the object is created but is still not ready beacuse of the http request, the code in the main continue to execute and pass the UserProvider to the Conto Provider that thinks UserProvider is ready but it is not. When the ContoProvider start to retrive the List with the token inside UserProvider access through userService failed the call because is still null.
So, How can i synchronize the creation of provider in order to wait the UserProvider to be full ready and then initialize all other providers?
main.dart
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<UserProvider>(
create: (_) => UserProvider.init(),
),
ChangeNotifierProxyProvider<UserProvider, ContoProvider>(
create: (_) {
return ContoProvider.init(
Provider.of<UserProvider>(_, listen: false));
},
update: (_, userProvider, contoProvider) =>
contoProvider..update(userProvider),
),
],
child:...
userProvider.dart
User activeUser = User(null, null, null);
UserStatus status = UserStatus.Checking;
UserProvider.init() {
checkUserPresence();
}
void checkUserPresence() async {
/*here i check if in secure storage there is a refreshtoken if there is i make an http
request for a new token and a second request to fill the User into UserProvider so i need await async*/
}
ContoProvider.dart
UserProvider userService;
ContoProvider.init(UserProvider user) {
userService = user;
lookUpConti();
}
void lookUpConti() async {
/*here i make an http call to retrive some data, i access to
userService for get token and refresh token if needed*/
}
You can use WidgetsFlutterBinding.ensureInitialized()
void main() {
/** WidgetsFlutterBinding.ensureInitialized() is required in Flutter v1.9.4+ before using any plugins if the code is executed before runApp. */
WidgetsFlutterBinding.ensureInitialized();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<UserProvider>(
create: (_) => UserProvider.init()),
ChangeNotifierProxyProvider<UserProvider, ContoProvider>(
create: (_) {
return ContoProvider.init(
Provider.of<UserProvider>(_, listen: false));
},
update: (_, userProvider, contoProvider) =>
contoProvider..update(userProvider),
),
],
child: MyApp(),
),
}
PS: I will recommend that you separate your repositories from your provider. That is, not API call to external/web resources should be found in your provider. You can pass such class into your provider as arguments.
New to Flutter, I have Provider on top of my app with the class Events. Is there any way to inject more than one object in Navigator builder like MapBox(events.itmaps, events.maps) for example?
class Events {
final String site, fb, itmaps, maps;
Events({this.site, this.fb, this.itmaps, this.maps});
}
void main() {
final events = Events();
runApp(
Provider<Events>.value(
value: events,
child: MyApp(),
),
);
}
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => MapBox(events.itmaps),
),
);
}
As I understood you have some conceptual misunderstandings!. I'll describe two scenarios, hopefully one of them will fit to your requirement.
Using MultiProvider to inject many Dependencies(Classes/Objects/Stores)
As https://pub.dev/packages/provider described it would be like this:
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
Passing arguments/props to Widgets
Despite the descriptions and keywords you used, by looking at your code I can guess you want to pass a second or more input/arguments/props to your screen widget. Every widget input is a class constructor argument. So you need just declare the desire parameters in the constructor of your MapBox class.
class MapBox extends StatelessWidget {
EventModel firstInput;
OtherEventModel secondInput;
MapBox(this.firstInput, this.secondInput);
.
.
.
}
I'm implementing a chat-based app in Flutter. I was thinking of using Provider package to create two main notifiers: UserService and ChatService. The first one handles the signIn (and all the other functions user-related), while the latter handles chat specific functions. However, the chatService needs to access the UserService for some functionalities. I tried to use ProxyProvider and this is the code:
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<UserService>(builder: (_) => UserService.instance()),
ChangeNotifierProxyProvider<UserService, ChatService>(builder: (_, user, chatService) => ChatService.init(user))
],
child: MaterialApp(
...
),
);
}
}
However, when I run the app, flutter throws this error:
Tried to use Provider with a subtype of Listenable/Stream (ChatService).
This is likely a mistake, as Provider will not automatically update dependents
when ChatService is updated. Instead, consider changing Provider for more specific
implementation that handles the update mechanism, such as:
ListenableProvider
ChangeNotifierProvider
ValueListenableProvider
Thank you!
It's not clear which "architecture" you are going to use, Provider is simply a mechanism to retrieve objects in the widget tree in a safe way.
Assuming you mean UserService and ChatService, and these are ChangeNotifiers (could be BLoC or anything else) - here's an example of how you'd hook them up with Provider:
main() {
runApp(MultiProvider(
providers: [
ChangeNotifierProvider<UserService>(create: (_) => UserService()),
ChangeNotifierProxyProvider<UserService, ChatService>(
create: (_) => ChatService(),
update: (_, userService, chatService) => chatService..userService= userService
),
],
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<ChatService>(
builder: (context, chatService, _) => Text(chatService.currentUser.lastMessage) // or whatever you need to do
);
}
}