Keep a list from provider up to date with AnimatedListView - flutter

I'm using the Provider library and have a class that extends ChangeNotifier. The class provides an UnmodifiableListView of historicalRosters to my widgets.
I want to create an AnimatedListView that displays these rosters. The animated list supposedly needs a separate list which will tell the widget when the widget to animate in or out new list items.
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
This example uses a model to update both the list attached to the widget's state and the AnimateListState at the same time.
// Inside model
void insert(int index, E item) {
_items.insert(index, item);
_animatedList.insertItem(index);
}
I want to do the same thing but I'm having trouble thinking how. That is, what's a good way I can update the list provided by provider and also change the AnimatedListState.

Related

Avoid no_logic_in_create_state warning when saving reference to State of StatefulWidget in Flutter

Using Flutter, I display a list of elements in an app.
I have a StatefulWidget (ObjectList) that holds a list of items in its State (ObjectListState).
The state has a method (_populateList) to update the list of items.
I want the list to be updated when a method (updateList) is called on the widget.
To achieve this, I save a reference (_state) to the state in the widget. The value is set in createState. Then the method on the state can be called from the widget itself.
class ObjectList extends StatefulWidget {
const ObjectList({super.key});
static late ObjectListState _state;
#override
State<ObjectList> createState() {
_state = ObjectListState();
return _state;
}
void updateList() {
_state._populateList();
}
}
class ObjectListState extends State<ObjectList> {
List<Object>? objects;
void _populateList() {
setState(() {
// objects = API().getObjects();
});
}
// ... return ListView in build
}
The problem is that this raises a no_logic_in_create_state warning. Since I'm not "passing data to State objects" I assume this is fine, but I would still like to avoid the warning.
Is there a way to do any of these?
Saving the reference to the state without violating no_logic_in_create_state.
Accessing the state from the widget, without saving the reference.
Calling the method of the state from the outside without going through the widget.
It make no sense to put the updateList() method in the widget. You will not be able to call it anyway. Widgets are part of a widget tree, and you do not store or use a reference to them (unlike in other frameworks, such as Qt).
To update information in a widget, use a StreamBuilder widget and create the widget to be updated in the build function, passing the updated list to as a parameter to the widget.
Then, you store the list inside the widget. Possibly this may then be implemented as a stateless widget.

How to check class parent from BuildContext?

I'm implementing a commerce app and the focus now is on the three components: CartPage, CatalogPage and ProductListItem. The ProductListItem component is reused on two pages, cart and catalog. So inside ProductListItem Widget I must know what class is your parent to return the specific component. So far I mean to use BuildContext object to get some information from parent. If no this way, I mean on use a static string to check the parent, passing it on constructor when create ProductListItem component.
class ProductListItem extends StatefulWidget {
final Product product;
ProductListItem(this.product);
#override
_ProductListItemState createState() => _ProductListItemState(product);
}
class _ProductListItemState extends State<ProductListItem> {
final Product product;
int amount = 1;
_ProductListItemState(this.product);
#override
Widget build(BuildContext context) {
// if (parent is Cart) return 1;
// else if (parent is Catalog) return 2;
}
}
I'm accepting suggestions for other changes too.
BuildContext objects are passed to WidgetBuilder functions (such as StatelessWidget.build), and are available from the State.context member. Some static functions (e.g. showDialog, Theme.of, and so forth) also take build contexts so that they can act on behalf of the calling widget, or obtain data specifically for the given context.
Each widget has its own BuildContext, which becomes the parent of the widget returned by the StatelessWidget.build or State.build function. (And similarly, the parent of any children for RenderObjectWidgets.)
Maybe you want get history of Navigator to check parent.
Currently Navigator not support to get history of Navigator. But Flutter team is will update this feature on Navigator 2.0, They are done and merge to master. Waiting for next release.
How to get _history from NavigatorState
Navigator 2.0
On your case, temporary, pass flag to Detail Page for Parent.

How to provide data with provider using nested data models [List > Item > Sublist > SubItem]

i started using Provider package for state management, and using it in a basic way.
As the app gets more complex i want to extend the usage.
Now i have this model structure in mind: List<Client> having a List<Product> (and deeper having a List<Component>).
I have a MultiProvider using a ChangeNotifierProvider for the Clients, means the List<Client> is managed by the provider, so far so good.
Now i want to directly use the List<Product> in a provider, or later the List<Component> inside the List<Product>. I do not want to go the way through the List<Client>...down to the Component.
Here i have an image map of the structure to visualize.
Here is some simplified code:
// Just an example idea of..
Class Product with ChangeNotifier {
final String title;
}
Class Client with ChangeNotifier {
final String name;
final String List<Product>;
}
Class Clients with ChangeNotifier {
final List<Client> _items;
}
void main() {
// start the app
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (ctx) => Clients()),
// How to provide a List<Product> that actually in the model
// belongs to a Client in the List<Client>
],
child: MaterialApp(
body: ...
)
);
}
}
So the main question is how to provide a List<Product> that actually in the model
belongs to a Client in the List<Client>?
Thanks for your help in the comments.
There seem to be two possible solutions to it.
Do not use nested structure. This is possible by keeping the id of the parent inside the before nested list and throw it out the of the parent list. Then give it an own ChangeNotifierProvider and filter the items by the parent id when needed.
So in my example List and List are on the same level and both have an
ChangeNotifierProvider. The product model holds the id of it's client and the list has an method getbyClientId(clientId) that results in a filtered productslist to the specified client. That's it.
Use ChangeNotifierProxyProvider to delegate the request into the nested list.
I have chosen the first way as in my case it seemed the right way. And after working some time now with it i can not say it was a wrong decision, so i have my freedom with it.
Probably for other cases the second approach might be better. I haven't invested too much time in ChangeNotifierProxyProvider, so please check other sources for more details on that.

Flutter: display dynamic list

I've got a widget that displays a classes' list, but that list changes (the content changes) over time by other elements and interactions of the program. How can the DisplayListWidget detect this? Do the elements that change this list have to communicate with the displaylistwidget?
EDIT: I'm familiair with stateful widgets and setState () {}. It's just that the data changes in the background (e.g. by a timer) so there's no reference from bussiness logic classes to widgets to even call setState.
If you would like to notify (rebuild) widgets when data changes, check out provider package.
There are some other options:
BLoC (Business Logic Component) design pattern.
mobx
Redux
Good luck
Try wrapping the display widget in a state widget, and then whenever you update the list, call setState(() { //update list here })
ex.
// this is the widget that you'd nest directly into the rest of your tree
class FooList extends StatefulWidget {
FooListState createState() => FooListState();
}
// this is the state you will want to call setState on
class FooListState extends State<FooList> {
Widget build(BuildContext context) {
return DisplayListWidget [...]
}
}
I would recommend following this flutter tutorial where they dynamically update a list
And to learn more about StatefulWidgets and States check this out: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html

Understanding Flutter Render Engine

The docs here about how to update a ListView say:
In Flutter, if you were to update the list of widgets inside a
setState(), you would quickly see that your data did not change
visually. This is because when setState() is called, the Flutter
rendering engine looks at the widget tree to see if anything has
changed. When it gets to your ListView, it performs a == check, and
determines that the two ListViews are the same. Nothing has changed,
so no update is required.
For a simple way to update your ListView, create a new List inside of
setState(), and copy the data from the old list to the new list.
I don't get how the Render Engine determines if there are any changes in the Widget Tree in this case.
AFAICS, we care calling setState, which marks the State object as dirty and asks it to rebuild. Once it rebuilds there will be a new ListView, won't it? So how come the == check says it's the same object?
Also, the new List will be internal to the State object, does the Flutter engine compare all the objects inside the State object? I thought it only compared the Widget tree.
So, basically I don't understand how the Render Engine decides what it's going to update and what's going to ignore, since I can't see how creating a new List sends any information to the Render Engine, as the docs says the Render Engine just looks for a new ListView... And AFAIK a new List won't create a new ListView.
Flutter isn't made only of Widgets.
When you call setState, you mark the Widget as dirty. But this Widget isn't actually what you render on the screen.
Widgets exist to create/mutate RenderObjects; it's these RenderObjects that draw your content on the screen.
The link between RenderObjects and Widgets is done using a new kind of Widget: RenderObjectWidget (such as LeafRenderObjectWidget)
Most widgets provided by Flutter are to some extent a RenderObjectWidget, including ListView.
A typical RenderObjectWidget example would be this:
class MyWidget extends LeafRenderObjectWidget {
final String title;
MyWidget(this.title);
#override
MyRenderObject createRenderObject(BuildContext context) {
return new MyRenderObject()
..title = title;
}
#override
void updateRenderObject(BuildContext context, MyRenderObject renderObject) {
renderObject
..title = title;
}
}
This example uses a widget to create/update a RenderObject. It's not enough to notify the framework that there's something to repaint though.
To make a RenderObject repaint, one must call markNeedsPaint or markNeedsLayout on the desired renderObject.
This is usually done by the RenderObject itself using custom field setter this way:
class MyRenderObject extends RenderBox {
String _title;
String get title => _title;
set title(String value) {
if (value != _title) {
markNeedsLayout();
_title = value;
}
}
}
Notice the if (value != previous).
This check ensures that when a widget rebuilds without changing anything, Flutter doesn't relayout/repaint anything.
It's due to this exact condition that mutating List or Map doesn't make ListView rerender. It basically has the following:
List<Widget> _children;
List<Widget> get children => _children;
set children(List<Widget> value) {
if (value != _children) {
markNeedsLayout();
_children = value;
}
}
But it implies that if you mutate the list instead of creating a new one, the RenderObject will not be marked as needing a relayout/repaint. Therefore there won't be any visual update.