I use mvvm architecture and I want to update my data in the view model without the need to call rebuild methods, use stream or future builders, and use any external packages. Overall I want to listen to changes not only on a specific variable but on the entire class instead
I tried using inherited widget but this doesn't help
for that, you should any os flutter state manager. You can use Bloc but for MVVM architecture you can use Getx. Try to learn Getx .obs and GetxBuilder to listen to new changes and update your application according to them.
Package : https://pub.dev/packages/get
you can use Provider as a state management in your project to make all the data that you need alive in your project:
step 1) please add provider: ^6.0.5 (or any version that is compatible) in your pubspec.yaml and call flutter pub get.
step 2) now you should create a provider class to make all the variables that you want to use everywhere, alive. please create a dart file as below:
import 'package:flutter/material.dart';
class ExpampleClassProvider extends ChangeNotifier {
String? _yourData;
void setYourData(String? newData){
_yourData = newData;
notifyListeners();
}
String? get yourData => _yourData;
}
as you see when _yourData is changed, it tells you and you can use this data where ever you want by providing ExpampleClass,even you can set a data in your first screen and use that data in the last screen without passing data step page by step.
step 3) you should add the provider to main.dart:
MultiProvider(
providers: [
// you are adding your provider
ListenableProvider.value(value: ExpampleClassProvider()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: ...........
),
);
now you can use it where ever you want:
Provider.of<ExpampleClass>(context, listen: false).yourData;
and even you can use that data in your widgets like this by using Consumer everywhere you want:
Consumer<ExpampleClassProvider>(
builder: (context, exampleClassProvider ,snapshot) {
return Text(exampleClassProvider!.yourData);
}
)
read the Provider document carefully.
happy coding...
Related
In case of error, I want to redirect my users to the same flow again (in order to reset the flow state).
I'm using the auto_route package for navigation, and I can see that router.replace and router.replaceAll do not replace (remove and insert from the stack) the same routes again (it just updates them).
From the official documentation:
// removes last entry in stack and pushs provided route
// if last entry == provided route page will just be updated
router.replace(const BooksListRoute())
// This's like providing a completely new stack as it rebuilds the stack
// with the list of passed routes
// entires might just update if already exist
router.replaceAll([
LoginRoute()
]);
How can I "hard reset" replace a route, or stack of routes, using auto_route package?
Add this package :
dependencies:
flutter_phoenix: ^1.1.0
Example:
void main() {
runApp(
Phoenix(
child: App(),
),
);
}
Phoenix.rebirth(context);
Phoenix restarts your application at the application level, rebuilding your application widget tree from scratch, losing any previous state.
Phoenix does not fully restart your application process at the OS level.
Solved.
In order to "hard reset" the same page using auto_route package, Use pushAndPopUntil function, with a predicate set to always false.
That way, all pages will be removed, and the provided stack will be inserted instead (in other words, a full replacement, even for the same page).
Then your page will reinitiate, and all state values will be reset (same as redirecting to a new page).
await _router.pushAndPopUntil(
const LoginRoute(),
predicate: (_) => false,
)
I searched a lot about "what are benefits of using named route to navigate between screens". And I can't find any actual benefits, actually it has many disadvantages and annoying.
1. In flutter document, it said that named routes use to avoid code duplication.
For example, if I want to navigate to SecondRoute with String one argument, it change from this
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute('Some text')),
);
to this
Navigator.pushNamed(context, SecondRoute.routeName, arguments: 'Some text');
and I need to register it in main file
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == SecondRoute.routeName) {
final String text = settings.arguments as String;
return MaterialPageRoute(
builder: (context) => SecondRoute(text),
);
}
},
);
And if I have more routes, I need to handle and assign arguments for every routes. Isn't that more duplication and complexity?
Also setting.arguments has no-type, isn't that very bad?
2. You can't select which constructor to use when using pushNamed.
For example, I have two constructor default and otherConstructor
class SecondRoute extends StatelessWidget {
static const String routeName = '/second';
String? text;
SecondRoute(this.text);
SecondRoute.otherConstructor(String text) {
this.text = 'Other Constructor: ' + text;
}
}
How can I tell pushNamed which one I want to use.
I have an idea to pass constructor name as an argument and check it like this
Navigator.pushNamed(context, SecondRoute.routeName, arguments: ['default' or 'otherConstructor','Some text']);
In main file
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == SecondRoute.routeName) {
final args = settings.arguments as List<String>;
if (args[0] == 'otherConstructor') {
return MaterialPageRoute(
builder: (context) => SecondRoute.otherConstructor(text),
);
} else if (args[0] == 'default') {
return MaterialPageRoute(
builder: (context) => SecondRoute(text),
);
}
}
},
);
But this is very very complicate and obviously not a good way to do.
3. Some anwsers in reddit and stackoverflow said that named route make code more centralize by keep every route in main file.
Surely centralization is good, but why not do it others way?
For example, keep all dart files that contain routes in new folder
Can someone enlighten me why most people use named route? Thanks for any help.
From my point of view, personally I think the root cause of your concern is you keep using pass the data around your screens using constructor or injection.
Reference from Official flutter docs: https://docs.flutter.dev/development/data-and-backend/state-mgmt/declarative
I am an iOS developer with UI kit so I think you has the same problem with me, that I resolved by changing my thinking flow.
Important thing : From the declaratively UI picture: UI = f(state).
For your problem (1):
I think we should not to pass the data around screens, you can use rxdart, provider, mobx, redux... or any state management. It will keep the data of for UI rendering and you no need to check it again in very long switch/if in main file. If you keep passing arguments so you can't build UI from state
For your problem (2):
I think it's the same problem with (1). Instead of using many constructors, you can create your state to reflecting it. So your screen(I mean the Widget reflecting your screen, not their sub widgets) can take data from state and no need to get it from arguments. Again, it about UI = f(state).
For your problem (3):
I definitely agree with you, keep it in another folder is a best choice.
Note: Personally right now I split widgets to 2 types: screen and Presentational widget.
Screen: Never pass arguments to it, it use the data from state(UI = f(state))
Presentational widget: can receive arguments in constructor.
The original idea is from the founder of redux (https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
=> With this idea, It clearly resolved your problem (1) and (2). With (3) by split to another folder and import it should end with some lines in main file like this(without any process arguments on screen changing):
For the benefit:
Personally I think most advantage is:
1 - You can change the screen later without modify working code(open-closed principle): Assuming your client want to change the screen A to screen A', which has alot of navigate in your code.
Example: your app can go to login screen from 3 different logic: from splash, from press logout, from admin banned message.
If you use: MaterialPageRoute(builder: (context) => SecondRoute('Some text')), so you need to replace it many times(3 times in example) inside the code that work perfectly. The most dangerous thing is: maybe the logic "from admin banned message" is # # from other dev
If you usse: Navigator.pushNamed(context, SecondRoute.routeName, arguments: 'Some text'), you change the code in splited folder(that you mention in (3)) by redirect it to another screen(the new login scrren)
Things below I think it less important:
2- (just a note) If you build your project with web version, you will the route name in the url address, so it turn your page to get url.
3- Prevents alot of unnecessary import(example you can see in login, if you use routes, just import 1 file. but you use navigator directly you need to import many screens related: register, forgotpass....). Anyway, it can resolve by create something like screen_index.dart.
I am working on a toy application, which is an online shop application. I use Provider for state management. As it progresses, I notice patterns which are not easy to implement. I wanted to understand what is your take on it. I will explain the case.
There is a productsScreen which shows a list of products, it gets its list of products from a ProductsProvider. Right now it just fetches all the products from the server.
There is another widget which is MenuBarWidget. This widget hosts a few stuff including the search box. Like the ProductScreen it has a provider called MenuBarProvider. Now when user types in a search term in the search box int the MenuBarWidget, it updates the MenuBarProvider; then somehow the ProductsProvider has to see that change and adjust list of products accordingly. How do you do that? How two providers communicate ?
I know that it can be handled in the widgets, but that seems very ugly. Ideally I want to listen to MenuBarProvider from ProductsProvider; but I am not sure how to do that, and I am not even sure if that is such a great idea.
Any idea is welcome.
to make 2 providers "communicate" with each other you should use the ProxyProvider.
It updates a provider when another provider updates.
ProxyProvider is part of the Provider package and it is meant exactly for this purpose, to provide dependency to Providers; see here for the docs:
https://pub.dev/packages/provider#proxyprovider
Check this article as well to see a full example:
https://dev.to/paulhalliday/how-to-use-proxyprovider-with-flutter-3ifo
You can use like this:
MultiProvider(
providers: [
Provider(
create: (_) => UserService(),
),
ProxyProvider<UserService, GreetingService>(
update: (BuildContext context, UserService userService,
GreetingService greetingService) =>
GreetingService(userService: userService),
),
],
)
When UserService provider is updated, GreetingService will be updated as well so they will always be in sync.
I have a simple app with which I am using the Provider package. Within the class using Provider, I fetch some data online and build a collection. I then use it on a couple of other screens. I set up the MultiProvider in main.dart.
return MultiProvider(
providers: [
ChangeNotifierProvider<UserStationInfoProvider>.value(
value: UserStationInfoProvider(),
),
ChangeNotifierProvider<UserStationList>.value(
value: UserStationList(),
),
],
child: MaterialApp(
Within UserStationList is list of the items I am getting online and an integer that indicates which one was selected by the user. I use this data in a couple of screens. The problem comes when I have an emulator up and I make a code change and save the code. The code in main.dart is run again.
The providers are set up again and I lose my collection of items and my integer that holds the item that was selected. If I don't fetch my data again, then I don't have any data and regardless of whether I fetch the data or not, I lose the value in the integer.
Is there any way around this? I'm starting to think that maybe I'm using Provider in a way that it shouldn't be used.
This has been working well. Once I get into my
That happens because you are creating your state directly inside the build method.
It is anti-pattern, and will make you loose your state when the widget rebuilds (which happens on hot reload but not only that).
provider also explicitly states that we should not use the .value constructor to create a value, and instead use the default constructor.
So you can change your:
ChangeNotifierProvider.value(
value: MyModel()
)
into:
ChangeNotifierProvider(
builder: (_) => MyModel()
)
I use Provider for state management and for separate business logic from UI.
I have put some Provider above MaterialApp so can access everywhere in app (for example user info).
But You don’t want to place ChangeNotifierProvider higher than necessary (because you don’t want to pollute the scope).
So I try put some Provider which are only use on some pages lower in widget tree.
For example in my purchase item flow I can have: SelectItemPage => ConfirmItemPage => CheckOutPage.
But my issue is Navigator.push() create separate widget tree every time. So if I initialise a Provider in SelectItemPage this cannot be accessed in ConfirmItemPage or CheckOutPage.
I want Provider be scope to this flow, but I cannot see how.
How I can solve this?
Or my approach is wrong?
Edit: I know I can use prop drilling for pass data from SelectItemPage => ConfirmItemPage => CheckOutPage but this hard to maintain. I want use Provider instead of prop drilling.
I think it's no other way, you mush initialize the provider on each class
try use like this on you app
on you main app
MultiProvider(
providers: ChangeNotifierProvider<yourstate>(
builder: (context) => yourstate,
),
child: MaterialApp...
on each page, you can initialize using below code
final state = Provider.of<yourstate>(context);
and use like this if you don't want to listen any change of the state
final state = Provider.of<yourstate>(context, listen: false);