How to provide a BLoC (with flutter_bloc) to `showSearch` - flutter

I am using the package flutter_bloc for state management. I want to create a search screen, and found the showSearch Flutter function, and have been having issues providing a BLoC instance to the ListView my SearchDelegate implementation creates. I finally made it work, but would like to ask what the best way of doing this is. Here is the code (excerpts, starting from a button that is placed in an AppBar within a Scaffold):
class ItemSearchButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.search),
onPressed: () {
final itemListBloc = context.bloc<ItemListBloc>();
showSearch(
context: context,
delegate: _ItemSearchDelegate(itemListBloc),
);
},
);
}
}
class _ItemSearchDelegate extends SearchDelegate<String> {
final ItemListBloc itemListBloc;
_ItemSearchDelegate(this.itemListBloc);
// other overridden methods
#override
Widget buildSuggestions(BuildContext context) {
return BlocProvider.value(
value: itemListBloc,
child: ItemListWidget(),
);
}
}
Basically, the context that invokes the showSearch method has the correct BLoC instance, but it is not available within my SearchDelegate implementation, unless I re-provide it again explicitly in buildSuggestions.
Why is the BLoC not available by default? The showSearch function internally pushes a new Navigator Route, is this the issue?
What is the canonical way of dealing with things like this?

Yes, when the route changes, buildContextchanges too. So you have to provide that bloc to the new context. Just wrap your page where you want to navigate with BlocProvider:
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) =>
BlocProvider(create: Mybloc(),child:MyPage());

In the end it works as intended - the pushed route has a new context that is not a child of a context that has my BLoC, it is a child of the Navigator. The solution is to either do what I did initially - pass the BLoC explicitly as constructor argument - or make sure the Navigator context has the BLoCs, which is what I eventually did; to do this, make sure the Navigator is a child of the (Multi)BlocProvider.

Related

How ChangeNotifier notify the state?

Can someone give me idea how provider notify the state?
I don't want to use ChangeNotifierProvider, Can you give me a suggestion without library?
I just need better explanation with example.
How provider combine InheritedWidget.
What do you think about the following example (inspired by an answer here) with an AnimatedBuilder:
import 'package:flutter/material.dart';
class MyChangeNotifier extends ChangeNotifier {
int count = 0;
void addOne() {
count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
final MyChangeNotifier myChangeNotifier = MyChangeNotifier();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ExampleButton(myChangeNotifier),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: myChangeNotifier.addOne,
),
),
);
}
}
class ExampleButton extends StatelessWidget {
final MyChangeNotifier myChangeNotifier;
const ExampleButton(this.myChangeNotifier, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: myChangeNotifier,
builder: (context, child) {
return OutlinedButton(
onPressed: myChangeNotifier.addOne,
child: Text(
'Tap me - or the floating button\n\n${myChangeNotifier.count}',
textAlign: TextAlign.center,
));
});
}
}
void main() => runApp(MyApp());
The ChangeNotifier implements the Listenable class. You can see here how to listen to that Listenable, for example with an AnimatedBuilder (what my code does).
A ChangeNotifyProvider (I know, you don't want that) would also implement that for you and notify your widgets lower in the widget tree about changes.
Here is some idea for you :
widgets listen to changes and notify each other if there is a rebuild. As soon as the state changes, that particular widget rebuilds without affecting other widgets in the tree.
Three major components make all of this possible: the ChangeNotifier class in Flutter, the ChangeNotifierProvider (primarily used in our sample app), and the Consumer widgets.
Whatever change in the state observed from the ChangeNotifier class causes the listening widget to rebuild. The Provider package offers different types of providers – listed below are some of them:
The Provider class takes a value and exposes it, regardless of the value type
ListenableProvider is the specific provider used for listenable objects. It will listen, then ask widgets depending on it and affected by the state change to rebuild any time the listener is called
ChangeNotifierProvider is similar to ListenableProvider but for ChangeNotifier objects, and calls ChangeNotifier.dispose automatically when needed
ValueListenableProvider listens to a ValueListenable and exposes the value
StreamProvider listens to a stream, exposes the latest value emitted,
and asks widgets dependent on the stream to rebuild FutureProvider
takes a Future class and updates the widgets depending on it when the
future is completed
As a suggestion to learn provider from this article-
https://medium.com/flutter-community/making-sense-all-of-those-flutter-providers-e842e18f45dd

access bloc from Navigator.pushNamed in flutter

I have a routing like this:
routes: {
'/': (context) => MainPage(),
'/othe_page': (context) => OthePage(),
},
Navigator.pushNamed(context, '/othe_page');
How can I pass bloc to OthePage widget?
1) passing bloc as a argument
You have to pass your bloc as argument in navigator as below.
Navigator.pushNamed(context, '/deletewidget',
arguments: bloc);
Now you have to accept that bloc in new page as below.
class _DeleteWidgetState extends State<DeleteWidget> {
var bloc;
#override
Widget build(BuildContext context) {
bloc = ModalRoute.of(context).settings.arguments;
Note: you have to create bloc variable out of build method and assign bloc variable in build method.
-when you return to last screen then you will not able to access bloc anymore.
2) you can use bloc provider in parent widget so that you can access your bloc in new screen also.
for more detail check out Flutter_bloc package.

ChangeNotifierProvider vs ChangeNotifierProvider.value

I am quite new to this framework and working on state management using provider package where I come across ChangeNotifierProvider and ChangeNotifierProvider.value, but I am unable to distinguish their use case.
I had used ChangeNotifierProvider in place of ChangeNotifierProvider.value, but it doesn't work as intended.
Let's take this in steps.
What is ChangeNotifier?
A class that extends ChangeNotifier can call notifyListeners() any time data in that class has been updated and you want to let a listener know about that update. This is often done in a view model to notify the UI to rebuild the layout based on the new data.
Here is an example:
class MyChangeNotifier extends ChangeNotifier {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners();
}
}
I wrote more about this in A beginner’s guide to architecting a Flutter app.
What is ChangeNotifierProvider?
ChangeNotifierProvider is one of many types of providers in the Provider package. If you already have a ChangeNotifier class (like the one above), then you can use ChangeNotifierProvider to provide it to the place you need it in the UI layout.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyChangeNotifier>( // define it
create: (context) => MyChangeNotifier(), // create it
child: MaterialApp(
...
child: Consumer<MyChangeNotifier>( // get it
builder: (context, myChangeNotifier, child) {
...
myChangeNotifier.increment(); // use it
Note in particular that a new instance of the MyChangeNotifier class was created in this line:
create: (context) => MyChangeNotifier(),
This is done one time when the widget is first built, and not on subsequent rebuilds.
What is ChangeNotifierProvider.value for then?
Use ChangeNotifierProvider.value if you have already created an instance of the ChangeNotifier class. This type of situation might happen if you had initialized your ChangeNotifier class in the initState() method of your StatefulWidget's State class.
In that case, you wouldn't want to create a whole new instance of your ChangeNotifier because you would be wasting any initialization work that you had already done. Using the ChangeNotifierProvider.value constructor allows you to provide your pre-created ChangeNotifier value.
class _MyWidgeState extends State<MyWidge> {
MyChangeNotifier myChangeNotifier;
#override
void initState() {
myChangeNotifier = MyChangeNotifier();
myChangeNotifier.doSomeInitializationWork();
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyChangeNotifier>.value(
value: myChangeNotifier, // <-- important part
child: ...
Take special note that there isn't a create parameter here, but a value parameter. That's where you pass in your ChangeNotifier class instance. Again, don't try to create a new instance there.
You can also find the usage of ChangeNotifierProvider and ChangeNotifierProvider.value described in the official docs: https://pub.dev/packages/provider#exposing-a-value
Does the official documentation help?
DO use ChangeNotifierProvider.value to provider an existing ChangeNotifier:
ChangeNotifierProvider.value(
value: variable,
child: ...
)
DON'T reuse an existing ChangeNotifier using the default constructor.
ChangeNotifierProvider(
builder: (_) => variable,
child: ...
)
Also check out this Github issue from the author about this.
ValueNotifier and ChangeNotifier are closely related.
In fact, ValueNotifier is a subclass of ChangeNotifier that implements
ValueListenable.
This is the implementation of ValueNotifier in the Flutter SDK:
/// A [ChangeNotifier] that holds a single value.
///
/// When [value] is replaced with something that is not equal to the old
/// value as evaluated by the equality operator ==, this class notifies its
/// listeners.
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
/// Creates a [ChangeNotifier] that wraps this value.
ValueNotifier(this._value);
/// The current value stored in this notifier.
///
/// When the value is replaced with something that is not equal to the old
/// value as evaluated by the equality operator ==, this class notifies its
/// listeners.
#override
T get value => _value;
T _value;
set value(T newValue) {
if (_value == newValue)
return;
_value = newValue;
notifyListeners();
}
#override
String toString() => '${describeIdentity(this)}($value)';
}
So, when should we use ValueNotifier vs ChangeNotifier?
Use ValueNotifier if you need widgets to rebuild when a simple value
changes. Use ChangeNotifier if you want more control on when
notifyListeners() is called.
Is an important difference between ChangeNotifierProvider.value and with the create function. When you're using Provider in a single list or grid item, Flatter removes items when they leave the screen and re adds them when they reentered the screen in such situations what actually happens is that the widget itself is reused by Flutter and just the data that's attached to it changes. So Flatter recycles the same widget it doesn't destroy it
and recreate it. when we are using Provider with the create function.
ChangeNotifierProvider(
create: (_) => new MyChangeNotifier(),
child: ...
)
☝☝☝ here which is content changes over time and our provider won't pick us up.
In a single list or grid item, we should use Provider dot value.
ChangeNotifierProvider.value(
value: new MyChangeNotifier(),
child: ...
)
Basically the ChangeNotifierProvider with builder(Provider v3) or create(Provider v4) parameter is a disposing provider, this provider owns the state source and manages its lifetime. The value provider only references the state source but does not manage its lifetime.
In disposing providers, the builder or create parameter provides a function for creating the state source. In value providers there is a value parameter which takes a reference to the state source and you are responsible for creating and disposing the state source as needed.

Flutter showDialog with navigator key rather than passing context

Currently its very hectic to show dialog from any layer of code in app just because one has to pass context in it. Hence i thought to pass navigatorKey.currentContext (Navigator key is a global key passed to Material app navigatorKey parameter) to show dialog. But i got the error
"Navigator operation requested with a context that does not include a Navigator.The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget."
The issue is showDialog calls Navigator.of(context) internally and which looks for the navigator ancestor which ofcourse will return null as the navigator is itself the root. Hence it will not find the navigator as ancestor.
Is there a way we can directly pass the navigator state/context to showDialog function to show the dialog? Or is there a more easy way to show Dialog without passing context to it if we want to show it from bloc?
I found a simple solution:
navigatorKey.currentState.overlay.context
I use this in a redux middleware where I keep navigatorKey, and want to show a dialog globally anywhere in the app everytime I dispatch a specific action.
Since this one is merged:
https://github.com/flutter/flutter/pull/58259
You can use:
navigatorKey.currentContext;
You can make use of InheritedWidget here. Make a InheritedWidget the root for your application which holds a navigator key. Then you can pass any context of child widgets to get the current navigator state.
Example:
InheritedWidget:
// Your InheritedWidget
class NavigatorStateFromKeyOrContext extends InheritedWidget {
const NavigatorStateFromKeyOrContext({
Key key,
#required this.navigatorKey,
#required Widget child,
}) : super(key: key, child: child);
final GlobalKey<NavigatorState> navigatorKey;
static GlobalKey<NavigatorState> getKey(BuildContext context) {
final NavigatorStateFromKeyOrContext provider =
context.inheritFromWidgetOfExactType(NavigatorStateFromKeyOrContext);
return provider.navigatorKey;
}
static NavigatorState of(BuildContext context) {
NavigatorState state;
try {
state = Navigator.of(context);
} catch (e) {
// Assertion error thrown in debug mode, in release mode no errors are thrown
print(e);
}
if (state != null) {
// state can be null when context does not include a Navigator in release mode
return state;
}
final NavigatorStateFromKeyOrContext provider =
context.inheritFromWidgetOfExactType(NavigatorStateFromKeyOrContext);
return provider.navigatorKey?.currentState;
}
#override
bool updateShouldNotify(NavigatorStateFromKeyOrContext oldWidget) {
return navigatorKey != oldWidget.navigatorKey;
}
}
HomeScreen:
// Your home screen
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: NavigatorStateFromKeyOrContext.getKey(context),
home: InitPage(),
);
}
}
The root of the application will look like,
final GlobalKey navigator = GlobalKey<NavigatorState>(debugLabel: 'AppNavigator');
runApp(
NavigatorStateFromKeyOrContext(
navigatorKey: navigator,
child: HomePage(),
),
);
Now from anywhere in the app, pass any context to get the NavigatorState like
NavigatorStateFromKeyOrContext.of(context)
Note: This is one approach I came up with where I used InheritedWidget, there are many other ways to achieve the same, like using Singleton, having a global bloc to provide navigator key, storing the navigator key in a Redux store or any other global state management solutions, etc.
Hope this helps!
Currently, I am showing a dialog by creating a function in my util class which takes the context as a parameter.
static void showAlertDialog(String title, String message, BuildContext context) {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text(title),
content: new Text(message),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Using the above function as:
UtilClass. showAlertDialog("Title", "Message", context);

Could not find a generator for route

I´m newbie to flutter and reveice one exception about route and paginator in Flutter.
EXCEPTION CAUGHT BY GESTURE
The following assertion was thrown while handling a gesture:
Could not find a generator for route "/listadecompras" in the _MaterialAppState.
Follow a excerpt from code:
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
// ...
return new ListTile(
onTap: () {
Navigator.pushNamed(context, "/listadecompras");
},
// ...
}
class ListaDeCompras extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
// ...
}
}
void main() {
runApp(new MaterialApp(
home: new MyApp(),
routes: <String, WidgetBuilder>{
"/listadecompras": (BuildContext context) => new ListaDeCompras()
}
));
}
Please, someone could send some advice?
thanks in advance for your attention
Because Of instantiated two MaterialApp widgets. You need to remove the one in MyApp class and may change it to Scaffold
Example:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
// ...
return new ListTile(
onTap: () {
Navigator.pushNamed(context, "/listadecompras");
},
// ...
}
THE PROBLEM IS YOUR CODE IS - the route is trying to resolve for the nearest MaterialApp which has no route definition. That said you should use only one MaterialApp as the root of your widget tree.
try this :
onPressed: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new ListaDeCompras(),
),
);
},
If a new file is created and you are defining a route for that new file, its better to re-run the app rather than hot reloading.It worked for me!
flutter clean and run again, is working for me.
I had a similar issue because I defined the route as '/listadecompras' and then called Navigator.pushNamed(context, "listadecompras"); without the forward slash.
It isn't probably the same scenario than this question but it shows the same error.
It happened to me, but in my case there was a single MaterialApp widget instantiated, so I wasn't able to find the cause.
I noticed that in my App, all calls to Navigator where done the same, with:
Navigator.of(context).pushNamed('/route-name');
All, except the first one, which was like this:
Navigator.pushNamed(context, '/route-name');
After changing the above line to be equal to the others (using .of(context)), the problem got solved!
Note
Until now I had no problems with navigation, it happened once tried to navigate to a new view from a dialog.
If you have only one MaterialApp, and you cannot fix this issue, try to check your navigation chain, and use the same way of calling Navigator in every place.
Bye
#aziza is the right answer
but in some miscellaneous situation
in my case
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
MaterialPageRoute(builder: (_) => MyScaffold());
break;
case '/homechart':
MaterialPageRoute(builder: (_) => analytics());
break;
}
}
}
I forget to return keyword and IDE not showing any error. showing this kind of error.
Solution is like this :
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => MyScaffold());
break;
case '/homechart':
return MaterialPageRoute(builder: (_) => analytics());
break;
}
}
}
You have instantiated two MaterialApp widget. You need to remove the one in MyApp class.
For push
Navigator.of(context).pushNamed('/screen1')
For Pop: "popAndPushNamed"
Navigator.of(context).popAndPushNamed('/screen4');
I have encountered the same error and solved by just restarting the app
In my case the error was because i deleted the route from main.dart and forgot the update the change in other files
I had the same error i don't know why flutter does that after creating a new screen but all you need is rebuild zor re-run the app again
There need to be only one MaterialApp if you have multiple MaterialApps then I would recommend to keep only one MaterialApp for your project.
I had the same problem. the problem was that I had a template and had duplicated the screens and didn't delete my materialapps after everything worked tanks a lot
I recently had the same problem any way I managed to resolve it by using Global Key
the context which I need to navigate from was the problem because it refers to a widget not a page that holds this widget so I made static GlobalKey appKey = GlobalKey();
then use it in the scaffold of the page then i can use its context from any place in the app MainPage.appKey.currentContext;
One of the solutions would be hot restarting your code.
That is because we declare the route name outside the Widget Build.
If you don't have a home property defined for your MaterialApp, you probably should have this kind of error. Please try to fill it with a Scaffold widget.