FlutterWorld
Published in

FlutterWorld

Flutter: MVVM Architecture

Last Updated: 28th march 2021

Model–View–ViewModel (MVVM) is a very established architectural pattern when it’s come to software development. What is so special about MVVM?

We need one architecture to be placed in the application which communicates between UI and business logic. MVVM is one of them which is able to segregate business logic from UI, this looks easy job to do, but my friend believe me if you missed something in architecture or doing anything inaccurate then be ready for big trouble at a later stage of application development, and eventually you will end up having some code hack in application to do your job.

Here I am going to explain MVVM with a simple example that will give you enough knowledge to implement in your application.

MVVM is useful to move business logic from view to ViewModel and Model. ViewModel is the mediator between View and Model which carry all user events and return back the result.

The key benefit is allowing true separation between the View and Model and the efficiency that you gain from having that. What it means in real terms is that when your model needs to change, it can be changed easily without the view needing to and vice-versa.

There are three key things that flow out of applying MVVM −

  • Maintainability:- The presentation layer and the logic are loosely coupled, due to this code is easily maintainable and reusable. As the code base will increase over the course of time, this will help you to distinguish between them.
  • Testability:- The ViewModel is easier to unit test than code-behind or event-driven code. Thanks to separation logic MVVM have 😀
  • Extensibility:- This architecture gives you assurance which enables code to get extensible over the period of time. but keep in mind it’s also over job to keep component reusable.

So, let's go through each component individually and try to understand the purpose. Please see the diagram while referring to the below points, you will have a clear understanding of flow.

Model

The model represents a single source of truth that carries the real-time fetch data or database-related queries.

This layer can contain business logic, code validation, etc. This layer interacts with ViewModel for local data or for real-time. Data are given in response to ViewModel.

ViewModel

ViewModel is the mediator between View and Model, which accept all the user events and request that to Model for data. Once the Model has data then it returns to ViewModel and then ViewModel notify that data to View.

ViewModel can be used by multiple views, which means a single ViewModel can provide data to more than one View.

View

The view is where the user is interacting with Widgets that are shown on the screen. These user events request some actions which navigate to ViewModel, and the rest of ViewModel does the job. Once ViewModel has the required data then it updates View.

Now, we will go through the example which will demonstrate MVVM architecture, for notifying data we will use the Provider state mechanism.

MediaService.dart

import 'dart:convert';
import 'dart:io';
import 'package:meta/meta.dart';

import 'package:http/http.dart' as http;
import 'package:mvvm_flutter_app/model/apis/app_exception.dart';

class MediaService {
final String _baseUrl = "https://itunes.apple.com/search?term=";

Future<dynamic> get(String url) async {
dynamic responseJson;
try {
final response = await http.get(_baseUrl + url);
responseJson = returnResponse(response);
} on SocketException {
throw FetchDataException('No Internet Connection');
}
return responseJson;
}

@visibleForTesting
dynamic returnResponse(http.Response response) {
switch (response.statusCode) {
case 200:
dynamic responseJson = jsonDecode(response.body);
return responseJson;
case 400:
throw BadRequestException(response.body.toString());
case 401:
case 403:
throw UnauthorisedException(response.body.toString());
case 500:
default:
throw FetchDataException(
'Error occured while communication with server' +
' with status code : ${response.statusCode}');
}
}
}

MediaRepository.dart

import 'package:mvvm_flutter_app/model/media.dart';
import 'package:mvvm_flutter_app/model/services/media_service.dart';

class MediaRepository {
MediaService _mediaService = MediaService();

Future<List<Media>> fetchMediaList(String value) async {
dynamic response = await _mediaService.get(value);
final jsonData = response['results'] as List;
List<Media> mediaList =
jsonData.map((tagJson) => Media.fromJson(tagJson)).toList();
return mediaList;
}
}

MediaViewModel.dart

import 'package:flutter/cupertino.dart';
import 'package:mvvm_flutter_app/model/apis/api_response.dart';
import 'package:mvvm_flutter_app/model/media.dart';
import 'package:mvvm_flutter_app/model/media_repository.dart';

class MediaViewModel with ChangeNotifier {
ApiResponse _apiResponse = ApiResponse.loading('Fetching artist data');

Media _media;

ApiResponse get response {
return _apiResponse;
}

Media get media {
return _media;
}

/// Call the media service and gets the data of requested media data of
/// an artist.
Future<void> fetchMediaData(String value) async {
try {
List<Media> mediaList = await MediaRepository().fetchMediaList(value);
_apiResponse = ApiResponse.completed(mediaList);
} catch (e) {
_apiResponse = ApiResponse.error(e.toString());
print(e);
}
notifyListeners();
}

void setSelectedMedia(Media media) {
_media = media;
notifyListeners();
}
}

HomScreen.dart

import 'package:flutter/material.dart';
import 'package:mvvm_flutter_app/model/apis/api_response.dart';
import 'package:mvvm_flutter_app/model/media.dart';
import 'package:mvvm_flutter_app/view/widgets/player_list_widget.dart';
import 'package:mvvm_flutter_app/view/widgets/player_widget.dart';
import 'package:mvvm_flutter_app/view_model/media_view_model.dart';

import 'package:provider/provider.dart';

class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
@override
void initState() {
// TODO: implement initState
super.initState();
}

@override
Widget build(BuildContext context) {
final _inputController = TextEditingController();
ApiResponse apiResponse = Provider.of<MediaViewModel>(context).response;
List<Media> mediaList = apiResponse.data as List<Media>;
return Scaffold(
appBar: AppBar(
title: Text('Media Player'),
),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
children: <Widget>[
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 20.0),
decoration: BoxDecoration(
color: Theme.of(context).accentColor.withAlpha(50),
borderRadius: BorderRadius.circular(30.0),
),
child: TextField(
style: TextStyle(
fontSize: 15.0,
color: Colors.grey,
),
controller: _inputController,
onChanged: (value) {},
onSubmitted: (value) {
if (value.isNotEmpty) {
Provider.of<MediaViewModel>(context)
.setSelectedMedia(null);
Provider.of<MediaViewModel>(context,
listen: false)
.fetchMediaData(value);
}
},
decoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
prefixIcon: Icon(
Icons.search,
color: Colors.grey,
),
hintText: 'Enter Artist Name',
)),
),
),
],
),
),
mediaList != null && mediaList.length > 0
? Expanded(
child: PlayerListWidget(mediaList, (Media media) {
Provider.of<MediaViewModel>(context)
.setSelectedMedia(media);
}))
: Expanded(
child: Center(
child: Text('Search the song by Artist'),
),
),
if (Provider.of<MediaViewModel>(context).media != null)
Align(
alignment: Alignment.bottomCenter,
child: PlayerWidget(
function: () {
setState(() {});
},
)),
],
));
}
}

Full Repository code available here

MVVM is heavily used nowadays as it supports an event-driven approach, which goes hand in hand as a lot of flutter components are performed based on events.

Full Repository code available here

https://github.com/jitsm555/Flutter-MVVM

--

--

--

The fastest growing community which makes development easier

Recommended from Medium

Advent of Code 2019 — it’s always day 1

Wix Engineering Podcast, E02: Growing Pains

CS371p Fall 2020: Tejna Dasari

What Is The Best URL Classification API For Brand Safety?

Sorting Techniques 2.0

New PHP package for implementing authorization via social media

Scalar DB — Universal transaction manager

Deploy Flask web application on Heroku [Basic] (Part 1)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Jitesh Mohite

Jitesh Mohite

I am technology enthusiastic, want to learn things quickly and dive deep inside it. I always believe in developing logical things which makes impact on end user

More from Medium

How to make Instagram-like post GUI in Flutter

Flutter CI/CD setup using GitHub Actions , fastlane and Firebase App Distribution for Android

Creating a Basic Login App using Flutter

Bringing Life To Your Flutter Application