26

在 Flutter 中使用 MVP 架构

 5 years ago
source link: http://caimuhao.com/2018/08/29/Flutter-MVP-Architecture/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在 Android 开发中有很多设计模式,从 MVC 到 MVP MVVM 等,而在 Flutter 中也可是使用 MVP 模式进行开发,在这篇文章中我们来看一下在 Flutter 中如何使用 MVP 模式开发应用.

MVP 模式主要包含三个部分

  • UI 层包含所有我们需要的 Widgets
  • Presenters 将连接 UI 层和数据层
  • Data 层包含所有我们的数据操作

最终的代码可以在这个仓库中获得 FlutterMvpArc

Data Layer

我们先来创建数据层,在 Flutter 项目的 lib 目录创建 data 目录,然后创建 contact_data.dart 文件,在这个文件中我们写入下面的代码:

import 'dart:async';

class Contact {
  final String fullName;
  final String email;

  const Contact({this.fullName, this.email});

  Contact.fromMap(Map<String, dynamic> map)
      : fullName = "${map['name']['first']} ${map['name']['last']}",
        email = map['email'];
}

abstract class ContactRepository {
  Future<List<Contact>> fetch();
}

class FetchDataException implements Exception {
  String _message;

  FetchDataException(this._message);

  @override
  String toString() {
    return "Exception:$_message";
  }
}

在上面的代码中我们首先引入了 dart 异步执行库,然后创建了 Contact 类, ContactRepository 接口,这个借口定义了 fetch 方法用来获取数据,最后自定义了 FetchDataException 异常.

Mock Repository

现在我们来创建第一个 ContactRepository 接口实现类,在 data 目录添加一个文件 contact_data_mock.dart ,这个类实现了 ContactRepository 接口,然后实现了 fetch 方法,返回我们模拟的数据.

import 'dart:async';
import 'contact_data.dart';

class MockContactRepository implements ContactRepository {
  @override
  Future<List<Contact>> fetch() => Future.value(kContacts);
}

const kContacts = const <Contact>[
  const Contact(
      fullName: 'Romain Hoogmoed', email: '[email protected]'),
  const Contact(fullName: 'Emilie Olsen', email: '[email protected]')
];

Random User Repository

我们的第二个 ContactRepository 实现类是 RandomUserRepository , 它将从网络获取数据;

在 data 目录我们创建一个 contact_data_impl.dart 文件,然后添加下面的代码:

import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'contact_data.dart';

class RandomUserRepository implements ContactRepository {
  static const _kRandomUserUrl = 'http://api.randomuser.me/?results=15';
  final JsonDecoder _decoder = new JsonDecoder();

  @override
  Future<List<Contact>> fetch() {
    return http.get(_kRandomUserUrl).then((http.Response response) {
      final String jsonBody = response.body;
      final statusCode = response.statusCode;

      if (statusCode < 200 || statusCode >= 300 || jsonBody == null) {
        throw new FetchDataException(
            "Error while getting contacts [StatusCode:$statusCode, Error:${response.toString()}]");
      }

      final contactsContainer = _decoder.convert(jsonBody);
      final List contactItems = contactsContainer['results'];

      return contactItems
          .map((contactRaw) => new Contact.fromMap(contactRaw))
          .toList();
    });
  }
}

为了使用网络请求,我们先引入了 package:flutter/http.dart 包.在这个类的fetch方法中,我们执行了一个 get 请求,当数据获取成功时,我们将取出请求中的结果,将数据转换成 Future<List<Contact>> 类型.

当数据获取成功时,Json 数据是这样的:

{
 “results”: [
   {
     “gender”: “female”,
     “name”: {
        “title”: “mrs”,
        “first”: “aubrey”,
        “last”: “ennis”
     },
     “email”: “[email protected]”,
   }
 ]
}

Dependency Injection

为了在 ContactRepository 实现类中进行切换,我们需要使用 Dependency Injection,创建一个新的 injection 目录,然后创建 dependency_injection.dart 文件,添加下面的代码:

import '../data/contact_data.dart';
import '../data/contact_data_impl.dart';
import '../data/contact_data_mock.dart';

enum Flavor { MOCK, PRO }

class Injector {
  static final Injector _singleton = new Injector._internal();
  static Flavor _flavor;

  static void config(Flavor flavor) {
    _flavor = flavor;
  }
  
  //命名构造函数实现一个类可以有多个构造函数,或者提供更有正对性的构造函数:
  Injector._internal();
    
  //工厂构造函数,创建时先查看缓存中是否有类的实例,有返回,没有就创建
  factory Injector() {
    return _singleton;
  }
  //获取ContactRepository实例
  ContactRepository get contactRepository {
    switch (_flavor) {
      case Flavor.MOCK:
        return new MockContactRepository();
      case Flavor.PRO:
        return new RandomUserRepository();
      default:
        return new MockContactRepository();
    }
  }
}

Presenter

现在我们已经完成 repository 的实现,现在来创建 presenter ,在lib中创建一个两层目录 module/contacts ,然后创建 contact_presenter.dart 文件,然后添加下面的代码:

import '../../data/contact_data.dart';
import '../../injection/dependency_injection.dart';

abstract class ContactListViewContract {
  void onLoadContactsComplete(List<Contact> items);

  void onLoadContactsError();
}

class ContactListPresenter {
  ContactListViewContract _view;
  ContactRepository _repository;

  ContactListPresenter(this._view){
      _repository= Injector().contactRepository;
  }

  void loadContacts() {
    assert(_view != null);

    _repository
        .fetch()
        .then((contacts) => _view.onLoadContactsComplete(contacts))
        .catchError((onError) => _view.onLoadContactsError());
  }
}

首先,我们创建了 ContactListViewContract 接口,他将帮助我们连接 UI 层和 Presenter 层.我们定义了两个方法,分别是数据加载成功和失败的接口.

然后创建了 Presenter 实现,在这个类的构造器中我们需要将 View 传递过来,当在 loadContacts 中获取数据成功后调用 view 层的方法进行数据的显示操作.

View

现在我们 module/contacts 文件夹中创建 contact_view.dart 文件,来显示我们的界面.代码如下:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import '../../data/contact_data.dart';
import 'contact_presenter.dart';

class ContactsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: AppBar(title: Text("Contacts")),
      body: ContactList(),
    );
  }
}

class ContactList extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _ContactListState();
  }
}

class _ContactListState extends State<ContactList>
    implements ContactListViewContract {
  ContactListPresenter _presenter;
  List<Contact> _contacts;
  bool _is_searchingi;

  _ContactListState() {
    _presenter = new ContactListPresenter(this);
  }

  @override
  void initState() {
    super.initState();
    _is_searchingi = true;
    _presenter.loadContacts();
  }

  @override
  Widget build(BuildContext context) {
    Widget widget;

    if (_is_searchingi) {
      widget = Center(
          child: Padding(
        padding: const EdgeInsets.only(left: 16.0, right: 16.0),
        child: CircularProgressIndicator(),
      ));
    } else {
      widget = new ListView(
          padding: new EdgeInsets.symmetric(vertical: 8.0),
          children: _buildContactList());
    }

    return widget;
  }

  @override
  void onLoadContactsComplete(List<Contact> items) {
    setState(() {
      _contacts = items;
      _is_searchingi = false;
    });
  }

  @override
  void onLoadContactsError() {
    // TODO: implement onLoadContactsError
  }

  List<_ContactListItem> _buildContactList() {
    return _contacts.map((contact) => new _ContactListItem(contact)).toList();
  }
}

class _ContactListItem extends ListTile {
  _ContactListItem(Contact contact)
      : super(
            title: new Text(contact.fullName),
            subtitle: new Text(contact.email),
            leading: new CircleAvatar(child: new Text(contact.fullName[0])));
}

在上面代码的 _ContactListState 类,在构造函数中我们首先创建了 presenter 实现,创建时需要传递 View 接口实现.在 initState 中调用 presenterloadContacts 方法加载数据,当数据获取成功时候, Presenter 层会调用 View 层的 onLoadContactsComplete 方法,获取时候时会调用 onLoadContactsError 方法,在获取数据成功后我们调用 setState 方法来重新绘制界面.


Recommend

  • 0

    10 Tips for Building a Successful MVP with Flutter10 Tips for Building a Successful MVP with FlutterAugust 21st 2023 New Story7...

  • 5
    • www.androidchina.net 2 years ago
    • Cache

    看完不会写MVP架构我跪搓板

    6. MVP示例 6.1 添加依赖 compile 'cn.finalteam:okhttpfinal:2.0.7' 6.2 初始化okhttpfinal public class ...

  • 4

    层次架构风格从之前的两层C/S到三层C/S,然后演化为三层B/S架构,三层B/S架构之后仍然在往后面演化,我们来看一下层次架构演化过程中都有了哪些演化的架构风格呢?而我们先简单了解一下之前的层次架构风格中分层的各个层次的作用。

  • 38

  • 71
    • 微信 mp.weixin.qq.com 6 years ago
    • Cache

    Android MVP升级路(二)时尚版

    Android MVP升级路(二)时尚版 Original JesseBraveMan...

  • 54
    • www.chanpin100.com 6 years ago
    • Cache

    五个步骤,实现MVP!

    关闭

  • 112
    • Github github.com 6 years ago
    • Cache

    GitHub - omisego/plasma-mvp

    Notice! This is an old research repo. No active work is being done here. Efforts in the direction of production-ready MVP plasma chain (MoreVP, ERC20, audits) are in ht...

  • 76
    • 掘金 juejin.im 6 years ago
    • Cache

    MVP模式的经典封装

    人之所以能,是相信能。 说到MVP,大家应该都不陌生了,由于其高度解等等优点,越来越多的项目使用这个设计模式。然而,优点虽在,缺点也不少,其中一个就是类多了很多,而且V与P直接要项目通信,那么P就得持有V得实例,但如果活动挂掉了,如果没有对V进行释放,还...

  • 89
    • 掘金 juejin.im 6 years ago
    • Cache

    MVP那些事儿(6)MVC转化为MVP(上)

    目录 MVP那些事儿(1)……用场景说话 MVP那些事儿(2)……MVC架构初探 MVP那些事儿(3)……在Android中使用MVC(上) MVP那些事儿(4)……在Android中使用MVC(下) MVP那些事儿(5)……中介者模式与MVP的关系 MVP那

  • 38
    • logohub.io 5 years ago
    • Cache

    LogoHub | MVP Logo Generator

    MVP Logo Generator

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK