MVVM With Flutter (Part1){MVVM, Provider, Injectable, Freezed}
Tags: flutter, mobile, tutorial, devjournal
Published: 2022-08-11 08:48:00

# Hai!, 
Here i'd like to record my code base line for flutter using MVVM pattern, 

#The Idea and the pattern
so the idea is i'd like to bind view and viewmodel one on one and making the viewmodel as our boiler plate to orchestrate all the logic related to its view like bellow.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3vdky68deu7dx542f84z.png) 

i'm using these dependencies to run 
- Injectable --> [Dependencies Injection](https://pub.dev/packages/injectable#setup) 
- Freeze --> [Model Mapper](https://pub.dev/packages/freezed)
- Provider --> [State Management](https://pub.dev/packages/provider)

# The Goal, What the apps will looks like
as a sample i create todolist app, like this

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e0kckwszv4arz7j6aml4.gif)!
 
# Now The Dev, lets deep into the code
now, in order to create the apps as above, we need 3 things.
- 1 Screen **BIND** to 1 View Model,  
- 1 Repositories Available for **any** view model
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5emr2ja31rd9tth3ot72.png) 

### 1. Here is the MainScreen..
```
class MainScreen extends BaseView<MainScreenViewModel> {
  @override
  Widget build(
      BuildContext context, MainScreenViewModel viewModel, Widget? child) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showModalBottomSheet(
              context: context,
              isScrollControlled: true,
              builder: (context) => SingleChildScrollView(
                    child: Container(
                        padding: EdgeInsets.only(
                            bottom: MediaQuery.of(context).viewInsets.bottom),
                        child: AddTaskScreen(onAddTaskClicked: viewModel.onAddTaskClicked,)),
                  ));
        },
        child: Icon(Icons.add),
      ),
      body: SafeArea(
        child: Container(
          color: Colors.lightBlue,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Container(
                padding: EdgeInsets.symmetric(horizontal: 30),
                margin: EdgeInsets.only(top: 50, left: 0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Container(
                      child: Container(
                        child: CircleAvatar(
                          child: Icon(
                            Icons.list_rounded,
                            color: Colors.lightBlue,
                            size: 75,
                          ),
                          radius: 50,
                          backgroundColor: Colors.white,
                        ),
                      ),
                    ),
                    Container(
                      child: Text(
                        "What To Do!",
                        style: TextStyle(
                            fontSize: 30, fontWeight: FontWeight.w300),
                      ),
                    ),
                  ],
                ),
              ),
              Expanded(
                child: Container(
                  padding: EdgeInsets.symmetric(horizontal: 20),
                  decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.only(
                          topLeft: Radius.circular(30),
                          topRight: Radius.circular(30))),
                  margin: EdgeInsets.only(top: 25),
                  child: ListView.builder(
                    itemCount: viewModel.taskDataCount,
                    itemBuilder: (context, index) {
                      return ListViewTaskItem(data: viewModel.taskData[index]);
                  },),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}
```
as you can see 
```
Widget build(BuildContext context, MainScreenViewModel viewModel, Widget? child)
``` 
now having access to the viewModel, meaning you can access all the state within that viewModel.

and now here is the `MainScreenViewModel`


### 2. Here is the MainScreenViewModel..
```
@injectable
class MainScreenViewModel extends BaseViewModel {
  List<TaskModel>? _tasksData;

  final ITaskRepository _taskRepository = getIt<TaskRepositoryImpl>();

  List<TaskModel> get taskData => _tasksData ?? [];  
  int get taskDataCount => _tasksData?.length ?? 0;

  @override
  void init() async{
    _tasksData = await _taskRepository.getAllTask();
    notifyListeners();
  }
  
  Future<void> onAddTaskClicked(String textValue) async {
    final data = TaskModel(description: textValue,title: textValue, isDone: false);
    _tasksData?.add(data);
    notifyListeners();
  }
}
```
on that code above everytime whenever the `init` are invoke the viewModel will fetch the taskData from repositories `_taskRepository.getAllTask();`


### 3. and here is the task repositories looks like..
```
@singleton 
class TaskRepositoryImpl implements ITaskRepository{
  final String baseUrl = "run.mocky.io";
  
  @override
  Future<List<TaskModel>?> getAllTask() async{
    var url = Uri.https(baseUrl, "/v3/6bb86bda-08f1-4d7d-99c6-a975bc1201e0");
    final response = await networking.get(url);
    final responseBody = GetTaskDTO.fromJson(jsonDecode(utf8.decode(response.bodyBytes)) as Map<String,dynamic>);
    return responseBody.tasks;
  }
}
```
the repositories will responsible to makesure the data availability of task. and whenever the `getAllTask()` are invoked, repositories will fetch the data from the backendServies...

so now we have this flow : 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vaml3b2sb51vmp9jcjqn.png)
 
... 

#When Is The ViewModel Are Injected?
ok up until here.. it was easy to understand right?. now we are moving to when exactly the viewModel are injected into view? how to make sure the viewModel are available when its needed.

##First Lets take a look at the `MainScreenViewModel`
pay attention to the declaration.

```
@injectable
class MainScreenViewModel extends BaseViewModel
```
we put `@injectable` to the `MainScreenViewModel` **telling** our Dependencies Injection **DI** to make sure to create the `MainScreenViewModel` whenever it was fetch by `getIt`..
for example like 
```
final viewModel = getIt<MainScreenViewModel>();
``` 
now getIt will create the object `MainScreenViewModel` and provide it to `viewModel`.

## Ok, then where is the real one?
Back to our code structure, so where exactly the viewModel being injected to view? now you can open the `BaseView` class as bellow : 

```
final RouteObserver<ModalRoute<void>> routeObserver =
    RouteObserver<ModalRoute<void>>();

abstract class BaseView<T extends BaseViewModel> extends StatefulWidget {
  BaseView({Key? key}) : super(key: key);

  Widget build(BuildContext context, T viewModel, Widget? child);

  @override
  State<BaseView> createState() => BaseViewState<T>();
}

class BaseViewState<T extends BaseViewModel> extends State<BaseView<T>>
    with RouteAware {
  T? viewModel = getIt<T>();
  
  @mustCallSuper
  @override
  void initState() {
    super.initState();
    viewModel?.init();
  }

  @mustCallSuper
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    // subscribe for the change of route
    routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
  }

  /// Called when the top route has been popped off, and the current route
  /// shows up.
  @mustCallSuper
  @override
  void didPopNext() {
    
    viewModel?.routingDidPopNext();
  }

  /// Called when the current route has been pushed.
  @mustCallSuper
  @override
  void didPush() {
    
    viewModel?.routingDidPush();
  }

  /// Called when the current route has been popped off.
  @mustCallSuper
  @override
  void didPop() {
    
    viewModel?.routingDidPop();
  }

  /// Called when a new route has been pushed, and the current route is no
  /// longer visible.
  @mustCallSuper
  @override
  void didPushNext() {
    
    viewModel?.routingDidPushNext();
  }

  @mustCallSuper
  @override
  void dispose() {
    routeObserver.unsubscribe(this);
    
    viewModel?.dispose();
    viewModel = null;
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider.value(
      value: viewModel,
      child: Consumer<T>(builder: widget.build),
    );
  }
}

```

now pay attention to the state implementation of `BaseViewState`, see this code
```
T? viewModel = getIt<T>();
```
this section is when the viewModel are injected to the view. so whenever you create a `Screen` extending to `BaseView` and you pump the `ViewModel`, then the `ViewModel` will become available for that view.

## now, what kind of view model can be injected to view.
see this code : 
```
class BaseView<T extends BaseViewModel>
```
this code saying that the Type of view model can be injected to view is only a view model that extend to `BaseViewModel`

## here is the BaseViewModel looks like.
```
abstract class BaseViewModel extends ChangeNotifier{
  BaseViewModel();

  /// This method is executed exactly once for each State object Flutter's
  ///  framework creates.
  void init() {}

  ///  This method is executed whenever the Widget's Stateful State gets
  /// disposed. It might happen a few times, always matching the amount 
  /// of times `init` is called.
  void dispose();

  /// Called when the top route has been popped off, and the current route
  /// shows up.
  void routingDidPopNext() {}

  /// Called when the current route has been pushed.
  void routingDidPush() {}

  /// Called when the current route has been popped off.
  void routingDidPop() {}

  /// Called when a new route has been pushed, and the current route is no
  /// longer visible.
  void routingDidPushNext() {}
}
```

Pay attention to here 
```
abstract class BaseViewModel extends ChangeNotifier
```

this saying that all the viewModel we create that extending `BaseViewModel` is a `ChangeNotifier` type. this make our **View Model State** able to be subscribed by other object, and any changes on our viewModel we can notify those changes to the viewModel subscriber.

## So Where the provider scope are declared?
now get back to the `BaseView` class, and you'll find this section code.
```
@override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider.value(
      value: viewModel,
      child: Consumer<T>(builder: widget.build),
    );
  }
```
whenever the `view` Extend to `BaseView` build the widget, the `BaseView` will first declare the provider first, so all the widget here..
`child: Consumer<T>(builder: widget.build)` will have access to the viewModel state.

#Where Are We Now? how about the repositories?#
remember this ?
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5emr2ja31rd9tth3ot72.png) 
nah, up until here.. now we have this diagram.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ncihpt103kzs6q3lnve.png)

##so now how about the repositories?
remember we would like to make the repositories as a singleton object, so whoever use the repositories, the object will remain single in our thread but available to many view models like this.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x5f2iyl7wsziblcau2by.png)
by the diagram above, it saying.. no matter how many screen are open with its own viewModel. and all those view model are using the Repositories(A) or Repositories(B) the Repositories itself remain only one object in our memory. thats why we have to told to our **DI** to make our repository as `@singleton`...[Here is the link explain more about the singleton](https://betterprogramming.pub/what-is-a-singleton-2dc38ca08e92)


 ##Back to our app, 
as you can see previously on the `TaskRepositoryImpl`
the declaration is like this
```
@singleton 
class TaskRepositoryImpl implements ITaskRepository
```
this should make our TaskRepository as a singleton object, so in our case.. whenever `MainScreenViewModel` require `TaskRepositoryImpl` as the code bellow :
```
final ITaskRepository _taskRepository = getIt<TaskRepositoryImpl>();
```
our **DI** will return the existing object of the repository in the memory..


 #Fine, Now how to run the code? 
well, talking about the [Injectable Package](https://pub.dev/packages/injectable#setup) all those repositories and viewmodel with 
 `@injectable` and `@singleton` will actually generating implementation file before the flutter itself compiling.. so before we run our application, whenever you create new class with injectable annotation. you have to run this command
`fvm flutter pub run build_runner build --delete-conflicting-outputs`
you have to ask the build runner to create the implementation file.


# Well, What is next?
oke, we haven't talk about how the repositories can communicate with the rest API?.. 
lets discuss about it [MVVM With Flutter (Part2)](https://dev.to/noveriojoee/mvvm-with-flutter-part2freeze-48m9)

until then, you can enjoy the code [Here..](https://github.com/noveriojoee/what_to_do_app_flutter)


Thanks Guys, hope this help for you to start the project with flutter.
P.S This are my codebaseline whenever i start a new project feel free to clone it.

Next : 
[MVVM With Flutter part 2](https://dev.to/noveriojoee/mvvm-with-flutter-part2freeze-48m9)
[How To Use Flutter with Freeze](https://dev.to/noveriojoee/flutter-how-to-use-freeze-3oba)
[How To Use Flutter with Injectable](https://dev.to/noveriojoee/flutter-how-to-use-injectable-4oag)