A Beginner’s Guide To Implementing MVVM Architecture In Flutter
Model View ViewModel (MVVM) is the industry-recognized software architecture pattern that overcomes all drawbacks of MVP and MVC design patterns.
Join the DZone community and get the full member experience.
Join For FreeThere was a time in the tech space, where businesses and companies struggled to develop an app for their idea. This was primarily because they had to develop the same idea twice as applications – one for the Android ecosystem another one for the iOS ecosystem.
Financially and logistically, this was straining as companies had to hire two dedicated teams to work on the app development process or increase their time to market by hiring one team and waiting until one of the applications got developed for the other one to start.
All of this changed in May 2017, when Google rolled out a revolutionary language that allowed developers to develop an app just once and use it across multiple platforms. That platform is what we call Flutter and Flutter application development has been a fan favorite among the developer community.
Though there are tons of features to rave about with Flutter application development, what appears to be the most prominent feature is its MVVM architecture.
As an experienced developer at a mobile application developing company, I have penned this article to briefly look at the model and understand how to implement MVVM architecture in Flutter.
What Is MVVM Architecture?
The MVVM pattern provides the distribution of data and UI which gives benefits such as flexibility and reusability of the code as well as data.
MVVM stands for Model, View, and View Model. The design pattern of MVVM is to build a view model that can represent the data through a view.
MVVM and Architecture
In the above diagram of MVVM, you could see that the view model occupies the central position, which sends and receives data from the model and provides data to the view. It observes the changes in the data and responds accordingly using the model to the view.
The Model
The Model in the MVVM presents real-time data that will be used in application development. The Model only reflects the actual data and not the characteristics or any feature related to the application.
It means you can’t manipulate the way the data will be represented or formatted. Each item in the dataset will be presenting its own model when the data is fetched. The main Model is kept away from the logic part for neat code but sometimes, it includes the validation logic as well.
The View
The View in the MVVM design pattern presents the user interacting with the application. The View is a set of libraries to present the data more accurately and appropriately.
The View possesses the property to utilize behaviors associated with the Model such as identifying and acting according to user input. The View handles the actions done by a user, which can be manipulated by the Model’s functionalities.
The View Model
The most essential element of MVVM architecture is that the View Model occupies a central position that sends and receives the data from the model and provides the data to the View. Besides, it also observes the data changes and responds accordingly using the model to the view.
The View Model holds the actual data and the View part holds the formatted or updated data while keeping the controller as an interface between them.
MVVM Implementation
MVV implementation is simple. All you have to do is follow the steps below:
- Create a new project.
- Get dependencies
- Create the model.
- Create UI.
- Create a web service.
- Create a view model.
- Create a widget.
- Create main.dart.
Create a New Project
Create a new Flutter app. Create a New mvvm_example and remove all the code as we will add a custom main.dart.
Get Dependencies
Add the latest version of HTTP/dio and Provider package under the dependencies in pubspec.yaml file.
1
2
3
4
5
|
dependencies:
dio: ^4.0.0 provider: ^5.0.0 cached_network_image: ^3.0.0 |
Note: Use the latest dependencies version.
Create the Model
We will be building the latest News Article app, where the model will be the NewsArticle. In your “lib” folder, create a new folder and name its models. Inside the model folder, create a dart file and name it “NewsArticle.dart”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
class NewsArticle { String author; String title; String description; String url; String urlToImage; String publishedAt; String content;
NewsArticle( {this.author, this.title, this.description, this.url, this.urlToImage, this.publishedAt, this.content});
NewsArticle.fromJson(Map<String, dynamic> json) { author = json['author']; title = json['title']; description = json['description']; url = json['url']; urlToImage = json['urlToImage']; publishedAt = json['publishedAt']; content = json['content']; } }
|
Create UI
Create a new folder in your lib folder and name it “screens.” Create a “dart” file and name it “NewsScreen.dart”. Enter the following code in it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import 'package:demo_mvvm/viewmodels/news_article_list_view_model.dart'; import 'package:demo_mvvm/widgets/news_grid.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart';
class NewsScreen extends StatefulWidget { @override _NewsScreenState createState() => _NewsScreenState(); }
class _NewsScreenState extends State<NewsScreen> { @override void initState() { super.initState(); Provider.of<NewArticleListViewModel>(context, listen: false).topHeadlines(); }
@override Widget build(BuildContext context) { var listviewModel = Provider.of<NewArticleListViewModel>(context); return Scaffold( appBar: AppBar( title: Text('Top News HeadLine'), ), body: NewsGrid(articles: listviewModel.article), ); } }
|
Create a Web Service
Create a new folder in your lib folder and name it “services.” Create a “dart” file and name it “WebServices.dart”. For the web service, we will use an API endpoint to newsapi.org.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import 'package:dio/dio.dart'; import '../models/news_article.dart';
class WebServices { var dio = new Dio();
Future<List<NewsArticle>> fetchTopHeadLines() async { String baseUrl = "https://newsapi.org/v2/top-headlines?country=in&apiKey=9e72a381576143e48ff8c07b8d2026f4";
final response = await dio.get(baseUrl);
if (response.statusCode == 200) { final result = response.data; Iterable list = result['articles']; return list.map((article) => NewsArticle.fromJson(article)).toList(); } else { throw Exception("Failed to get top new"); } } }
|
Create a View Model
In your “lib” folder, create another folder and name it “viewmodels”. In this folder, we will create two files: one will be the “NewsArticleViewModel,” which will have the following content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import '../models/news_article.dart';
class NewsArticleViewModel { NewsArticle _newsArticle;
NewsArticleViewModel({NewsArticle article}) : _newsArticle = article;
String get title { return _newsArticle.title; }
String get author { return _newsArticle.author; }
String get description { return _newsArticle.description; }
String get url { return _newsArticle.url; }
String get urlToImage { return _newsArticle.urlToImage; }
String get publishedAt { return _newsArticle.publishedAt; }
String get content { return _newsArticle.content; } }
|
Create a new class called “NewsArticleViewModel” that will take in the model “NewsArticle” as a parameter and return the values from that model inside the View Model.
The next file will be NewArticleListViewModel” and will have the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
import 'package:demo_mvvm/models/news_article.dart'; import 'package:demo_mvvm/services/web_services.dart'; import 'package:demo_mvvm/viewmodels/news_article_news_model.dart'; import 'package:flutter/material.dart';
enum LoadingStatus { completed, searching, empty }
class NewArticleListViewModel with ChangeNotifier { LoadingStatus loadingStatus = LoadingStatus.empty; List<NewsArticleViewModel> article;
void topHeadlines() async { List<NewsArticle> newsArticle = await WebServices().fetchTopHeadLines(); loadingStatus = LoadingStatus.searching; notifyListeners();
this.article = newsArticle .map((article) => NewsArticleViewModel(article: article)) .toList();
if (article.isEmpty) { loadingStatus = LoadingStatus.empty; } else { loadingStatus = LoadingStatus.completed; }
notifyListeners(); } }
|
Create a Widget.
Widget creator will be inside a new folder. We will create a widget in the “lib” folder. Inside this folder, create a file and name it “NewsGrid.dart”. This is just to continue with the MVVM reusability of the code. Here is the code for it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
import 'package:cached_network_image/cached_network_image.dart'; import 'package:demo_mvvm/viewmodels/news_article_news_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart';
class NewsGrid extends StatelessWidget { final List<NewsArticleViewModel> articles;
NewsGrid({this.articles});
@override Widget build(BuildContext context) { return GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, ), itemBuilder: (context, index) { var article = this.articles[index]; return GridTile( child: Container( child: CachedNetworkImage( imageUrl: article.urlToImage, imageBuilder: (context, imageProvider) { return Container(
margin: EdgeInsets.symmetric(horizontal: 10, vertical: 20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(25), image: DecorationImage( image: imageProvider, fit: BoxFit.cover, ), ), ); }, placeholder: (context, url) { return Center( child: CircularProgressIndicator(), );
}, errorWidget: (context, url, error) { return Container( child: Center( child: Text( 'No Image Found', style: TextStyle(fontWeight: FontWeight.bold), ), ), ); }, ), ), footer: Container( padding: EdgeInsets.symmetric(horizontal: 15), child: Text( article.title, style: TextStyle(fontWeight: FontWeight.bold), maxLines: 1, ), ), ); }, itemCount: this.articles.length, ); } }
|
Create main.dart
Finally, we will create the main.dart file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
import 'package:demo_mvvm/screens/news_screen.dart'; import 'package:demo_mvvm/viewmodels/news_article_list_view_model.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter/services.dart';
void main() { SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( statusBarColor: Colors.blue, statusBarBrightness: Brightness.light)); runApp(MyApp()); }
class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ),
home: MultiProvider( providers: [ ChangeNotifierProvider( create: (_) => NewArticleListViewModel(), ) ], child: NewsScreen(), ), ); } }
|
ChangeNotifierProvider is defined to listen to a ChangeNotifier to expose the instance to its rebuild dependents whenever ChangeNotifier.notifyListeners is called. The NewsArticleViewModel(data) is of type NewsArticle, an instance of which will be provided at the top-level widget in main.dart
The parent widget is wrapped in ChangeNotifierProvider with the datatype of the ChangeNotifier NewsArticleViewModel (Data) class. This includes a builder method that returns an instance of the ChangeNotifier ‘NewsArticleViewModel(data)’ class, which is used to expose the instance to all the children.
Wrapping Up
So, this is how you implement MVVM architecture in Flutter. Flutter mobile application development is seamless when you take a systematic approach.
I hope this post was resourceful to you!
Published at DZone with permission of Brijesh Patel. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments