Flutter-MVVM-Hooks (state management)

Mahdi Ben Amor
5 min readMay 13, 2021

Model–View–ViewModel (MVVM) is well known, so what is special about it ?

In this blog, we will introduce the importance Model View ViewModel (MVVM) architecture. Used to implement application designs.

The MVVM model provides a uniform distribution of data with the advantages of flexibility and reusability of code as well as data.

Model–view–viewmodel (MVVM) is a software architectural pattern that facilitates the separation of the graphical user interface development
(the View) from the business logic or back-end logic development(the model) so that the View is not dependent on any specific model platform.
The ViewModel of MVVM is a value handler, meaning it is responsible for exposing the data objects from the model in such a way that objects are easily managed and presented.

In this respect, the ViewModel is more model than View, and handles most if not all of the View’s display logic.
The View model may implement a mediator pattern, organizing access to the back-end logic around the set of use cases supported by the View.

The Model:

The Model is what I like to refer to as the domain object. The Model represents the actual data and/or information we are dealing with.
The key to remember with the Model is that it holds the information, but not behaviors or services that manipulate the information, It is not responsible for formatting text to look pretty on the screen.

The View:

The View is what most of us are familiar with and the only thing the end user really interacts with.
It is the presentation of the data. The View takes certain liberties to make this data more presentable.
One thing to remember about the view is that it is not responsible for maintaining its state. Instead, it will synchronize this with the ViewModel.

The ViewModel:

The viewmodel is a key piece of the triad because it introduces Presentation Separation, or the concept of keeping the nuances of the View separated from the Model.

Instead of making the Model aware of the user’s View of data, the Model simply holds it, and View holds the formatted data, while the ViewModel acts as the liaison between the two.

ViewModel might take input from the view and place it on the model, or it might interact with a service to retrieve the model, then translate properties and place it on the View.
The ViewModel also exposes methods, commands, and other points that help to maintain the state of the view, manipulate the Model as the result of actions on the View, and trigger events in the view itself.

UseCase:

Views :

In this quick exemple, we are using Flutter hooks to manage the Widgets life cycle and to increase the code-sharing between widgets by removing duplicates.
So for that we are creating a class called BasePage wich is going to be the parent of all screens of the app.
Not only that, but we are adding a dynamic object “BindingContext” definied in a mixin called “IBindablePage”. This variable will hold the ViewModel (BindingContext) forEach view.

Nevertheless, it will be setted or created only when navigating to the current screen. More details will come over bellow.

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_app/views/IBindablePage.dart';
class BasePage extends HookWidget with IBindablePage {
BasePage();
@override Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
void setState(VoidCallback fn) {
fn();
}
}

ViewModel:

ViewModel are a class that holds the business layer of each screen, it is created only while navigating to the current page.

import'package:flutter_app/Services/Navigation/Navigation/ANavigationService.dart'; abstract class ANavigableViewModel{  

ANavigationService navigationService ;
ANavigableViewModel({ANavigationService navigationService}) {}
}

The problem here is how we are going to map each view with it’s current ViewModel, so we are creating a static object called viewLocator
containing a dictionnary that hold for each view its ViewModel.
When the app boots, we are creating this object:

static void RegisterDependencies() {    registerViewLocator();   
registerServicesLocator();
}
static void registerViewLocator() {

ViewLocator viewLocator = ViewLocator();
Map<Type, Type> viewLocatorDict = Map<Type, Type>();
viewLocatorDict[HomePageViewModel] = HomeViewPage; viewLocatorDict[LoginPageViewModel] = LoginViewPage; viewLocatorDict[SettingsPageViewModel] = SettingsViewPage;

viewLocator.ViewLocatorDictionary = viewLocatorDict; ioc.registerSingleton<ViewLocator>(viewLocator);
}
static void registerServicesLocator() {
// --ProviderLocator
// hold the instance for app's services
ProviderLocator providerLocator = ProviderLocator(); providerLocator.AddSerivce(NavigationService,new NavigationService()); ioc.registerSingleton<ProviderLocator>(providerLocator);

}

Whenever the user decide to navigate to another screen, he only need to localize the ViewModel type to wich we are navigating. Then we are making a centralized and reliable navigation system.
So at runTime, and only at the moment of navigation, we are creating the new BindingContext and the new View based on the ViewModelType passed while navigating.

class ViewLocator{

Map<Type, Type> ViewLocatorDictionary;

BasePage GetViewFor<ViewModelType>(){
switch (ViewModelType) {
case HomePageViewModel:
{
BasePage page = HomeViewPage();
page.BindingContext =
GetViewModelForView<HomePageViewModel>();
return page;
}
case LoginPageViewModel:
{
BasePage page = LoginViewPage();
page.BindingContext =
GetViewModelForView<LoginPageViewModel>();
return page;
}
case SettingsPageViewModel:
{
BasePage page = SettingsViewPage();
page.BindingContext =
GetViewModelForView<SettingsPageViewModel>();
return page;
}
case ControlShowCasesPageViewModel:
{
BasePage page = ControlShowCasesViewPage();
page.BindingContext=
GetViewModelForView<ControlShowCasesPageViewModel();
return page;
}
break;
}
}

ANavigableViewModel GetViewModelForView<ViewModelType>({List<Type> services}){
ProviderLocator services =
EntryPoint.ioc.get<ProviderLocator>();

switch (ViewModelType) {
case HomePageViewModel:
return new HomePageViewModel(
services.GetSerive<NavigationService>()
);
case LoginPageViewModel:
return new LoginPageViewModel(
services.GetSerive<NavigationService>()
);
case SettingsPageViewModel:
return new SettingsPageViewModel(
services.GetSerive<NavigationService>()
);
case ControlShowCasesPageViewModel:
return new ControlShowCasesPageViewModel(
services.GetSerive<NavigationService>()
);
break;
}
}
}

Here, we are injecting this object over the app using “get_it” package.
Also when the app boots, we are creating the services that the app needs, hither we should keep trace for each ViewModel’s services, to solve this problem, a static object called ProviderLocator is going to hold this logic.

Model:

In this case, just we are using just a static models “modelShowcases
and we do recommande to use IRepository pattern to unify the data incoming weather it’s from local or distant database.

When it comes to mobile development, I recommend the MVVM model when possible, regardless of “native development, cross platform, hybrid.”

Full Repository code available here

--

--

Mahdi Ben Amor

I am technology enthusiastic, want to learn new technologies and dive deep inside it. I always believe in developing new and logical things