How to consume Provider after navigating to another route? - flutter

My app have 2 type of user: admin and normal. I show admin screen to admin and normal screen to normal user. All user need access Provider model1. But only admin need access model2.
How I can initialise only model2 for admin user?
For example:
class MyApp extends StatelessWidget {
final model1 = Model1();
final model2 = Model2();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => model1),
ChangeNotifierProvider(builder: (_) => model2),
],
child: MaterialApp(
I want to put model2 only in AdminScreen. But if I do this other admin pages cannot access model2 after Navigator.push because they are not descendant of AdminScreen.
Thanks for help!

You can pass the existing model forward creating a new ChangeNotifierProvider using the value of the existing one.
For that, you need to use ChangeNotifierProvider.value constructor passing the existing model2 as the value.
If you already have an instance of ChangeNotifier and want to expose it, you should use ChangeNotifierProvider.value instead of the default constructor.
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider<Model2>.value(
value: model2,
child: SecondRoute(),
),
);

Related

Scoping Lifecycle of ChangeNotifier to just a specific number of screens

I have a ChangeNotifier Subclass that I am sharing between a number of screens like this:
final value = MyChangeNotifier();
MaterialApp(
routes: {
'/list': (_) => ChangeNotifierProvider.value(value: value, child: ListScreen()),
'/detials': (_) => ChangeNotifierProvider.value(value: value, child: DetailsScreen()),
'/other_screen': (_) => OtherScreen(),
}
)
I want to dispose my ChangeNotifier once the user has left the last screen in this flow, and I want to create a new Instance of the shared ChangeNotifier once user re-enters the given flow.
Is it possible to do with Provider?

Flutter Authentication and Data Persistence with Provider Architecture

I come from a HTML/CSS/JQUERY for frontend background and mostly I have been a Nodejs backend developer.
However a project I took over was based on HTML/CSS/JQuery and PHP and I am doing quite well in that until they needed a mobile application.
So my choice was flutter since it was hot at that moment.
I have mastered flutter basics and took provider as my state management and also incorporated logic inside it.
However I have 2 issue that I am not being able to figure out.
One is state persistence in provider even when app is forced closed.
Maintain login persistence unless logged out and clear state if logged out.
For 1. I haven't been able to figure out a concrete solution except Application Storage but then I would have to write alot of code more to store all the data and class into it.
For 2. I have done some experimental method as below
runApp(
ChangeNotifierProvider(
create: (_) => UserRepository(), // Here is my auth state provider
child: MyApp(),
),
);
In UserRepository class user data like user id are stored in SharedPreferences which checks if there is userdata is stored or not hence Status becomes Authenticated or Uninitialized per condition.
Below is a Stateful widget that sets the status with respect to UserRepository
class _MyAppState extends State<MyApp> {
ProjectRepository projectRepository = ProjectRepository();
UIKeypadRepository uiKeypadRepository = UIKeypadRepository();
PODRepository podRepository = PODRepository();
Status currentStatus;
//Also there are more Change notifier class here that manages state of various features of the app
void initProviders() {
projectRepository = ProjectRepository();
uiKeypadRepository = UIKeypadRepository();
podRepository = PODRepository();
}
#override
void initState() {
currentStatus = Provider.of<UserRepository>(context, listen: false).status;
//if not logged out current status is authenticated
super.initState();
}
#override
Widget build(BuildContext context) {
return Consumer(builder: (context, UserRepository authStatus, child) {
if (currentStatus != Status.Authenticated) {
initProviders(); // this function is called and all changenotifier classes are reinitialized
}
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: projectRepository),
ChangeNotifierProvider.value(value: uiKeypadRepository),
ChangeNotifierProvider.value(value: podRepository),
],
//instead of create I used value so if classes are initialized initial values are taken else existing values are made available to below the widget tree
child: MaterialApp(
home: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: LandingPage())),
);
});
}
}
You can use authStateChanges() and StreamBuilder to get the authentication status.
Reference

Flutter Provider on Navigator inject multiple objects

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);
.
.
.
}

How to build the architecture of a flutter app

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
);
}
}

How to use a provider inside of another provider in Flutter

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.