Why I should use named routes? - flutter

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.

Related

Creating a flutter list tile with as many tiles as I have elements in a List i'm passing

I am normally a Laravel frontend dev, and I'm trying to learn flutter and dart as an intro to mobile frontend. In laravel blade's html, when you want to create visual elements for each data you are feeding to the front it's exactly this: a for each loop that repeats the element but with different data. I undertsand Dart uses a different paradigm, and I am too stuck in the "laravel cone" and finding it difficult to implement solutions as it should be done in dart.
The way this is working now is: I've got some data I got in a post request, saving that in a Todo class and passing it as parameters to the renderer of the next view:
The Class
class Todo {
String name;
String lastacc;
String created_at;
String farmDoc;
String farmName;
String clientId;
List farms;
Todo(this.name, this.lastacc, this.created_at, this.farmDoc, this.farmName, this.farms, this.clientId);
}
The Function
Todo infoAcc = Todo(name, lastAcc, createdAcc, farmDoc, farmName, farms, id );
Future.delayed(
const Duration(milliseconds: 500),
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Farmlist(),
settings: RouteSettings(arguments: infoAcc)));
},
);
This is working nice, before the function I managed to get the variables correctly set and by debugging I know it's working fine; Now i have this other view, the Farmlist, where it should get de List farms and use it to generate several Tiles in a ListTile, each one with info from a different farm. It should be dynamic, as to show always the amount of tiles equivalent to the amount of elements in the array I pass, as different users will have a different amount of farms.
Any example or documentation i could read, or alternative solution is welcome. I've been struggling with this for quite the time now.
You can use the ListView.builder widget, you simply need to pass the array that you want to build and with the builder function you can create a widget that depends on the given data
For more information:
https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html
https://flutter.dev/docs/cookbook/lists/long-lists

Provider hierarchy in flutter

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.

Flutter Provider best practice

I have been experimenting with the provider package and am usually able to get it to do what I want it to do. However, in some cases I am not sure if what I am doing is at all best practice.
For example, suppose I have a settings page with various, unrelated options - say a theming option, a notifications option, some filter options specific to the app etc.
My question is, should each of these options have their own class dedicated to a single value so that only the parts of the widget tree dependent on that single value rebuild. Or should they all be in the same SettingsProvider class, and there is some way of using the fields in this class separately so as to avoid excessive rebuilds?
Or am I missing the bigger picture entirely? Any help would be great thanks!
A solution I've found is to put all the values in a single class eg SettingsProvider. Then, instead of using Provider.of<> or Consumer<>,
use Selector<>. For example, to get/set the notifications option of settings, you could wrap the widget with a Selector like so -
Selector<SettingsProvider, bool>(
builder: (context, notifications, child) {
(notifications)
? return Text('Notifications are on')
: return Text('Notifications are off')
},
selector: (context , settingsPro) => settingsPro.notifications,
),
This should display whether or not the notifications are on, and is only rebuilt when the notification option changes.
Here is the provider doc page
Here is an article about Selector
Let me know if there are any better solutions.

Flutter Provider package reinitializes when saving in VSCode

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()
)

How to make Provider and Navigator work together?

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