Widget re-render when I focus on a TextField - Bloc Pattern - dart

I'm using a BLoC to keep state between two nested FullScreenDialogs.
I'm initializing the bloc when I push the first screen, like so
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => ProductBlocProvider(child: ProductEntryScreen()),
fullscreenDialog: true
));
},
);
ProductEntryScreen has a bunch of TextFields and a button than opens a new FullScreenDialog. This new Screen also has TextFields.
The problem I'm having is that every time I write on a TextField on the second FullScreenDialog, the onPressed function where I start the ProductBlocProvider runs again.
And that re-run is causing the Bloc to create a new instance, so I end up loosing the state.
What I want to do?
Maybe I'm doing it wrong so I'll explain what I'm trying to achieve.
I want to keep state between the two FullScreenDialogs while I fill all the fields, and when I'm done I want to press a button that send all of the data (both screens) to a database.

The problem is that I was creating the instance of the bloc inside the provider in the builder function of the MaterialPageRoute.
That builder function was being called repeatedly, and creating a new instance of the bloc every time. The solution was to take out from the builde function the creation of the bloc instance, like this:
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
//Here I create the instance
var _bloc = ProductBloc();
Navigator.of(context).push(MaterialPageRoute(
//And I pass the bloc instance to the provider
builder: (BuildContext context) => ProductBlocProvider(bloc: _bloc, child: ProductEntryScreen()),
fullscreenDialog: true
));
},
);

The package get_it may be of help to you. get_it is a service locator library, and uses a Map to store the registered objects; therefore, it provides access at a complexity of O(1), which means it's incredibly fast. The package comes with a singleton GetIt which you can use like so,
// Create a global variable (traditionally called sl or locator)
final sl = GetIt.instance; // There is also a shorthand GetIt.i
// ...
// Then, maybe in a global function called initDi(),
// you could register your dependencies.
sl.registerLazySingleton(() => ProductBloc());
registerLazySingleton() or registerSingleton() will always
return the same instance; lazily (i.e., when first called)
or at app start-up respectively.
If you want to create a new instance every time, use registerFactory() instead (I put this here even though it's not exactly what you want).
For example,
sl.registerFactory(() => ValidatorCubit());
And it could be accessed like this,
MultiBlocProvider(
providers: [
// The type is inferred here
BlocProvider<AuthenticationBloc>(create: (_) => sl()),
// The type is explicitly given here
BlocProvider(create: (_) => sl<ProductsBloc>()),
],
child: ProductsScreen(),
),
This example primarily shows you how it can be done with the flutter_bloc library, but get_it works anywhere, even in non-flutter dart projects.
If you need more functionality, do make sure to read the docs for this package. It is well documented, and contains (almost) every feature you might need, including scoping.
Also, this approach allows you to use the interface pattern, making the code much more maintainable and testable, as you will have to change just one place to use a different implementation.

Related

Flutter provider, question around Dart syntax

I'm relatively new to Dart/Flutter,
Just struggling to understand some code/syntax and wondered if someone can help explain.
Im looking at the example of setting up multiple providers and I cant get my head round the code for setting up the update..
providers: [
// In this sample app, CatalogModel never changes, so a simple Provider
// is sufficient.
Provider(create: (context) => CatalogModel()),
// CartModel is implemented as a ChangeNotifier, which calls for the use
// of ChangeNotifierProvider. Moreover, CartModel depends
// on CatalogModel, so a ProxyProvider is needed.
ChangeNotifierProxyProvider<CatalogModel, CartModel>(
create: (context) => CartModel(),
update: (context, catalog, cart) {
cart.catalog = catalog;
return cart;
},
),
],
Specifically...
update: (context, catalog, cart) {
cart.catalog = catalog;
return cart;
}
I thought it was a function that takes in 3 parameters context, catelog, cart
But I dont see anywhere where they are first instantiated
Can anyone explain what is going on here?
Thanks
update: denotes a parameter to the ChangeNotifierProxyProvider<CatalogModel, CartModel> constructor, passing it an anonymous function that takes three parameters. The code in (or near) the ChangeNotifierProxyProvider will be invoking this function as necessary.

Flutter Navigator.of(context).pop vs Navigator.pop(context) difference

What's the difference between Navigator.of(context).pop and Navigator.pop(context)?
To me both seems to do the same work, what is the actual difference. Is one deprecated?
Navigator.push(context, route) vs Navigator.of(context).push(route)
Navigator is used to manage the app's stack of pages(routes). When push the given route onto the screen(Navigator), We need to get the right Navigator and then push.
Navigator.of(context).push(route) splits .of(context) to get the right Navigator and .push(route). Navigator.of(context) has optional parameters, if rootNavigator is set to true, the NavigatorState from the furthest is given instead.
static NavigatorState of(
BuildContext context, {
bool rootNavigator = false,
bool nullOk = false,
})
Navigator.push(context, route) is a static method and do both at the same time. It internally calls Navigator.of(context).push(route). The navigator is most tightly encloses the given context.
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
return Navigator.of(context).push(route);
}
pop() is similar to push().
When multiple Navigators are nested in App. The dialog route created by showDialog(...) method is pushed to the root navigator. If the application has multiple Navigator objects, it may be necessary to call Navigator.of(context, rootNavigator: true).pop(result) to close the dialog rather than just Navigator.pop(context, result).
There is no difference between the two, source code confirms this. Calling
Navigator.pop(context)
actually calls
Navigator.of(context).pop()
Source code:
static bool pop(BuildContext context, [ dynamic result ]) {
return Navigator.of(context).pop(result);
}
A little bit (not really a little) late to this but the main difference I notice between these two is that Navigator.pop(context) calls Navigator.of(context).pop() with the current widget's BuildContext.
Basically Navigator.of(context).pop() gets a NavigatorState from the passed context and pops the top-most route off the navigator. So when you use it directly, it pops the top-most route off the parent's Navigator (which is the route you are currently on). When you do Navigator.pop(context), you are doing Navigator.of(<current widget's context>).pop() which usually does the same thing since the current widget is usually on the top-most route.
To see differences between objects, you can try to check their hash code. For example with the code below, you can see if the function is being called with the same instance of BuildContext.
final navigatorState = Navigator.of(context);
print(navigatorState.context.hashCode); // Prints the parent's context's hash code.
print(context.hashCode); // Prints the current widget's context's hash code.
BUT this can become tricky when for example you call showDialog on the current context and your widget rebuilds, while the Dialog is still showing. In this case, If you try to pop the dialog with a Navigator.pop(context) you may encounter an Exception such as:
The following assertion was thrown while handling a gesture:
Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by
calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
In this use case, using Navigator.of(context).pop() would be better.

How to cache rxdart streams in flutter for infinite scroll

class PollScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
bloc.paginatePolls(null, null);
return StreamBuilder(
stream: bloc.polls,
builder: (context, AsyncSnapshot<List<PollModel>> snapshot) {
if (snapshot.data == null || snapshot.data.length < 1) {
return Text('loading...');
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, int index) {
final PollModel curItem = snapshot.data[index];
return Card(
//Render Logic
);
},
);
});
}
}
class Bloc {
final Repository _repository = Repository();
final PublishSubject<List<PollModel>> _polls = PublishSubject<List<PollModel>>();
Observable<List<PollModel>> get polls => _polls.stream;
paginatePolls(int count, String last) async {
final List<PollModel> polls = await _repository.paginatePolls(count, last);
_polls.sink.add(polls);
}
dispose(){
_polls.close();
}
}
final bloc = Bloc();
I am from react native background in terms of mobile dev and I have used tools like apollo and redux so rxdart is a little bit confusing for me. paginatePolls just retrieves simple list of objects from the server and adds to the stream and PollScreen class is rendering the result. Everything works fine but I am curious how I can cache the first paginatePolls requests so that I can make subsequent queries with count (number of docs to return) last (id of the last item returned from previous result) and simply append the result to what is already there.
In apollo and redux I would just be adding more and more docs to the cache as I make more requests but since rxdart is a stream, I am unsure of which approach is the most effective.
I have thought of cacheing with sqlite but seems like a huge over kill + unsure if it would be fast enough.
Next method I have thought of is just to create a list in the bloc and keep adding items to it as more requests are made. Then stream the whole list every time. But this would mean re-creating the entire list for every stream e.g. first request render 1-50, second render 1-100, third render 1-150 where what would be preferable is - first render 1-50, second render 51-100 and just attach below the first render, etc.
How do you implement counterparts of apollo and redux cache in flutter using rxdart?
I've been stuck on this similar problem. I was thinking of keeping a Map in my repository (class where you make your network requests) and just do a look up in the map from there. But I am having issues with this, since I use the "compute" method, and whatever you pass into the "compute" function cannot be a instance method. (https://flutter.dev/docs/cookbook/networking/background-parsing)
I looked online and a few people advise against a global bloc, I am from a React Native + Redux background as well, but I really want to use the bloc pattern the right way, wish there were better examples out there.

What are Keys in the Stateless widgets class?

In the flutter docs there's sample code for a stateless widget subclass as shown:
class GreenFrog extends StatelessWidget {
const GreenFrog({ Key key }) : super(key: key);
#override
Widget build(BuildContext context) {
return new Container(color: const Color(0xFF2DBD3A));
}
}
and this
class Frog extends StatelessWidget {
const Frog({
Key key,
this.color: const Color(0xFF2DBD3A),
this.child,
}) : super(key: key);
final Color color;
final Widget child;
#override
Widget build(BuildContext context) {
return new Container(color: color, child: child);
}
}
What is a key and when should this super constructor be used? It seems like if you have your own constructor you must have {Key key} why? I've seen other examples where the super keyword is not used so this is where my confusion is.
TLDR: All widgets should have a Key key as optional parameter or their constructor.
Key is something used by flutter engine at the step of recognizing which widget in a list as changed.
It is useful when you have a list (Column, Row, whatever) of widgets of the same type that can potentially get removed/inserted.
Let's say you have this (code not working, but you get the idea) :
AnimatedList(
children: [
Card(child: Text("foo")),
Card(child: Text("bar")),
Card(child: Text("42")),
]
)
Potentially, you can remove any of these widgets individually with a swipe.
The thing is, our list has an animation when a child is removed. So let's remove "bar".
AnimatedList(
children: [
Card(child: Text("foo")),
Card(child: Text("42")),
]
)
The problem: Without Key, flutter won't be able to know if the second element of your Row disappeared. Or if it's the last one that disappeared and the second has its child change.
So without Key, you could potentially have a bug where your leave animation will be played on the last element instead!
This is where Key takes place.
If we start our example again, using key we'd have this :
AnimatedList(
children: [
Card(key: ObjectKey("foo"), child: Text("foo")),
Card(key: ObjectKey("bar"), child: Text("bar")),
Card(key: ObjectKey("42"), child: Text("42")),
]
)
notice how the key is not the child index but something unique to the element.
From this point, if we remove "bar" again, we'll have
AnimatedList(
children: [
Card(key: ObjectKey("foo"), child: Text("foo")),
Card(key: ObjectKey("42"), child: Text("42")),
]
)
Thanks to key being present, flutter engine now knows for sure which widget got removed. And now our leave animation will correctly play on "bar" instead of "42".
What are Keys?
Keys are IDs for widgets. All widgets have them, not just StatelessWidgets. They are used by the Element tree to determine if a widget can be reused or if it needs to be rebuilt. When no key is specified (the usual case), then the widget type is used to determine this.
Why use Keys?
Keys are useful for maintaining state when the number or position of widgets changes. If there is no key then the Flutter framework can get confused about which widget changed.
When to use Keys?
Only use them when the framework needs your help to know which widget to update.
Most of the time you don't need to use keys. Since keys are mostly only useful for maintaining state, if you have a stateless widget whose children are all stateless, then there is no need to use a key on it. It won't hurt to use a key in this case, but it also won't help.
There are some micro-optimizations you can make using keys. See this article.
Where to use Keys?
Put the key at the part of the widget tree where the reordering or addition/deletion is taking place. For example, if you are reordering the items of a ListView whose children are ListTile widgets, then add the keys to the ListTile widgets.
What kind of Keys to use?
A key is just an id, but the kind of ID you use can vary.
ValueKey
A ValueKey is a local key that takes a simple value like a string or integer.
ObjectKey
If you widget is displaying more complex data than a single value, then you can use an ObjectKey for that widget.
UniqueKey
This type of key is guaranteed to give you a unique ID every time. If you use it, though, do NOT put it in the build method. Otherwise your widget will never have the same ID and so the Element tree will never find a match to reuse.
GlobalKey
GlobalKeys can be used to maintain state across your app, but use them sparingly because they are similar to global variables. It is often preferable to use a state management solution instead.
Examples of using Keys
AnimatedLists
Changing the position in a column/row
TextFormField
References
Keys! What are they good for?
Using Keys in Flutter
Key is object that is used to identify a widget uniquely.
They are used to access or restore state In a StatefulWidget (Mostly we don't need them at all if our widget tree is all Stateless Widgets).
There are various types of key that I will try to explain on the basis of usage.
Purpose (key types)
1. Mutate the collection i.e. remove / add / reorder item to list in stateful widget like draggable todo list where checked items get removed
➡️ ObjectKey, ValueKey & UniqueKey
2. Move widget from one Parent to another preserving it's state.
➡️ GlobalKey
3. Display same Widget in multiple screens and holding its state.
➡️ GlobalKey
4. Validate Form.
➡️ GlobalKey
5. You want to give a key without using any data.
➡️ UniqueKey
6. If you can use a certain fields of data like UUID of users as unique Key.
➡️ ValueKey
7. If you do not have any unique field to use as key but object itself is unique.
➡️ ObjectKey
8. If you have multiple Forms or Multiple Widgets of the same type that need GlobalKey.
➡️ GlobalObjectKey, LabeledGlobalKey whichever is appropriate, similar logic to ValueKey and ObjectKey
❌ Do not use random string/number as key, it defeats the purpose of keys ❌
The Key is an optional parameter needed to preserve state in your widget tree, you have to use them if you want to move a collection of elements in your tree and preserve the state of them.
The best explanation can be found in this video by Google When to Use Keys - Flutter Widgets 101 Ep. 4
With Dart 2.12 or later, add ? after Key to make it optional if you want.
class Frog extends StatelessWidget {
const Frog({
Key? key,
this.color: const Color(0xFF2DBD3A),
this.child,
}) : super(key: key);
final Color color;
final Widget child;
#override
Widget build(BuildContext context) {
return new Container(color: color, child: child);
}
}

Observer for Navigator route changes in Flutter

Is there a way to listen to route changes of the Navigator in Flutter? Basically, I'd like to be notified when a route is pushed or popped, and have the current route on display on screen be returned from the notification
Building on navigator observers, you can also use RouteObserver and RouteAware.
Navigator has observers. You can implement NavigatorObserver and receive notifications with details.
I was also struggling with that and for my purpose the RouteObservers felt overkill and where too much for my needs. The way I handled it lately was to use the onGenerateRoute property inside my MaterialApp. So this solution is more applicable if you are using onGenerateRoute with your app (might especially be useful if you are navigating with arguments). You can read more about it here.
My MaterialApp looks like the following:
runApp(MaterialApp(
title: 'bla',
home: BuilderPage(LoginArguments(false)),
onGenerateRoute: generateRoute
));
The generateRoute method looks like the following:
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'main':
print('Navigated to main')
return MaterialPageRoute(builder: (_) => MainScreen());
case 'register':
print('Navigated to register')
return MaterialPageRoute(builder: (_) => RegisterScreen());
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}')),
));
}
}
So everytime I now do for example:
Navigator.pushNamed(
context,
'register',
);
It will print Navigated to register. I think this way is especially helpful if you don't necessarily need to know whether you pushed or popped. With some additional implementation it would also be possible to observe that via injected arguments.

Resources