Overriding builder method of BeamLocation - flutter

I am currently trying to build a flutter app using Beamer and Provider. The app should have a BottomNavbar and remember the state of each tab when switching. Currently I am doing that by using nested Beamers with an IndexedStack.
Now I want to have a BeamLocation with multiple pages, that all share a common state. To do that I want to wrap the entire BeamLocation with a ChangeNotifierProvider. However when doing that, the app tries to build a Location of the wrapped BeamLocation after it already is on another BeamLocation. I am pretty sure the issue is with the builder method of BeamLocation:
When overriding it like this:
#override
Widget builder(BuildContext context, Widget navigator) => navigator
It works fine, which is expected since it doesn't really do anything.
But if I change it to this:
#override
Widget builder(BuildContext context, Widget navigator) {
return Container(
child: navigator,
);
}
It tries to build a page of that location, even when a different location from another BeamLocation is already active.
For a container this isn't a problem, but since I am trying to wrap the BeamLocation with a Provider as stated in the documentation, it is. When doing that, the extra build happens after the Provider is already gone, causing an error.

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.

Flutter show SnackBar without context

I have a flutter app that makes many different http requests in different screens of the app. Now, I want to implement some general error handling, for example, whenever there is no connectivity, I want to show a SnackBar, no matter where I currently am in the app. I managed to write a method that gets called everytime there is an exception in one of my requests. Now, I want to show the SnackBar from within this method. But I don't have access to the BuildContext in this method, and I can't pass it as a parameter to the method either.
So, is there a way to show a SnackBar without having the context? For example, a method to get the context of the currently active screen, and then using it to show the SnackBar?
Suggesting to use a package as pointed by Kaival is not the right way to go, since you won't learn the right solution for your problem other than relying on 3rd party packages and also over-engineering your app with those when sometimes not needed.
Tu use a SnackBar without a context it's actually very simple through a key. You can create a GlobalKey<ScaffoldState> wherever you want and assign it to the Scaffold where you want to display the SnackBar.
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
Widget build(BuildContext context) {
return Scaffold(key: _scaffoldKey);
}
and then anywhere you want, you can check its state to launch a SnackBar as such:
void foo() {
if(_scaffoldKey.currentState != null) {
_scaffoldKey.currentState.showSnackBar();
}
}
You just need to make sure the state is mounted (this is, the Scaffold you want is available, before displaying it.
Just make sure that you are using the new ScaffoldMessenger API you may want to consider adding it to your root MaterialApp a ScaffoldMessengerState so you can use it everywhere.
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
MaterialApp(
scaffoldMessengerKey: rootScaffoldMessengerKey,
home: ...
)
and call it accordingly
rootScaffoldMessengerKey.currentState.showSnackBar();
Refer to the docs for more info.
Hi you could use a globalkey as a navigation key associated with your MaterialApp. That would allow you to also navigate eventually to an error scree or anywhere else.
With the globalkey, you will be able to retrieve the context everywhere (at least when your app is in foreground and there actually is a context).
Although it is not always really reccomended to have a global variable, you can do Something like this:
In your main create a GlobalVariable class containing the key:
class GlobalVariable {
/// This global key is used in material app for navigation through firebase notifications.
static final GlobalKey<NavigatorState> navigatorState = GlobalKey<NavigatorState>();
}
Than declare the key as navigation key in your MaterialApp:
MaterialApp(
navigatorKey: GlobalVariable.navigatorState,
Finally access the key and context everywhere:
GlobalVariable.navigatorState.currentContext

How to refresh Dialog when the data has change? Flutter

I'm a new Flutter developer and I have a problem, which I haven't been able to solve yet, even if I tried a lot.
I'm developing an App, where at some point a Dialog is opened(with showDialog()). In this Dialog there are 2 AutoCompleteTextField:
In the first one, the data will always be the same. So there is a list and the user needs to choose one of the choices. First AutoCompleteTextField code
In the second one, the data to be shown depends on the previous choice. In other words,
whenever an item from the previous list is chosen, the subdata of the item will be requested. Second AutoCompleteTextField code
The required data is fetched properly, however the dialog is not refreshing state so the suggestions of the second AutoCompleteTextField appears empty. When I close and enter again the suggestions of the second appears as they should.
To get notified when the data changes I use ChangeNotifier and Provider, but doesn´t refresh the Dialog (ChangeNotifier class). I also use StatefulBuilder to be able to use setState, but again, doesn´t refresh (Dialog code).
I would like to know if there is any other way to refresh the Dialog. Thank you!
PD: This is my first question in StackOverflow. I tried my best.
I think you need to use Provider.of(context) to be able to listen to the updates and refresh or add/update the data.
Provider.of(context)
I would create a widget class instead of the _widgetTask function and then use Provider.of(context) inside the widget build function like following (I think you can also try to add it inside the _widgetTask function but I dont know if this would work).
class WidgetTask extends StatelessWidget {
//WidgetTask contructor here..
#override
Widget build(BuildContext context) {
final odooData = Provider.of<OdooData>(context);
And then use the odooData to access the data/functions you need to update/listen to from the provider.
Also you can wrap the widget with GestureDetector so that you can update the widget upon tapping using the OnTap property.
flutterdartdialogrefreshproviderconsumer

How do I use Flutters Navigator with a BLoC setup?

I am writing an app that is making use of Cubits for state management. It is an audiobook player and you can look up the different audiobooks and display them. Then jump to the author or the series and explore around your library.
As it stands at the moment the different pages you move between are being rendered using a BLoC builder as follows.
class ScreenSelector extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ScreenCubit, ScreenState>(builder: (context, state) {
if (state is HomeScreenState) {
return HomeScreen();
}
if (state is BookScreenState) {
return BookScreen(bookId: state.bookId);
}
return null;
});
}
}
There are genre, author and series screens as well that will work the same as the book screen. There is a cubit that is emitting the appropriate state hooked up to different clickable widgets on the home and book page. This all works as expected. There is also an authorization flow that is worked to either render the HomeScreen or go to the Loginscreen. This also works nicely.
But i'd like to use a navigator instead so you can go backwards to previous pages. That seems like the right way to navigate but i'm lost on getting Navigator to work with BLoC.
I tried constructing the MaterialPageRoutes and pushing them when the state changes instead of loading the page and I tried converting the BlocBuilder to a BlocListener. Trying to push the page in the BlocBuilder did nothing and the BlocListener would only ever throw an exception.
If anybody has a pointer to how I might go about doing this or knows of a simple example of using Cubits with Navigator 1.0 in flutter I would be most grateful. I looked around but all of the examples I found either mixed in an authorization example and I got lost.
I expect i'm approaching the problem incorrectly but i'm not sure where I have gone off track.
Turns out I just have really crappy GoogleFu and you can find the solution to the problem at
https://bloclibrary.dev/#/recipesflutternavigation
It has a nice recipe that explains it well by the author of Bloc

Flutter detect when Navigator is ready

Working on a Flutter app, I am handling deeplinks with a SDK for which I am given a listener that I must handle as soon as the app starts, therefore in the main().
Once a deeplink is received, I must navigate to the proper screen, based on the parameters passed along the deeplink data.
Since I receive the deeplink in the main function, I am detached from the Context of the app, therefore to access the NavigatorState I used a navigation singleton with a GlobalKey passed to my CupertinoApp's navigatorKey. I later use this key to retrieve the NavigatorState and call push. (instead of Navigator.of(context)....
However, if opening the app from a deeplink, it is very likely that the navigatorKey does not contain anything (yet).
How can I detect/wait until the Navigator is ready ?
As of right now my approach is to add a WidgetsBinding.instance.addPostFrameCallback in my App's initState, that resolves a promise to indicate when the GlobalKey pointing to the NavigatorState can be used... I'm sure there is a beter way to achieve this.
For this i make the initialRoute as a Loading screen and execute all my logic inside it , that way i can have different initialRoutes and i don't have to worry about not having MediaQuery, or Navigator, or even ThemeData.
However the question specifies a way to know if the navigator is available, for this i would use the builder function of MaterialApp/WidgetsApp/CupertinoApp, it's used to override the navigator altought i use it to add pading and a global background color and even override the navigator widget with a custom one,
CupertinoApp(
...
builder: (context, child){
//The navigator is ready
Future.delayed(Duration.zero,
() => print(navigatorKey.currentContext)); //We use a delayed since the child needs to be returned first. It's kinda like a hack 7u7
return child;
}