The MVVM framework is implemented based on the provider. The common way is that the ViewModel inherits the changenotifier and then provides it to the child widget through the changenotifierprovider. The ViewModel data refresh notifies the widget to refresh by calling notifylisteners(), and the widget updates through the provider Of, consumer and selector to listen for data changes and rebuild and update the UI. The problems with this approach are:
- ViewModel data refresh requires notifylisteners() to be called every time, which is easy to be missed
- Notifylisteners () acts on the entire ViewModel, which is not convenient for local UI refresh control
- Although the selector can control local refresh, it needs to customize shouldrebuild to understand the principle of provider
- The management of ViewModel and widget life cycle is missing
The viewmodelprovider is compatible with existing functions, implements the framework of minimal changes, does not need to call notifylisteners() every time, supports local UI refresh and life cycle management
First give the source code, and then introduce it in detail when you are freeview_model_provider
Local refresh control
1. Create observable objects through valuenotifier
class ViewModel extends ChangeNotifier {
final value1 = ValueNotifier(0);
final value2 = ValueNotifier(0);
}
2. Listen for data change and refresh through valuelistenablebuilder
ValueListenableBuilder(
valueListenable: viewModel.value1,
builder: (context, value, child) {
debugPrint("ValueListenableBuilder $value");
return Text("ValueListenableBuilder $value");
},
)
List refresh control
1. Create observable objects through listnotifier
class ViewModel extends ChangeNotifier {
final list = ListNotifier<String>([]);
}
2. Listen for data change and refresh through listenablebuilder
ListListenableBuilder(
valueListenable: viewModel.list,
builder: (context, value, child) {
debugPrint("ValueListenableBuilder $value");
return Text("ValueListenableBuilder $value");
},
)
Implement lifecycle management
Lifecyclewidget provides widget lifecycle monitoring, and opens the following callback interfaces for initialization and unbinding
- Create, you can listen for a data change
- Initstate, widget initstate callback
- Initframe, the first frame of the widget is drawn and called
- Deactivate, widget deactivate callback
- Dispose, widget dispose callback
- Didupdatewidget, widget, didupdatewidget callback
- Didchangedependencies, widget didchangedependencies callback
ViewModelProvider
Create a ViewModel to be used by child widgets, and open the following callback interfaces for initialization and unbinding
- Initviewmodel, which is executed during the first initialization of widget initstate
- Bindviewmodel: the ViewModel binds the widget for the first time, and the method is executed during widget build
- Disposeviewmodel, the ViewModel is destroyed, and the widget is disposed
///[viewmodelprovider] create ViewModel
class ProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ViewModelProvider<ViewModel>(
create: (_) => ViewModel(),
initViewModel: (context, viewModel) {
debugPrint("ProviderBuilderExample initViewModel $viewModel");
},
bindViewModel: (context, viewModel) {
debugPrint("ProviderBuilderExample bindViewModel $viewModel");
},
disposeViewModel: (context, viewModel) {
debugPrint("ProviderBuilderExample disposeViewModel $viewModel");
},
builder: (context, viewModel, child) {
debugPrint("ProviderBuilderExample builder $viewModel");
return ViewModelWidget(viewModel);
},
);
}
}
You can also inheritViewModelProviderWidget
To create a ViewModel
///Inherit [viewmodelproviderwidget] to create ViewModel
class ProviderWidgetExample extends ViewModelProviderWidget<ViewModel> {
ProviderWidgetExample() : super();
@override
ViewModel create(BuildContext context) => ViewModel();
@override
void initViewModel(BuildContext context, ViewModel viewModel) {
debugPrint("ProviderWidgetExample initViewModel $viewModel");
}
@override
void bindViewModel(BuildContext context, ViewModel viewModel) {
debugPrint("ProviderWidgetExample bindViewModel $viewModel");
}
@override
Widget buildChild(BuildContext context, ViewModel viewModel, Widget child) {
debugPrint("ProviderWidgetExample build $viewModel");
return ViewModelWidget(viewModel);
}
}
ViewModel nesting
ViewModel nesting ViewModel to manage child viewmodels provides two ways: one needs to manually call refresh, and the other needs to replace ViewModel through valuenotifier packaging. There is no need to manually refresh. Like viewmodelprovider, there are related abstract classes to provide inheritance support.
class ParentViewModel extends ChangeNotifier {
final valueViewModel = ValueNotifier(ChildViewModel());
var childViewModel = ChildViewModel();
void valueNotifier() {
valueViewModel.value = ChildViewModel();
}
void notifyListenerChild() {
childViewModel = ChildViewModel();
notifyListeners();
}
}
class ChildViewModel extends ChangeNotifier {
final value = ValueNotifier(0);
addValue() {
value.value++;
}
}
1 create parent ViewModel through viewmodelprovider
class ChildProviderExapmle extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ViewModelProvider<ParentViewModel>(
create: (context) => ParentViewModel(),
builder: (context, viewModel, child) {
return Scaffold(
body: Container(
child: Column(
children: [
ValueViewModelProviderExample(),
ChildViewModelProviderExample(),
ElevatedButton(
onPressed: () => viewModel.valueNotifier(),
child: Text("valueNotifier"),
),
ElevatedButton(
onPressed: () => viewModel.notifyListenerChild(),
child: Text("notifyListenerChild"),
),
],
),
),
);
},
);
}
}
2 create a child viewmodelprovider
2-1 ChildViewModelProvider
Manual refresh is required. It is usually used to refresh the item area of the list. It is added based on the existing callback of viewmodelprovider
- Changeviewmodel, the binding process can be re executed after the child ViewModel is replaced
///[childviewmodelprovider] get child ViewModel examples
class ChildViewModelProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChildViewModelProvider<ParentViewModel, ChildViewModel>(
create: (_, parent) => parent.childViewModel,
changeViewModel: (context, parent, viewModel, oldViewModel) {
debugPrint(
"ChildViewModelProvider changeViewModel $viewModel, $oldViewModel");
},
builder: (context, parent, viewModel, child) {
debugPrint("ChildViewModelProvider builder $viewModel");
return Row(
children: [
ValueListenableBuilder(
valueListenable: viewModel.value,
builder: (context, value, child) => Text("${viewModel.value}"),
),
ElevatedButton(
onPressed: () => viewModel.addValue(),
child: Text("addValue"),
)
],
);
},
);
}
}
2-2 ValueViewModelProvider
The function and callback are the same as childviewmodelprovider, and the received data type isValueListenable<ChangeNotifier>
///[valueviewmodelprovider] get child ViewModel examples
class ValueViewModelProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueViewModelProvider<ParentViewModel, ChildViewModel>(
create: (_, parent) => parent.valueViewModel,
changeViewModel: (context, parent, viewModel, oldViewModel) {
debugPrint(
"ValueViewModelProvider changeViewModel $viewModel, $oldViewModel");
},
builder: (context, parent, viewModel, child) {
debugPrint("ValueViewModelProvider builder $viewModel");
return Row(
children: [
ValueListenableBuilder(
valueListenable: viewModel.value,
builder: (context, value, child) => Text("${viewModel.value}"),
),
ElevatedButton(
onPressed: () => viewModel.addValue(),
child: Text("addValue"),
)
],
);
},
);
}
Get ViewModel
1 extension function
adoptcontext.viewModel<ViewModel>()
It can be removed quicklyViewModelProvider
ChildViewModelProvider
andValueViewModelProvider
ViewModel, this method is in the widgetbuild
During use, if you want toinitStatus
You can directly use the extension provided by the provider during usecontext.read<ViewModel>()
。
2 ViewModelBuilder
Used to retrieve the ViewModel provided by the viewmodelprovider
ValueListenableBuilder
Valuelistenablebuilder can only listen to the refresh of corresponding amount data, and can listen to multiple data refreshes at the same timeValueTuple2WidgetBuilderreachValueListenableTuple7BuilderandValueListenableListBuilder
///When the ViewModel is imported externally, the [valuelistenablebuilder] series can be used to monitor data changes
Widget _buildValueListenable(ViewModel viewModel) {
return Column(
children: [
///Monitor individual data changes
ValueListenableBuilder(
valueListenable: viewModel.value1,
builder: (context, value, child) {
debugPrint("ValueListenableBuilder $value");
return Text("ValueListenableBuilder $value");
}),
///Monitor multiple data changes, inherited from
ValueListenableListBuilder(
valueListenables: [
viewModel.value1,
viewModel.value2,
],
builder: (context, value, child) {
debugPrint(
"ValueListenableListBuilder ${value.first}, ${value.last}");
return Text(
"ValueListenableListBuilder ${value.first}, ${value.last}");
},
),
///Monitor multiple data changes, inherited from[ValueListenableListBuilder]可指定泛型
ValueListenableTuple2Builder(
valueListenables: Tuple2(viewModel.value1, viewModel.value2),
builder: (context, value, child) {
debugPrint(
"ValueListenableTuple2Builder ${value.item1}, ${value.item2}");
return Text(
"ValueListenableTuple2Builder ${value.item1}, ${value.item2}");
},
),
],
);
}
ViewModelValueBuilder
The combination of viewmodelbuilder and valuelistenablebuilder is used to obtain the ViewModel and manage the widget refresh area.
An implementation was providedViewModelValueListBuilder,ViewModelValueTuple2BuilderreachViewModelValueTuple7WidgetBuilderMultiple viewmode parameter changes can be monitored simultaneously to refresh the widget
///Instead of passing in the ViewModel externally, you can use the [viewmodelvaluebuilder] series to obtain the ViewModel and listen for data changes
Widget _buildViewModelValue() {
return Column(
children: [
ViewModelValueBuilder(
valueListenable: (ViewModel viewModel) => viewModel.value1,
builder: (context, viewModel, value, child) {
debugPrint("ViewModelValueBuilder $value");
return Text("ViewModelValueBuilder $value");
},
),
ViewModelValueListBuilder(
valueListenables: (ViewModel viewModel) => [
viewModel.value1,
viewModel.value2,
],
builder: (context, viewModel, value, child) {
debugPrint(
"ViewModelValueListBuilder ${value.first}, ${value.last}");
return Text(
"ViewModelValueListBuilder ${value.first}, ${value.last}");
},
),
ViewModelValueTuple2Builder(
valueListenables: (ViewModel viewModel) =>
Tuple2(viewModel.value1, viewModel.value2),
builder: (context, viewModel, value, child) {
debugPrint(
"ViewModelValueTuple2Builder ${value.item1}, ${value.item2}");
return Text(
"ViewModelValueTuple2Builder ${value.item1}, ${value.item2}");
},
),
],
);
}