InheritedWidget update cause problems - flutter

I use the inherited widget at the top of my app to update my application whenever a new location is setted. This results in a refresh of my location coordinates at every possible place in my app.
However I also have a textfield. When i tap on the textfield some seconds later
the keyboard will be hidden by the update of the inherited widget.
Is there a way to prevent flutter to hide the keyboard or to reinitialize the state so that the update of the inherited widgets works together with my search field?
It should be also possible that when i enter some text that this trigger the inherited widget for a new update but the search bar and keyboard should stay open.
new TextField(
controller: _controller,
autocorrect: false,
autofocus: true
...
)

Without seeing all of your code, it's a little bit difficult to know what's going on.
My best guess though is that you're using a context somewhere above the TextField to register for updates from the inherited widget.
What I'd advise you to do though is go up the widget tree from your TextField and make sure that you're not using the inherited widget in a context anywhere above it. It might be worth putting some debug statements at the beginning of your build methods to determine exactly where the build is being triggered from (the inherited widget shouldn't trigger a rebuild immediately below it if you're using it right, but stranger things have happened).
This includes if you have something like this:
MyWidget extends StatelessWidget {
Widget build(BuildContext context) {
Location location = MyInheritedWidget.of(context).location;
return new Row(
children: [
new LocationDisplayer(location),
new TextField( ..... ),
],
);
}
}
The reason I say this is that from my understanding of how inherited widgets work; when it changes, whichever widgets whose contexts were used to get the inherited widget get rebuilt.
That means that even though your textfield technically isn't changing any properties, it might actually be a new textfield depending on how the build went (there's a bit of flutter magic I don't fully understand around how they decide when to make new vs re-build things) and therefore it hasn't requested focus.
Rather than having to write a bunch of new widgets to enclose whatever is using MyInheritedWidget, I believe you could use something like this:
MyWidget extends StatelessWidget {
Widget build(BuildContext context) {
return new Row(
children: [
new Builder(
builder: (context) {
Location location = MyInheritedWidget.of(context).location;
return new LocationDisplayer(location);
},
),
new TextField( ..... ),
],
);
}
}
If you're 100% sure you're not doing this anywhere, then you could go for the non-flutter way of doing things... your location class could expose a subscribe and unsubscribe method. All of the widgets where you're actually using the location call subscribe (which should also be stateful widgets) with a callback in their initState() and unsubscribe in their dispose(). The location class would simply call each of the callbacks instead of changing state as you're doing now, and within each of the callbacks you should call setState for the widget that actually shows the location.

Related

Overriding builder method of BeamLocation

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.

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

flutter StreamBuilder and AnimatedWidget

I am struggling with the following problem:
I built a list of widgets using StreamBuilder (and made it searchable). The widgets are Cards and inside them the user can make his selection and then push a button.
Everything is (was) working just fine.
Then I wanted to add a little animation and make the Icon associated to the button an animated one.
Now it is a mess, the StreamBuilder is in an infinite loop and I have also some problem on my list. If I comment out the animated icon and put in again the previous Icon ...everything starts to work fine again.
...
child: FloatingActionButton(
onPressed: (){
addFood();
mealListState.getAcontroller(id).forward();
},
child:
// MyAddIcon(id), //--> my animated Icon. It does not work
Icon(Icons.add), //--> it works
....
I read that the problem is that adding States management (Unnecessary Widget Rebuilds While Using Selector (Provider) inside StreamBuilder) inside the stream mess things up and that you have to make the widget building the stream Stateful and set up the stream in the initState.
I tried to follow this way but I need the context to build my card list, so I could follow the above hint just to read the data from db (firestore) and ...it is not enough
Could someone point me in the right direction or I have to leave the idea and move on?
Thanks
If you need the context, you can try to listen to the the stream in the didChangeDependencies method of your State class.
Use the Streamsubscription returned by stream.listen to cancel your subscription like so to avoid subsribing twice or even more times:
#override
didChangeDependencies() {
final stream = Provider.of<MyStream>(context); // context available here.
if (this.streamsubscription != null) {
this.streamsubscription.cancel();
}
this.streamsubscription = stream.listen((value) {
// your callback code here
});
super.didChangeDependencies(); // important
}

Notification to adjacent widget

I have a widget that contains a "save" button. After pressing that button several other widgets (not only ancestor ones) must save its state to the file. I don't know how to inform them. What is the best way to achieve that in flutter? I was thinking about using Notification in the "save" widget, closest shared ancestor would contain a NotificationListener that triggers an event to which every widget will subscribe. For me, it doesn't look like a solution.
Provider is the recommended way to do State Management for apps of all sizes. -–Chris Sells – Product Manager, Flutter. June 19, 2019
It's pretty complicated at first, best to check out Simple app state management
The ChangeNotifier uses the notifyListeners function whenever there is any change in the class:
class ChangeNotifierModel extends ChangeNotifier {
String stringThatChanges = 'startValue';
void changeTheString(String value) {
stringThatChanges = value;
notifyListeners();
}
}
The only code that is specific to ChangeNotifier is the call to notifyListeners(). Call this method any time the model changes in a way that might change your app’s UI.
I'm pretty new to Flutter myself, but the way I understand it so, it kinda acts like a wrapper, e.g. wrapping the child of main.dart
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => SingleNotifier())
],
child: MyApp(),
));
don't forget the dependencies
dependencies:
provider: ^4.3.2+2

Flutter Bloc Listener rebuilding even though there is no state change

I have an App which uses a bottom navigation bar to switch between pages. To do this, I'm using Bloc.
On one of the tab pages, I render a list with some items in it. This list is built when the State changes to SchedulesLoaded(see picture).
The problem I have is that when I'm changing pages using the bottom navigation bar(Which is using a completely different Bloc) the list in the picture is being rebuilt. The Listener is actually redrawing the widget without a change in the Bloc State it's listening to. I can't get my head around why it is happening. Does anyone have a clue?
I found this link discussing the issue, but I got nothing useful out of it.
For anyone looking at this now, you can call buildWhen after building Builder to provide some condition when to build.
From docs:
An optional [buildWhen] can be implemented for more granular control over how often [BlocBuilder] rebuilds. [buildWhen] will be invoked on each [cubit] state change. [buildWhen] takes the previous state and current state and must return a [bool] which determines whether or not the [builder] function will be invoked.
The previous state will be initialized to the state of the [cubit] when the [BlocBuilder] is initialized.
[buildWhen] is optional and if omitted, it will default to true.
Example:
return Scaffold(
appBar: AppBar(
title: BlocBuilder<Bloc, State>(
buildWhen: (p, c) => p.isEditing != c.isEditing,
builder: (context, state) {
return Text(state.isEditing ? 'Edit' : 'Create');
},
),