The general paradigm used by providers in lists
In the previous tutorial, most of our scenarios are in the ordinary Box layout, I believe you are very clear about the use of providers, let’s look at the use of the List scenario, I believe that for many apps, lists should be the core UI of most pages, so, How do you use providers when a list is “drop-down refresh”, “drop-down load more”, or “Item click to modify state”? The official Demo does not give good advice, and the official Demo does not involve changing the list, so I will discuss with you how to use providers in lists.
Of course, this is just my exploration, and I hope readers can come up with better solutions.
First, create the Demo interface for this example.
In order to simplify the Demo and let readers focus on the use of providers, instead of using the framework of pull-down refresh and pull-up load, two buttons are used to simulate these two operations. Meanwhile, each Item provides a CheckBox to demonstrate the refresh of a single Item.
To restore the scene as much as possible, Mock data is also provided, as shown below.
class ItemModel {
String title;
bool isCheck;
int likeCount;
ItemModel(this.title, this.isCheck, this.likeCount);
}
class DataModel {
List<ItemModel> dataList = List();
Future<List<ItemModel>> getData(int pageIndex) async {
List<ItemModel> items = await api.getListDataOfIndex(pageIndex);
dataList.addAll(items);
return dataList;
}
}
class API {
Future<List<ItemModel>> getListDataOfIndex(int pageIndex) async {
List<ItemModel> data = List();
await Future.delayed(Duration(seconds: 3));
List.generate(
10,
(index) => (data.add(ItemModel('Title $index @Page $pageIndex', false, Random().nextInt(20) + 1))),
);
return data;
}
}
var api = API();
Copy the code
For additional UI code, you can refer to Dojo’s source code, as shown below.
flutter_dojo/category/backend/providerstate4widget.dart
Copy the code
Using Setstate
Let’s start with the most basic approach. Data is updated through setState, which is used to refresh the UI after the Future is complete. The core code is shown below.
Get the data.
data.getData(pageIndex).then((value) {
setState(() => data.dataList = value);
});
Copy the code
Refresh selected.
Checkbox( value: itemModel.isCheck, onChanged: (flag) { setState(() { var isCheck = itemModel.isCheck; if (isCheck) { checkedCount--; } else { checkedCount++; } return itemModel.isCheck = ! isCheck; }); }),Copy the code
Pull-down refresh and pull-up load.
RaisedButton(
onPressed: () {
setState(() => data.dataList.clear());
data.getData(0).then((value) {
setState(() => data.dataList = value);
});
},
child: Text('Refresh'),
),
RaisedButton(
onPressed: () {
data.getData(++pageIndex).then((value) {
setState(() => data.dataList = value);
});
},
child: Text('Load More'),
),
Text('Checked Count $checkedCount'),
Copy the code
There’s nothing wrong with this approach, especially if the List takes up the entire UI, and it’s actually the simplest and most efficient. Only when the page is complex should you consider using a Provider to reduce the efficiency of refreshing.
Now let’s think about how we can modify the whole Demo with a Selector to refresh data, load more data, and display Checked numbers.
Transformation Model
Model is the Provider’s data processing object, which encapsulates the data Model and data processing operations. The transformation here is basically the same as the Model using the Provider described earlier, as shown in the code below.
class ItemModel { String title; bool isCheck; int likeCount; ItemModel(this.title, this.isCheck, this.likeCount); } class DataModel with ChangeNotifier { List<ItemModel> dataList = List(); int checkedCount = 0; bool shouldListRebuild = true; getData(int pageIndex) async { List<ItemModel> items = await api.getListDataOfIndex(pageIndex); shouldListRebuild = true; dataList.addAll(items); notifyListeners(); } refreshData() { dataList.clear(); checkedCount = 0; shouldListRebuild = true; notifyListeners(); } updateChecked(int index, bool isChecked) { shouldListRebuild = false; var item = dataList[index]; if (isChecked) { checkedCount++; } else { checkedCount--; } dataList[index] = ItemModel(item.title, isChecked, item.likeCount); notifyListeners(); }}Copy the code
Several functions have been added to fetch paging data, refresh data, and update the Checked status of items.
Modify the refresh logic for ListItem selection
In the previous scheme, when we clicked on an Item to modify it, the whole List would be rebuilt, and we could filter out the Item that we wanted to refresh by Selector.
When the List is fixed, you don’t need to refresh the entire List, you just need to update the changed items.
In the ItemBuilder of the List, we do a Selector filter. The filter content is ItemModel in the dataList. When the specified Item is clicked, the model is updated. So the Selector’s shouldRebuild is judged to be true, so this Item will be updated, and other unclicked items will not be updated because they haven’t changed. This controls the refresh range of the List to the updated Item, as shown below.
return ListView.builder( itemBuilder: (context, index) { return Selector<DataModel, ItemModel>( selector: (context, value) => value.dataList[index], builder: (BuildContext context, data, Widget child) { debugPrint(('Item $index rebuild')); Return Card(child: Padding(Padding: const EdgeInsets. All (8.0), child: Row(children: [Checkbox(value: data.isCheck, onChanged: (flag) { dataModel.updateChecked(index, !data.isCheck); }), Text(data.title), Spacer(), Icon(Icons.favorite), ConstrainedBox( constraints: BoxConstraints(minWidth: 30), child: Center(child: Text(data.likeCount.toString())), ), ], ), ), ); }); }, itemCount: dataModel.dataList.length, );Copy the code
The refresh control for an Item is actually quite simple, and it’s a general solution for selectors, but the context for using a Selector is a List of fixed data. If the List data changes, then there’s a problem with using Selector. For example, most of our List usage scenarios involve refreshing data and loading paging data, so the data source of the List is always changing, and when the home page data is loaded, We may also need to display a Loading interface, so in these scenarios, the entire List must be rebuilt. In this case, a Selector can’t do anything. However, we can add another Selector to control whether the List needs to be refreshed or not. Fine-tune the refresh range of the List.
- Refresh the entire List when the List data is not fixed
- When the list data is fixed, only updated items are refreshed
With that in mind, you can see why we need a shouldListRebuild variable in the previous Model, with the rest of the code shown below.
Expanded(
child: Selector<DataModel, DataModel>(
shouldRebuild: (pre, next) => pre.shouldListRebuild,
selector: (context, dataModel) => dataModel,
builder: (BuildContext context, DataModel dataModel, Widget child) {
if (dataModel.dataList.length > 0) {
return ListView.builder(
itemBuilder: (context, index) {
return Selector<DataModel, ItemModel>(
selector: (context, value) => value.dataList[index],
builder: (BuildContext context, data, Widget child)
Copy the code
One trick here is Selector<DataModel, DataModel>, which only uses the Selector shouldRebuild method, so it doesn’t filter the data. Please refer to the implementation in Dojo for the complete code.
flutter_dojo/category/backend/providerstate4widget.dart Copy the code
The actual action is to shouldRebuild true for things like refreshing and loading pagination data, and shouldRebuild false for things like modifying only Item data, thus fully controlling the refresh range of the List.
To sum up
Of course, such processing is only for scenarios that require extreme performance. In most cases, it is not necessary to consider such details, and the Rebuild of List will not incur much performance overhead. Developers need to adopt different schemes for different scenarios, and there is no need to control the refresh too strictly.
For more information, please pay attention to my personal website – xuyisheng.top/
The public no. :