Kiến trúc MVVM trong Flutter – MVVM Pattern Flutter
Kiến trúc MVVM trong Flutter là một trong những pattern được sử dụng nhiều trong quá trình xây dựng ứng dụng Flutter. Để hiểu hơn về cách sử dụng pattern này, mời bạn theo dõi bài viết này.
Kiến trúc MVVM
Viết tắt của Model – View – ViewModel.
Là một mô hình hỗ trợ ràng buộc dữ liệu hai chiều (Two-way data binding ) giữa View và ViewModel.
Cho phép tách biệt dữ liệu (Model), mã thực thi (Logic hay ViewModel ) và giao diện người dùng (View).
Model
Model là thành phần liên kết với cơ sở dữ liệu, trong Model luôn có các hàm truy xuất dữ liệu từ cơ sở dữ liệu.
View
View hay UI hay Presentation Layer là phần giao diện của ứng dụng và nhận tương tác của người dùng.
Một điểm khác biệt của View trong MVVM là : khả năng thực hiện các hành vi và phản hồi lại người dùng thông qua tính năng binding, command.
ViewModel
- Là lớp trung gian giữa View và Model.
- Được xem là một thành phần thay thế lớp Controller trong mô hình MVC.
- ViewModel không biết được các thông tin gì về tầng bên trên của nó, cũng như không biết thông tin gì về View.
- Một ViewModel có thể sử dụng cho nhiều View thông qua binding data, có thể 1 hay 2 chiều tuỳ nhu cầu của ứng dụng.
- Sự khác biệt ở chỗ : Trong ViewModel có chứa các mã lệnh thực hiện Data Binding, Command.
Triển khai kiến trúc MVVM Flutter
Tạo project mới và khai báo thư viện mới nhất của : provider, http
Tạo các folder như bên dưới:
models
Tạo một file model có tên: user.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 31 32 33 34 35 36 37 38 39 |
class User { Name? name; Picture? picture; String? email; String? phone; User({this.name, this.picture, this.email, this.phone}); User.fromJson(Map<String, dynamic> json) : name = new Name.fromJson(json['name']), picture = new Picture.fromJson(json['picture']), email = json['email'], phone = json['phone']; @override String toString() { // TODO: implement toString return phone! + " " + email! + " " + name!.first!; } } class Name { String? last; String? first; Name({this.last, this.first}); Name.fromJson(Map<String, dynamic> json) : last = json['last'], first = json['first']; } class Picture { String? medium; Picture({this.medium}); Picture.fromJson(Map<String, dynamic> json) : medium = json['large']; } |
data_sources
Tạo file api_urls.dart:
1 2 3 |
class ApiUrls{ final Uri API_USER_LIST = Uri.parse('https://api.randomuser.me/?results=50'); } |
Tạo file api_services.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 |
import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:mvvm/models/user.dart'; import 'dart:convert' as json; import 'package:mvvm/resources/utils/fetch_data_exception.dart'; import 'api_urls.dart'; class ApiServices{ Future<List<User>> fetchUser() { return http .get(ApiUrls().API_USER_LIST) .then((http.Response response) { final String jsonBody = response.body; final int statusCode = response.statusCode; if(statusCode != 200 || jsonBody == null){ print(response.reasonPhrase); throw new FetchDataException("StatusCode:$statusCode, Error:${response.reasonPhrase}"); } final JsonDecoder _decoder = new JsonDecoder(); final useListContainer = _decoder.convert(jsonBody); final List userList = useListContainer['results']; return userList.map((contactRaw) => new User.fromJson(contactRaw)).toList(); }); } } |
view_models
Tạo file user_list_view_model.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import 'package:flutter/material.dart'; import 'package:mvvm/data_sources/api_services.dart'; import 'package:mvvm/models/user.dart'; class UserListViewModel with ChangeNotifier{ List<User> userList = []; fetchUserList() async { this.userList = await ApiServices().fetchUser(); notifyListeners(); } } |
views
Tạo file user_list_screen.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 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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:mvvm/data_sources/api_services.dart'; import 'package:mvvm/models/user.dart'; import 'package:mvvm/resources/strings.dart'; import 'package:mvvm/view_models/user_list_view_model.dart'; import 'package:provider/provider.dart'; class UserListScreen extends StatefulWidget { @override _UserListScreenState createState() => _UserListScreenState(); } class _UserListScreenState extends State<UserListScreen> { @override void initState() { // TODO: implement initState super.initState(); Provider.of<UserListViewModel>(context, listen: false).fetchUserList(); } @override Widget build(BuildContext context) { final userListOnProvider = Provider.of<UserListViewModel>(context); return Scaffold( appBar: AppBar( title: Text(USER_LIST), centerTitle: true, ), body: Container( padding: EdgeInsets.only(left: 10), height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.height, child: userListOnProvider.userList.length!= 0 ? ListView.builder( itemCount: userListOnProvider.userList.length, itemBuilder: (BuildContext context, int index) { return UserItem(user: userListOnProvider.userList[index],); } ): Center( child: Container( height: 100, width: 100, child: CircularProgressIndicator(), ), ) ), ); } } class UserItem extends StatelessWidget { User? user; UserItem({this.user}); @override Widget build(BuildContext context) { return new Container( margin: new EdgeInsets.only(top: 20.0), child: new Row( children: <Widget>[ new Container( height: 80.0, width: 80.0, margin: new EdgeInsets.only(right: 20.0), child: new CircleAvatar( backgroundImage: new NetworkImage(user!.picture!.medium!), ), ), new Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ new Text( user!.name!.first! + " " + user!.name!.last!, style: new TextStyle( fontSize: 20.0, color: Colors.black, ), ), new Container( margin: new EdgeInsets.only(top: 10.0), child: new Text( user!.email!, style: new TextStyle( fontSize: 15.0, color: Colors.grey, ), ), ), ], ) ], ), ); } } |
resources
Trong file này chứa các file utils hoặc Widgets, strings , sẽ dùng chung cho toàn bộ dự án.
Bạn có thể xem chi tiết toàn bộ source code ở link Github bên dưới.
SOURCE CODE => Xem ở link Github
Kiến trúc MVVM trong Flutter là một trong những kiến trúc được dùng phổ biến không chỉ trong Flutter mà còn các nền tảng lập trình mobile khác, đặc biệt là native. Trong Flutter , MVVM đã được tích hợp vào kiến trúc BLoc – một kiểu kiến trúc được dùng nhiều trong qúa trình phát triển các ứng dụng từ nhỏ đến lớn.
Cảm ơn bạn đã theo dõi !