- Data Layer
- Mock Repository
- Random User Repository
- Dependency Injection
- Presenter
- View
- Final result
There are many design patterns in Android development, from MVC to MVP MVVM, and MVP is also used in Flutter development. In this article we will look at how to use MVP to develop applications in Flutter.
The MVP mode has three main parts
- The UI layer contains all the Widgets we need
- Presenters will connect the UI layer to the data layer
- The Data layer contains all of our Data operations
The final code is available in this repository for FlutterMvpArc
Data Layer
To create the data layer, create the data directory in the lib folder of the Flutter project. Then create the contact_data.dart file.
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"; }}Copy the code
In the above code we first introduced the Dart asynchronous execution library, then created the Contact class,ContactRepository interface, which defines the FETCH method to fetch data, and finally customized the FetchDataException exception.
Mock Repository
Now let’s create our first ContactRepository interface implementation class in Data The directory adds a file, contact_data_mock.dart, which implements the ContactRepository interface and then the FETCH method, which returns the data we mock.
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]')
];
Copy the code
Random User Repository
Our second ContactRepository implementation class is RandomUserRepository, which will fetch data from the web; In the data directory we create a contact_data_impl. Dart file and add the following code:
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();
});
}
}
Copy the code
In order to use the network request, we first introduce the package: flutter/HTTP. Dart package. In the fetch method of this class, we perform a GET request, and when the data is retrieved, we fetch the result from the request and convert the data to type Future
>.
When the data is retrieved successfully, the Json data looks like this:
{" results ": [{" gender" : "female", "name" : {" title ":" Mrs ", "first" : "aubrey", "last" : "ennis}", "email" : "[email protected]"}}]Copy the code
Dependency Injection
To switch in the ContactRepository implementation class, we need to use Dependency Injection, create a new Injection directory, and then create dependency_injection.dart file and add the following code:
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; } // The named constructor implements that a class can have multiple constructors, or provide a more orthonegative constructor: injector._internal (); // Factory Injector() {return _singleton; // Factory Injector() {return _singleton; ContactRepository get ContactRepository {switch (_flavor) {case Flavor.MOCK: return new MockContactRepository(); case Flavor.PRO: return new RandomUserRepository(); default: return new MockContactRepository(); }}}Copy the code
Presenter
Now that we have completed the repository implementation, we can create presenter, create a two-tier directory module/contacts in lib, then create the contact_presby. dart file, and add the following code:
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()); }}Copy the code
First, we created the ContactListViewContract interface, which will help us connect the UI layer to the Presenter layer. We define two methods, the interfaces for data loading success and failure. We then create the Presenter implementation. In the constructor of this class we need to pass in the View and call the View layer method to display the data when the data is successfully retrieved from loadContacts.
View
Now we create a contact_view.dart file in the Module /contacts folder to display our interface. The code is as follows:
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]))); }Copy the code
In the _ContactListState class above, we first create the Presenter implementation in the constructor, passing in the View interface implementation. Call the Presenter’s loadContacts method in initState to load the data, and the Presenter layer calls the View when the data is retrieved successfully OnLoadContactsComplete method of layer, onLoadContactsError method will be called when obtaining data, and setState method will be called to redraw the interface after obtaining data successfully.