在 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{ @override
Future<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);
} @override
void loadContacts(){
assert(_view!= null); _repository.fetch().then(
(contacts){
_view.onLoadContactsComplete(contacts);
})
.catchError((error){
print(error);
_view.onLoadContactsError();
}
);
}
@override
init() {
_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{ @override
Widget 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; @override
void 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(,)))) ,
title: new Text(contact.fullName),
subtitle: new Text(contact.email),
),
);
} @override
Widget 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;
}
@override
void onLoadContactsComplete(List<Contact> items) {
setState((){
contacts = items;
print(" contacts size ${contacts.length}");
});
} @override
void onLoadContactsError() {
} @override
setPresenter(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{

  @override
Future<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{ @override
Future<List<Contact>> fetch() {
return new Future.value(kContacts);
}
} class HttpContactRepository implements ContactRepository{ @override
Future<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 方法,

  @override
init() {
_repository = new Injector().getContactRepository(RepositoryType.mock);
}

这样就能方便对真是数据和测试数据做切换了。

总结

看到这,是不是觉得 mvp 还是比较简单的,其关键就是对 View 和Presenter 的定义和实现。另外如果对 mvp 还是不很熟悉的可以多在网上找些资料。

如果需要上述代码,可以在https://github.com/flutter-dev/flutter-mvp 下载。

最后做一下广告,我们的 Flutter 中文开发者论坛已经上线了,如果你对 Flutter 感兴趣的话可以前往 flutter-dev.cn/bbsflutter-dev.com/bbs 与大家一起讨论和学习 。

mvp 在 flutter 中的应用的更多相关文章

  1. mvp在flutter中的应用

    mvp模式的优点mvp模式将视图.业务逻辑.数据模型隔离,使用mvp模式,能使复杂的业务逻辑变得更加清晰,使代码更具有灵活性和扩展性,正是这些优点,使mvp模式广泛应用于原生开发中. flutter使 ...

  2. 在Flutter中嵌入Native组件的正确姿势是...

    引言 在漫长的从Native向Flutter过渡的混合工程时期,要想平滑地过渡,在Flutter中使用Native中较为完善的控件会是一个很好的选择.本文希望向大家介绍AndroidView的使用方式 ...

  3. 从零学习Fluter(五):Flutter中手势滑动拖动已经网络请求

    从六号开始搞Flutter,到今天写这篇blog已经过了4天时间,文档初步浏览了一遍,写下了这个demo.demo源码分享在github上,现在对flutter有种说不出的喜欢了.大家一起搞吧! 废话 ...

  4. Flutter 中 JSON 解析

    本文介绍一下Flutter中如何进行json数据的解析.在移动端开发中,请求服务端返回json数据并解析是一个很常见的使用场景.Android原生开发中,有GsonFormat这样的神器,一键生成Ja ...

  5. Flutter 中文文档网站 flutter.cn 正式发布!

    在通常的对 Flutter 介绍中,最耳熟能详的是下面四个特点: 精美 (Beautiful):充分的赋予和发挥设计师的创造力和想象力,让你真正掌控屏幕上的每一个像素. ** 极速 (Fast)**: ...

  6. 理解 Flutter 中的 Key

    概览 在 Flutter 中,大概大家都知道如何更新界面视图: 通过修改 Stata 去触发 Widget 重建,触发和更新的操作是 Flutter 框架做的. 但是有时即使修改了 State,Flu ...

  7. flutter 中的样式

    flutter 中的样式 样式 值 width 320.0 height 240.0 color Colors.white,Colors.grey[300] textAlign TextAlign.c ...

  8. flutter中的异步机制Future

    饿补一下Flutter中Http请求的异步操作. Dart是一个单线程语言,可以理解成物理线路中的串联,当其遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到 ...

  9. Flutter中管理路由栈的方法和应用

    原文地址:https://www.jianshu.com/p/5df089d360e4 本文首先讲的Flutter中的路由,然后主要讲下Flutter中栈管理的几种方法. 了解下Route和Navig ...

随机推荐

  1. centos 7 初始化脚本

    #!/bin/bash # 时间: 2018-11-21 # 作者: HuYuan # 描述: CentOS 7 初始化脚本 # 加载配置文件 if [ -n "${1}" ];t ...

  2. POJ 1131

    #include <iostream> #include <string> using namespace std; ]; int main() { //freopen(&qu ...

  3. http2.0之头部压缩

    什么是头部压缩?为什么要头部压缩? 我们知道,http请求和响应都是由[状态行.请求/响应头部.消息主题]三部分组成的. 一般而言,消息主体都会经过gzip压缩,或者本身传输的就是压缩过后的二进制文件 ...

  4. C++ STL 初探

    学过C++的人肯定会很熟悉STL标准模板库,STL其实就是封装了一系列的接口,供我们调用.很多函数或者算法的实现不需要我们从头开始写,大大提高我们的编程效率.这篇博客在简单介绍STL的情况下,会详细的 ...

  5. SQLServer覆盖索引

    为了更好地理解覆盖索引,在正式介绍覆盖索引之前,首先稍微来谈一谈有关索引的一些基础知识. 数据页和索引页 在SQLServer中,数据存储的基本单位是页,一页的大小为8KB,分别由页首,数据行和行偏移 ...

  6. Linq基础知识小记四之操作EF

    1.EF简介 EF之于Linq,EF是一种包含Linq功能对象关系映射技术.EF对数据库架构和我们查询的类型进行更好的解耦,使用EF,我们查询的对象不再是C#类,而是更高层的抽象:Entity Dat ...

  7. Python基础语法——(引号、字符串、长字符串、原始字符串、Unicode)

    一.单引号字符串和转义引号 当字符串中出现单引号'时,我们可以用双引号""将该字符串引起来:"Let's go!" 而当字符串中出现双引号时,我们可以用单引号' ...

  8. 分区助手里如何从临近盘(如D盘)抽取一定的空间给已经快满了的盘(如E盘)(博主推荐)(图文详解)

    不多说,直接上干货! 分区助手是什么?(博主推荐)(图文详解) 分区助手各版本比较(图文详解) 分区助手官网使用教程(专业版.绿色版和WinPE版)(图文详解) 安装分区助手时出现“分区助手已安装到你 ...

  9. sql 递归树

    with CTE as ( -->Begin 一个定位点成员 select ID, PersonName,ParentID,cast(PersonName as nvarchar(max)) a ...

  10. OpenGL10-骨骼动画原理篇(2)

    接上一篇的内容,上一篇,简单的介绍了,骨骼动画的原理,给出来一个 简单的例程,这一例程将给展示一个最初级的人物动画,具备多细节内容 以人走路为例子,当人走路的从一个站立开始,到迈出一步,这个过程是 一 ...