Fluent MVVM utility framework

Time:2021-12-16

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 inheritViewModelProviderWidgetTo 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 ChildViewModelProviderandValueViewModelProviderViewModel, this method is in the widgetbuildDuring use, if you want toinitStatusYou 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 providedViewModelValueListBuilderViewModelValueTuple2BuilderreachViewModelValueTuple7WidgetBuilderMultiple 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}");
          },
        ),
      ],
    );
  }