Flutter: Update specific screen using provider that's consumed in multiple screens - flutter

I have a typical CRUD operation task: (List Sites, Add Site, ...)
I created a SitesProvider. In the same provider, I added 2 methods for add and edit. I was thinking to use the same provider in the 2 screens (ListSites and AddEditSite)
In the ListSites screen, it works fine. Here is the problem:
I open the AddSite screen by clicking the AddButton in the ListSites screen
Hit submit (did not enter data, simulating error case)
The error gets displayed in the ListSites screen, not in the AddSite screen.
It makes sense. They both use the same provider, both screens are on the stack. It seems that the first one only consumes the state update and displays the error.
I use MultipleProviders approach that wraps the MaterialApp with all providers in the app.
Can we fix that without creating separate providers for each of the 2 screens?
EDIT:
I used provider.removeListener in the ListSites screen right before I open the AddEdit and it shows the error in the correct screen now. I still have to do some other tweaks to get it back to listen after I add. Not efficient I think but it is a step.

I ended up doing that:
Added Another Field in the provider (stateType: list/addedit)
Change the type per the screen I'm currently in
provider.stateType = UIStateType.add_edit;
await Navigator.push(context,
MaterialPageRoute(builder: (context) => AddEditSiteScreen()));
provider.stateType = UIStateType.list;
in build(), I check for the type
sitesProvider = Provider.of(context);
if (sitesProvider.stateType != UIStateType.add_edit) return Container();

Related

How can I call setState() safely after returning a value from a dialog in Flutter?

Here is the problem: I have a list of items displayed in a list view. I can edit these items with the dialogs displayed by clicking on them. After editing and closing the dialog, I also need to update the items on the list view. I'm currently doing this with the following code snippet in my list view item widget:
showDialog(
context: context,
builder: (context) {
return UpdateItemDialog(item: _item);
},
).then((updatedItem) {
if (updatedItem != null) {
setState(() => _item = updatedItem);
}
});
and by calling Navigator.of(context).pop(_item); from my dialog.
It works perfectly fine when there are no rebuilds occur until the dialog is dismissed. However, for example, if the orientation is changed when the dialog is open, I get Unhandled Exception: setState() called after dispose() error since the list view also rebuilt because of the orientation change and the then clause in my code runs on the destroyed widget.
Is there a way to access the rebuilt widget from a destroyed widget?
Or should I use a different approach to this problem?
By the way, changes should only be accepted after the dialog is dismissed, so I should return the item from the dialog.
I believe your best bet would be to introduce even a simple state management solution (Provider would be good) to handle communication between a dialog and other widgets.
Check out the Gist below (Run it on Dartpad.dev) as an example how you can keep it simple, yet clean and decoupled
https://gist.github.com/romanejaquez/8aed8d699fba8fdfff4b0966dfe47663
in which I show that instead of passing data from a dialog back to another widget, a State Management solution would allow you for a decoupled way of passing data back and forth, notifying each other and triggering rebuilds (which is kind of calling setState() since pretty much that's what you want - trigger a rebuild on the other widget. In this example, I'm sending a value back to the originating widget and triggering a rebuild (thanks to the Consumer widget listening to the changes triggered in the common service. That'd be my honest suggestion.

Is there a way to create a "safe" Navigator.popUntil function in Flutter?

When you pop the navigator too many times in Flutter you are met with a black screen. To prevent that from ever happening to the user because of some unforeseen event on my part, I have created the following function:
static void safePop<T extends Object>(BuildContext context, [T? result]) {
if (Navigator.canPop(context)) {
Navigator.of(context).pop<T>(result);
}
}
That works great for simple pops, but I've recently migrated my app to using named routes and would like to use the Navigator.popUntil(context, ModalRoute.withName()) to more accurately pop widgets when I'm 4 or 5 pages into the app. I'm having trouble figuring out a way to implement something similar to above with popUntil.
Is there a way to prevent the Navigator from popping to a black screen in case of an incorrect/non-existent route being passed to the popUntil function?
Thanks

How to change a value in a text field and have it show up on another screen (Flutter)?

I'm trying to have the user put information in a TextField on one screen and then save that info when they press "Save". Then I want this text to show up on another screen in another Container (say it asks for their name, they put it in the TextField, press the Button called "Save", it takes them back to the other screen, and the box that previously said "Name" now says "[The name the user entered]").
Thank you in advance for any help!!
Use StateManagement Libraries like flutter_bloc,provider,mobx, getX.
This way you will be able to save your data and use in in any part of your app
There are multiple ways of going about doing this.
You can pass the value through the constructors of the screens you're navigating to (for e.g.
MaterialPageRoute(builder: (context) => NewPage(name: nameValue))
If you want to use this value in multiple places in your application, you can consider storing it in a config class of sorts (i.e., create a class Config as a singleton (only once instance allowed), and store the name there while saving like
Config.name = nameValue. Then, anywhere in your app, you'd access it as Config.name)
If you want this value to be used anywhere else in the app, but also want it to be persistent, then it's probably best to store it as SharedPreferences or in a database. Look into the sharedpreferences package, or sqflite package for Flutter.

Flutter: When might SystemChrome.setEnabledSystemUIOverlays value override subsequent screen overlay values

I have two app 'screens', A and B. Both set the SystemChrome.setEnabledSystemUIOverlays in the build methods.
A sets as per
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
return WillPopScope(
....
whilst B sets as per
Widget build(BuildContext context) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
SystemChrome.setEnabledSystemUIOverlays([]);
}
else {
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
}
return WillPopScope(
.....
Navigating from A to B in portrait works fine. If however I enter landscape in B flutter removes the overlays and then re-implements them immediately, ie I see the overlay disappear and reappear quickly. If I remove the overlay line completely from A, B works fine. Its like B is rebuilt followed by A but in the background. Is that a thing?
I'm navigating like so
Navigator.push(context, MaterialPageRoute(builder: (mContext) => DataForm(file: _file)))
I've been tying to track this bug for three days. For reference overlays are set here an only here in all my code. The issue only occurs on Android.
I appreciate the first response will be 'its in your code we need to see it to solve it'. I tried reproducing the issue in 'minimal code' to post an example but I cannot get the error to reoccur and my code is so extensive I am not sure how to show it all.
As per the title I am instead asking what scenarios might cause screen A overlay setting to influence screen B at each build.
Thanks in advance!
Answering my own question after 5 days of tracking down my bug in the hope it might help some else if future. I suspect those with experience will be thinking 'yes of course and' so this is probably more for someone new like myself.
SystemUIOverlay is like a global variable that persists in the app and is implemented across screens automatically. A new screen/code can change this overlay setting at any time and know if will be applied globally and retrospectively to all screens. In my case SystemUIOverlay is set in build so any action in Screen A that forces a rebuild will therefore enforce the update and apply the global overlay value to B.
Even though Screen A is not showing it can continue to rebuild in the background if triggered, even when Screen B is opened. Therefore anything triggering a rebuild of A whilst B is open means B will adopt A's overlay setting globally. This was the result I was seeing.
A couple of examples:
As referenced here, MediaQuery.of will cause your widget to rebuild automatically whenever the MediaQueryData changes (e.g., if the user rotates their device). So for instance if you include 'MediaQuery.of' anywhere in you build of Screen A, a build can occur even when not in focus. This rebuild of A then enforces it's overlay value globally which is picked up by B whilst it is open.
In my case rebuilding B set the overlay to [] and affected the insets. MediaQuery in A picked up on this, rebuilt and reset the overlay to .values and forced these onto B. And round she goes.
A periodic timer calling setState in A will do the same thing. I am sure there are many more examples.
So look out for and avoid any rebuild potential in A when it has lost focus.

Flutter how to navigate back to a particular page

I have an app with multiple pages where some pages are skip depending on the user selection...
for example, I have the following page
details.dart
contact.dart
address.dart
code.dart
summary.dart
I would like to go to another page from any of the above pages...However, I want first check to see if they are in the stack and then pop to it. Otherwise push that page onto the stack
How do I do this in Flutter
I think you might want to use Navigator.of(context).pushNamedAndRemoveUntil or Navigator.popUntil.
They are very similar however like the name suggests pushNamedAndRemoveUntil pushes a new route when the predicate matches, while popUntil re-uses an existing Route/Widget in the tree.
You can read more about them on this wonderful Medium post explaining all options in greater detail.
You can use routes inside materialApp, after home
return MaterialApp(
home:... ,
routes:{
"/routeName" : (context) => PageRoute()
}
and then call route name from any pages
Ex : app in Contact page need to move to PageRoute
//Contact Page
...
onTap:(){
Navigator.pushReplacementName(context,'/routeName');
}
just use multiple Navigator.pop