Intro to MVVM architecture in Flutter using PMVVM package
Tags: flutter, architecture
Published: 2021-08-24 20:28:21

Flutter is easy to pick up and start with coding. For small projects, low-quality code will not be a problem for the team, but as the codebase of the project increases over time, you will end up with complicated classes, code duplicates, and views containing business logic. Therefore, it's important to utilize architectural patterns in your project to keep your code loosely coupled, maintainable, and easy to test.

> By definition, an [Architecture Pattern](http://pubs.opengroup.org/architecture/togaf8-doc/arch/chap28.html) expresses a fundamental structural organization or schema for software systems. It provides a set of predefined subsystems, specifies their responsibilities, and includes rules and guidelines for organizing the relationships between them.

Since architectural patterns are platform-agnostic, in this article we’ll see how an architectural pattern such as MVVM can be applied to a Flutter project and significantly improve the code quality.

---

## Dive into MVVM ⚙️

MVVM stands for Model-View-ViewModel, it originated from Microsoft and was heavily used in writing Windows Presentation Foundation (WPF) applications. MVVM facilitates the separation of the widgets (the _view_) from the business logic (the _model_) using view models.

We need three major pieces in MVVM. These pieces are:
![MVVM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lee16itnxr22nzb0d56v.png)

### **View**

It represents the UI of the application devoid of any application logic. The view model sends notifications to the view to update the UI whenever the state changes.

### **View Model**

Which holds the state and the events of the view. Additionally, It acts as a bridge between the model and the view.

The view model is platform-independent and doesn't know its view. Therefore, it can be easily bound to a web, mobile, or desktop view.

![MVVM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z54j5a3vz8duc3zkx65w.png)

### **Model**

Holds app data and the business logic (e.g. local and remote data sources, model classes, and repositories). They’re usually simple classes.

#### Advantages ✔️

We can summarize the advantages of MVVM into the following:

- Your code will be more easily testable.
- Your code will be further decoupled (the biggest advantage.)
- The project is even easier to maintain.
- Your team can add new features even more quickly.

---

## MVVM using PMVVM package

[PMVVM](https://pub.dev/packages/pmvvm) is a Flutter package for simple and scalable state management based on the MVVM pattern, it uses Provider & Hooks under the hood. PMVVM serves the same purpose as BloC, but unlike BloC it doesn’t require too much boilerplate.

It's worth mentioning that the package adopts some concepts from the Stacked package, but with a much simpler and cleaner approach.

**When should you use it?** 👌

To keep it simple, use the `MVVM` whenever your widget has its own events that can mutate the state directly e.g: pages, posts, ...etc.

## Usage 👨‍💻

The best way to get to know PMVVM is to get your hands dirty and try it out. Let's look at the code:

- Build your `ViewModel`.

```dart
class MyViewModel extends ViewModel {
  int counter = 0;

  // Optional
  @override
  void init() {
    // It's called after the ViewModel is constructed
  }

  // Optional
  @override
  void onBuild() {
    // It's called everytime the view is rebuilt
  }

  void increase() {
    counter++;
    notifyListeners();
  }
}
```
- You can also access the `context` inside the `ViewModel` directly.

```dart
class MyViewModel extends ViewModel {
  @override
  void init() {
    var height = MediaQuery.of(context).size.height;
  }
}
```

- Declare `MVVM` inside your builder.

```dart
class MyWidget extends StatelessWidget {
  const MyWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MVVM<MyViewModel>(
      view: (context, vmodel) => _MyView(),
      viewModel: MyViewModel(),
    );
  }
}
```
- Build your `View`.

```dart
// StatelessView

class _MyView extends StatelessView<MyViewModel> {
  /// Set [reactive] to [false] if you don't want the view to listen to the ViewModel.
  /// It's [true] by default.
  const _MyView({Key key}) : super(key: key, reactive: true);

  @override
  Widget render(context, vmodel) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text(vmodel.counter.toString()),
        SizedBox(height: 24),
        RaisedButton(onPressed: vmodel.increase, child: Text('Increase')),
      ],
  );
}

// HookView

class _MyView extends HookView<MyViewModel> {
  /// Set [reactive] to [false] if you don't want the view to listen to the ViewModel.
  /// It's [true] by default.
  const _MyView({Key key}) : super(key: key, reactive: true);

  @override
  Widget render(context, vmodel) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text(vmodel.counter.toString()),
        SizedBox(height: 24),
        RaisedButton(onPressed: vmodel.increase, child: Text('Increase')),
      ],
    );
  }
}
```

## More about PMVVM🎯

- The `init` lifecycle method is called by default every time the view model dependencies are updated. To init the `ViewModel` only once and ignore dependencies updates, set `initOnce` of the `MVVM` builder to `true`.
- You can use `context.fetch<T>(listen: true/false)` which is equivalent to `Provider.of<T>(context)`
- To make the view ignore the state notifications from the `ViewModel` , set `reactive` to `false` when you are constructing the `StatelessView` or `HookView` :

```dart
class _MyView extends StatelessView<MyViewModel> {
  const _MyView({Key key}) : super(key: key, reactive: false);
  ....
}
```

- `ViewModel` Lifecycle methods **(All of them are optional)**

```dart
/// - A callback after [ViewModel] is constructed.
/// - The event is called by default every time the [ViewModel] view dependencies are updated.
/// - Set [initOnce] of the [MVVM] as [true] to ignore dependencies updates.
void init() {}

/// A callback when the [build] method is called.
void onBuild() {}

/// A callback when the view disposed.
void onDispose() {}

/// A callback when the application is visible and responding to user input.
void onResume() {}

/// A callback when the application is not currently visible to the user, not responding to
/// user input, and running in the background.
void onPause() {}

/// - A callback when the application is in an inactive state and is not receiving user input.
/// - For [IOS] only.
void onInactive() {}

/// - A callback when the application is still hosted on a flutter engine but
///   is detached from any host views.
/// - For [Android] only.
void onDetach() {}
```
---

## Patterns 🧩

In this section Let's discuss some patterns that can help your view model to access your widget properties:

- **Naive approach:** Using cascade notation to initialize your view model with the widget properties. The problem with this approach is that your view model becomes non-reactive when the widget's dependencies (properties) are updated.

```dart
class MyWidget extends StatelessWidget {
  const MyWidget({Key key, this.varName}) : super(key: key);

  final String varName;

  @override
  Widget build(BuildContext context) {
    return MVVM<MyViewModel>(
      view: (context, vmodel) => _MyView(),
      viewModel: MyViewModel()..varName = varName,
    );
  }
}
```

- **Clean approach:** similar to ReactJS, you should create a properties class, in which all your widget properties are kept.

`my_widget.props.dart`

```dart
class MyWidgetProps {
  MyWidgetProps({required this.name});

  final String name;
}
```

`my_widget.vm.dart`

```dart
class MyWidgetVM extends ViewModel {
  late MyWidgetProps props;

  @override
  void init() {
    props = context.fetch<MyWidgetProps>();
  }
}
```

`my_widget.view.dart`

```dart
class MyWidget extends StatelessWidget {
  MyWidget({
    Key? key,
    required String name,
  })  : props = MyWidgetProps(name: name),
        super(key: key);

  final MyWidgetProps props;

  Widget build(context) {
    return Provider.value(
      value: props,
      child: MVVM<MyWidgetVM>(
        view: (_, __) => _MyWidgetView(),
        viewModel: MyWidgetVM(),
      ),
    );
  }
}
```

## FAQ 🤔

- Can I use it in production?
  - Yep! It's stable and ready to rock
- What is the difference between `Stacked` & `PMVVM` since both adopt the same principles?


| **Stacked**                                                                                                                                                               | **PMVVM**                                                                                                                                                                                                        |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| You can't access the `BuildContext` from the `ViewModel`.                                                                                                                 | `BuildContext` can be accessed inside the `ViewModel` using:<br />- `Provider.of<T>(context)` <br />- `context.watch<T>()` <br />- `context.read<T>()` <br />- `context.select<T, R>(R cb(T value))`         |
| You should implement the `Initialisable` interface to call `initialise`.                                                                                                  | `init` event is called by default, all you need to do is to override it (optional).                                                                                                                              |
| There is no `build` method in the `ViewModel`.                                                                                                                            | `onBuild` method is called by default every time the `View` is rebuilt, and you can override it to implement yours (optional).                                                                                   |
| It over-wraps `provider` with many `ViewModels` like `FutureViewModel`, `StreamViewModel`, …etc. Which `provider` & `flutter_hooks` are built to do without any wrapping. | It doesn’t over-wrap `provider` package with such classes. Instead, you can use `StreamProvider/FutureProvider` or `Hooks` which gives you the flexibility to make the most out of `provider` & `flutter_hooks`. |
| It has **reactive & non-reactive** constructors that force developers to use consumers in a specific position in the sub-tree.                                            | It doesn’t have such concepts, all you need is to declare the `MVVM` and consume it from anywhere in the sub-tree.                                                                                               |