mvp 在 flutter 中的应用
在 Android 應用程序開發過程中,我們經常會用到一些所謂的架構方法,如:mvp,mvvm,clean等。之所以這些方法會被推崇是因為他們可以大大的解耦我們的代碼的功能模塊,讓我們的代碼在項目中后期更容易擴展和維護。
我個人比較推薦 mvp,主要是因為其相對比較簡單且易上手,這次我將給大家介紹如何在 Flutter 中使用 mvp 來組織項目的功能模塊。為了演示方便,我選擇了一個比較簡單的通訊錄列表來為大家做演示。
MVP
首先需要準備 mvp 鼎鼎有名的兩個類:IView和IPrensenter,其中 IView 用于約束視圖的行為,IPresenter 則用于與 IView 進行交互,為其提供除了 UI 行為的其他邏輯處理,如網絡請求,數據庫查詢等操作。
這里我們首先使用 IntelliJ 新建一個名為 flutter_mvp 的項目,接著在 lib 目錄下新建 mvp.dart 文件,文件內容如下:
abstract class IView<T> {setPresenter(T presenter); }abstract class IPresenter{init(); }對,這兩個類就是如此簡單。
數據源
首先我們不急著寫 UI 代碼,先保持 main.dart 文件不變。我們首先要定義一個 Contact 類,用于表示通訊錄中的每一項,接著還要定義一個數據倉庫接口 ContactRepository ,用于獲取數據,代碼如下:
import 'dart:async';class Contact {final String fullName;final String email;const Contact({this.fullName,this.email}); }abstract class ContactRepository{Future<List<Contact>> fetch(); }其中 Contact 有兩個字段 fullName 和 email 。ContactRepository 有一個 fetch 方法,用于獲取通訊錄列表。
既然定義了 ContactRepository 接口,接下來編寫它的實現類 MockContactRepository ,新建文件 contact_data_impl.dart ,其內容如下:
import 'dart:async'; import 'contact_data.dart'; import 'package:flutter/services.dart'; import 'dart:convert'; class MockContactRepository implements ContactRepository{@overrideFuture<List<Contact>> fetch() {return new Future.value(kContacts);} }const kContacts = const<Contact>[const Contact(fullName: "Li bai",email: "libai@live.com"),const Contact(fullName: "Cheng yaojin",email: "chengyaojin@live.com"),const Contact(fullName: "Mi yue",email: "miyue@live.com"),const Contact(fullName: "A ke",email: "ake@live.com"),const Contact(fullName: "Lu ban",email: "luban@live.com"),const Contact(fullName: "Da qiao",email: "daqiao@live.com"),const Contact(fullName: "Hou yi",email: "houyi@live.com"),const Contact(fullName: "Liu bei",email: "liubei@live.com"),const Contact(fullName: "Wang zhaojun",email: "wangzhaoju@live.com"),];MockContactRepository 的功能就是在前期提供測試的假數據。
約束
接著是比較重要的環節,為通訊錄功能編寫約束,約束的內容為 IView 和 IPresenter。新建 contract.dart 文件,內容如下:
import 'package:flutter_mvp/mvp.dart'; import 'package:flutter_mvp/contact/data/contact_data.dart';abstract class Presenter implements IPresenter{loadContacts(); }abstract class View implements IView<Presenter>{void onLoadContactsComplete(List<Contact> items);void onLoadContactsError(); }這里我們給我們的通訊錄定義了屬于自己的兩個約束 Presenter 和 View,其中 Presenter 提供一個 loadContacts 方法,用于加載數據。View 提供了 onLoadContactsComplete 方法,用于更新界面;onLoadContactsError 用于界面的錯誤處理。
Presenter 的實現
接下來我們首先實現 Presenter 接口,新建文件 contact_presenter.dart文件,文件內容如下:
import 'package:flutter_mvp/contact/contract.dart'; import 'package:flutter_mvp/contact/data/contact_data.dart'; import 'package:flutter_mvp/contact/data/contact_data_impl.dart';class ContactPresenter implements Presenter{View _view;ContactRepository _repository;ContactPresenter(this._view){_view.setPresenter(this);}@overridevoid loadContacts(){assert(_view!= null);_repository.fetch().then((contacts){_view.onLoadContactsComplete(contacts);}).catchError((error){print(error);_view.onLoadContactsError();});}@overrideinit() {_repository = new MockContactRepository();} }該 Presenter 在構造方法中初始化自己的 _view 字段,并且調用 _view 的 setPresenter 方法,為其注入了 presenter 對象。這樣一來 View 和 Presenter 兩者就綁定到了一起。接著在 init 方法中初始化了 _repository 對象。
這里的重點是 loadContacts 方法,它會調用 _repository 的 fetch 方法來獲取數據,當拿到數據后調用 _view 的 onLoadContactsComplete 方法來更新 UI。
View 的實現
最后就是我們的 UI 部分了,這里新建文件 contact_page.dart ,其內容如下:
import 'package:flutter/material.dart'; import 'package:flutter_mvp/contact/data/contact_data.dart'; import 'package:flutter_mvp/contact/contact_presenter.dart'; import 'package:flutter_mvp/contact/contract.dart'; class ContactsPage extends StatelessWidget{@overrideWidget build(BuildContext context) {return new Scaffold(appBar: new AppBar(title: new Text("Contacts"),),body: new ContactList());} }class ContactList extends StatefulWidget{ContactList({ Key key }) : super(key: key);@override_ContactListState createState(){_ContactListState view = new _ContactListState();ContactPresenter presenter = new ContactPresenter(view);presenter.init();return view ;} }class _ContactListState extends State<ContactList> implements View {List<Contact> contacts = [];ContactPresenter _presenter;@overridevoid initState() {super.initState();_presenter.loadContacts();}Widget buildListTile(BuildContext context, Contact contact) {return new MergeSemantics(child: new ListTile(isThreeLine: true,dense: false,leading: new ExcludeSemantics(child: new CircleAvatar(child: new Text(contact.fullName.substring(0,1)))) ,title: new Text(contact.fullName),subtitle: new Text(contact.email),),);}@overrideWidget build(BuildContext context) {Widget widget ;widget = new ListView.builder(padding: new EdgeInsets.symmetric(vertical: 8.0),itemBuilder: (BuildContext context, int index){return buildListTile(context,contacts[index]);},itemCount: contacts.length,);return widget;}@overridevoid onLoadContactsComplete(List<Contact> items) {setState((){contacts = items;print(" contacts size ${contacts.length}");});}@overridevoid onLoadContactsError() {}@overridesetPresenter(Presenter presenter) {_presenter = presenter;} }這段代碼有些長,我們分段來看。
首先是類 ContactsPage ,它主要用于提供 UI 上的 AppBar 和 body。其中 body 為 ContactList 就是我們的通訊錄列表。
接著看 ContactList ,其 createState 方法如下:
@override_ContactListState createState(){_ContactListState view = new _ContactListState();ContactPresenter presenter = new ContactPresenter(view);presenter.init();return view ;}首先是初始化了通訊錄的 UI 類 _ContactListState,接著初始化了 ContactPresenter ,并將 _ContactListState 傳入其中。最后調用了 Presenter 的 init 方法來初始化 Presenter。
接下來就是 _ContactListState 類了,通訊錄列表就是由它構建的。UI 相關代碼不多說,這里主要看 initState 方法,在其中調用了 Presenter 的 loadContacts 方法來加載數據。當 Presenter 加載完數據后會調用 _ContactListState 的 onLoadContactsComplete 方法來更新 UI 。
最后運行結果如下:
使用真是數據
在上面我們使用的是 MockContactRepository 提供的假數據,接著我們定義一個 HttpContactRepository 來從網絡上加載數據,在 contact_data_impl 添加 HttpContactRepository 類,
const String kContactsUrl = "http://o6p4e1uhv.bkt.clouddn.com/contacts.json";class HttpContactRepository implements ContactRepository{@overrideFuture<List<Contact>> fetch() async{var httpClient = createHttpClient();var response = await httpClient.get(kContactsUrl);var body = response.body;List<Map> contacts = JSON.decode(body)['contacts'];return contacts.map((contact){return new Contact(fullName: contact['fullname'],email: contact['email']);}).toList();} }為了 HttpContactRepository 和 MockContactRepository 切換翻遍,另外增加 RepositoryType 和 Injector 兩個類,
enum RepositoryType{mock,http }class Injector{ContactRepository getContactRepository(RepositoryType type){switch(type){case RepositoryType.mock:return new MockContactRepository();default:return new HttpContactRepository();}}}其中 Injector 用于管理外界對 ContactRepository 的依賴。
最終 contact_data_impl 文件內容如下:
import 'dart:async'; import 'contact_data.dart'; import 'package:flutter/services.dart'; import 'dart:convert'; class MockContactRepository implements ContactRepository{@overrideFuture<List<Contact>> fetch() {return new Future.value(kContacts);} }class HttpContactRepository implements ContactRepository{@overrideFuture<List<Contact>> fetch() async{var httpClient = createHttpClient();var response = await httpClient.get(kContactsUrl);var body = response.body;List<Map> contacts = JSON.decode(body)['contacts'];return contacts.map((contact){return new Contact(fullName: contact['fullname'],email: contact['email']);}).toList();} }enum RepositoryType{mock,http }class Injector{ContactRepository getContactRepository(RepositoryType type){switch(type){case RepositoryType.mock:return new MockContactRepository();default:return new HttpContactRepository();}}}const String kContactsUrl = "http://o6p4e1uhv.bkt.clouddn.com/contacts.json";const kContacts = const<Contact>[const Contact(fullName: "Li bai",email: "libai@live.com"),const Contact(fullName: "Cheng yaojin",email: "chengyaojin@live.com"),const Contact(fullName: "Mi yue",email: "miyue@live.com"),const Contact(fullName: "A ke",email: "ake@live.com"),const Contact(fullName: "Lu ban",email: "luban@live.com"),const Contact(fullName: "Da qiao",email: "daqiao@live.com"),const Contact(fullName: "Hou yi",email: "houyi@live.com"),const Contact(fullName: "Liu bei",email: "liubei@live.com"),const Contact(fullName: "Wang zhaojun",email: "wangzhaoju@live.com"),];最后需要改動的地方是 ContactPresenter 類的 init 方法,
@overrideinit() {_repository = new Injector().getContactRepository(RepositoryType.mock);}這樣就能方便對真是數據和測試數據做切換了。
總結
看到這,是不是覺得 mvp 還是比較簡單的,其關鍵就是對 View 和Presenter 的定義和實現。另外如果對 mvp 還是不很熟悉的可以多在網上找些資料。
如果需要上述代碼,可以在https://github.com/flutter-dev/flutter-mvp 下載。
最后做一下廣告,我們的 Flutter 中文開發者論壇已經上線了,如果你對 Flutter 感興趣的話可以前往 flutter-dev.cn/bbs 或 flutter-dev.com/bbs 與大家一起討論和學習 。
轉載于:https://www.cnblogs.com/zhujiabin/p/10120641.html
總結
以上是生活随笔為你收集整理的mvp 在 flutter 中的应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nginx安装和基础代理配置
- 下一篇: hive建表映射到hbase