Всем добрый денек! Надеюсь после первых трех статей, эта вам покажется не менее полезной.
Сегодня я постараюсь простым языком объяснить MVC паттерн.
И конечно же покажу все на практике!
Поехали!
Наш план
-
Часть 1 — введение в разработку, первое приложение, понятие состояния;
-
Часть 2 — файл pubspec.yaml и использование flutter в командной строке;
-
Часть 3 — BottomNavigationBar и Navigator;
-
Часть 4 (текущая статья) — MVC. Мы будем использовать именно этот паттерн, как один из самых простых;
-
Часть 5 — http пакет. Создание Repository класса, первые запросы, вывод списка постов;
-
Часть 6 — Работа с картинками, вывод картинок в виде сетки, получение картинок из сети, добавление своих в приложение;
-
Часть 7 — Создание своей темы, добавление кастомных шрифтов и анимации;
-
Часть 8 — Немного о тестировании;
Зачем MVC и прочие архитектурные принципы?
Возможно новичкам сначала совсем непонятно, для какой цели использовать архитектурные принципы, ведь без них хорошо.
Зачем все усложнять?
Наиболее веские причины:
-
Сложность кода — когда у вас небольшое приложение с одним или двумя экранами, будь это Flutter или нативное Android / iOS приложение, вы возможно спокойно обойдетесь без понимания принципов архитектуры. Другое дело, когда у проект приличных размеров, вы не сможете обойтесь без единых правил и принципов.
-
Сложность задачи — например: вам необходимо реализовать переключение между 3-мя, 5-ю или даже 10-ю темами (возможно задача не является распространенной). Без четкого понимания архитектуры это так не так просто сделать.
-
Сложность поддержки — если вы разрабатываете огромный коммерческий проект, скажем: Портал какого-либо города, объединенный с различными сервисами (карта, гостиницы и т.д.) вы по крайнее мере должны иметь команду. Каждый член команды должен действовать слаженно. А чтобы действовать слаженно нужно понимать чужой код. Без какого-либо единого подхода в вашей команде возникнет хаос и система потерпит крах.
Это наиболее распространенные причины по моему мнению
Также хорошая архитектура приложения наводит порядок в голове программиста 🙂
В чем суть MVC?
MVC (Model — View — Controller) является довольно старым изобретением и содержит три основных компонента:
-
Модель (Model) представляет собой данные, что и является сутью любого приложения. Само по себе приложение невозможно без данных. Вернемся к примеру из предыдущей главы: список поняшек. В том случае данными являлись пони, которые мы отображали в виде списка. Модель должна обратывать все, что с ней связано (сохранение и манипулирование данными). Ещё модель может иметь отношения (один к одному, один ко многих, многие ко многим). Практически, модель — это класс Dart, например: Pony
-
Представление (View), в нашем случае это Flutter виджеты (кнопки, текст, списки), которые будут отображать нашу модель. View должно знать о модели и о её свойствах. Пользователь взаимодействует только с представлением и инициирует различные события (нажатие кнопки, свайп пальцем и т.д.). События могут оказывать влияние на модель, это происходит не напрямую, а через контроллер. Практически, представление — это виджеты: Text, Scaffold, AppBar, ListView и другие.
-
Контроллер (Controller) получает необработанные данные (например от сервера) и заполняет ими модель. При возникновении какого-либо события контроллер может изменить модель. После этого измененная модель снова отобразиться в представлении. Практически, это специальный класс, который мы вынесем отдельно, например: HomeController
Более подробная информация есть на Википедии.
MVC на деле
Ну что ж применим полученные знания на практике.
Для Flutter есть специальный pub-пакет, который мы уже подключили во части II в pubspec.yaml файле:
# блок зависимостей dependencies: flutter: sdk: flutter # подключение необходимых pub-пакетов # используется для произвольного размещения # компонентов в виде сетки flutter_staggered_grid_view: ^0.4.0 # этот пакет содержит вспомогательные # элементы для реализации MVC паттерна # в Flutter приложении mvc_pattern: ^7.0.0 # большая часть данных будет браться из сети, # поэтому мы будем использовать http для # осуществления наших запросов http: ^0.13.3
После этого выполним pub get команду в корне нашего проекта:
flutter pub get
Также вы можете воспользоваться встроенными возможностями Android Studio (блок Flutter commands):

Воспользуемся готовым кодом из прошлых частей и сделаем нашу домашнюю страницу в соотвествии с паттерном MVC.
Мы уже имеем модель:

И представление:

Обратите внимание, что представление (View) содержит лишнюю логику, которая должна быть вынесена в контроллер.
Не поленимся и вынесем)
Для этого создадим новую папку controllers и в ней файл home_controller.dart:
import 'package:flutter/material.dart'; import 'package:mvc_pattern/mvc_pattern.dart'; import '../models/tab.dart'; // библиотека mvc_pattern предлагает // нам специальный класс ControllerMVC, // который предоставит нам setState метод class HomeController extends ControllerMVC { // ссылка на объект самого контроллера static HomeController _this; static HomeController get controller => _this; // сам по себе factory конструктор не создает // экземляра класса HomeController // и используется для различных кастомных вещей // в данном случае мы реализуем паттерн Singleton // то есть будет существовать единственный экземпляр // класса HomeController factory HomeController() { if (_this == null) _this = HomeController._(); return _this; } HomeController._(); // GlobalKey будет хранить уникальный ключ, // по которому мы сможем получить доступ // к виджетам, которые уже находяться в иерархии // NavigatorState - состояние Navigator виджета // знак _ как уже было отмечено указывает на то, // что это private переменная, поэтому мы // не сможем получить доступ извне к _navigatorKeys final _navigatorKeys = { TabItem.POSTS: GlobalKey<NavigatorState>(), TabItem.ALBUMS: GlobalKey<NavigatorState>(), TabItem.TODOS: GlobalKey<NavigatorState>(), }; // ключевое слово get указывает на getter // мы сможем только получить значение _navigatorKeys, // но не сможем его изменить // это называется инкапсуляцией данных (один из принципов ООП) Map<TabItem, GlobalKey> get navigatorKeys => _navigatorKeys; // текущий выбранный элемент var _currentTab = TabItem.POSTS; // то же самое и для текущего выбранного пункта меню TabItem get currentTab => _currentTab; // выбор элемента меню // здесь мы делаем функцию selectTab публичной // чтобы иметь доступ к ней из HomePage // обратите внимание, что библиотека mvc_pattern // предоставляет нам возможность вызывать setState // в контроллере, что очень удобно void selectTab(TabItem tabItem) { setState(() => _currentTab = tabItem); } }
Большую часть кода мы вынесли из HomePage.dart
Теперь нам осталось подключить наш контроллер к нашему представлению:
import 'package:flutter/material.dart'; import 'package:mvc_pattern/mvc_pattern.dart'; import '../../models/tab.dart'; import '../../controllers/home_controller.dart'; import 'bottom_navigation.dart'; import 'tab_navigator.dart'; class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } // наше состояние теперь расширяет специальный класс // StateMVC из пакета mvc_pattern class _HomePageState extends StateMVC { // ссылка на наш контроллер HomeController _con; // super вызывает конструктор StateMVC и // передает ему наш контроллер _HomePageState() : super(HomeController()) { // получаем ссылку на наш контроллер _con = HomeController.controller; } // здесь почти ничего не изменилось // только currentTab и selectTab теперь // являются частью нашего контроллера @override Widget build(BuildContext context) { // WillPopScope переопределяет поведения // нажатия кнопки Back return WillPopScope( // логика обработки кнопки back может быть разной // здесь реализована следующая логика: // когда мы находимся на первом пункте меню (посты) // и нажимаем кнопку Back, то сразу выходим из приложения // в противном случае выбранный элемент меню переключается // на предыдущий: c заданий на альбомы, с альбомов на посты, // и после этого только выходим из приложения onWillPop: () async { if (_con.currentTab != TabItem.POSTS) { if (_con.currentTab == TabItem.TODOS) { _con.selectTab(TabItem.ALBUMS); } else { _con.selectTab(TabItem.POSTS); } return false; } else { return true; } }, child: Scaffold( // Stack размещает один элемент над другим // Проще говоря, каждый экран будет находится // поверх другого, мы будем только переключаться между ними body: Stack(children: <Widget>[ _buildOffstageNavigator(TabItem.POSTS), _buildOffstageNavigator(TabItem.ALBUMS), _buildOffstageNavigator(TabItem.TODOS), ]), // MyBottomNavigation мы создадим позже bottomNavigationBar: MyBottomNavigation( currentTab: _con.currentTab, onSelectTab: _con.selectTab, ), ),); } // Создание одного из экранов - посты, альбомы или задания Widget _buildOffstageNavigator(TabItem tabItem) { return Offstage( // Offstage работает следующим образом: // если это не текущий выбранный элемент // в нижнем меню, то мы его скрываем offstage: _con.currentTab != tabItem, // TabNavigator мы создадим позже child: TabNavigator( navigatorKey: _con.navigatorKeys[tabItem], tabItem: tabItem, ), ); } }
В представлении практически ничего не изменилось, мы только вынесли основную логику из HomePage в наш HomeController.

Вуаля! Все работает как прежде.
Немного слов об архитектуре Flutter приложений
Flutter является декларативным фреймворком и поэтому архитектура Flutter приложения всегда сводится к управлению состоянием StatefulWidget‘ов.
Существует множество подходов по управлению состоянием.
Более подробно об этом написано в самой документации по Flutter
Заключение
Поздравляю вас.
Хотелось бы отметить, что на этом знания об MVC не исчерпываются.
К тому же мы ещё не раз будет создавать новые контроллеры и модели.
Так что все впереди! До скорой встречи.
Полезные ссылки:
ссылка на оригинал статьи https://habr.com/ru/post/560806/
Добавить комментарий