Основы Flutter для начинающих (Часть IV)

от автора

Всем добрый денек! Надеюсь после первых трех статей, эта вам покажется не менее полезной.

Сегодня я постараюсь простым языком объяснить 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) является довольно старым изобретением и содержит три основных компонента:

  1. Модель (Model) представляет собой данные, что и является сутью любого приложения. Само по себе приложение невозможно без данных. Вернемся к примеру из предыдущей главы: список поняшек. В том случае данными являлись пони, которые мы отображали в виде списка. Модель должна обратывать все, что с ней связано (сохранение и манипулирование данными). Ещё модель может иметь отношения (один к одному, один ко многих, многие ко многим). Практически, модель — это класс Dart, например: Pony

  2. Представление (View), в нашем случае это Flutter виджеты (кнопки, текст, списки), которые будут отображать нашу модель. View должно знать о модели и о её свойствах. Пользователь взаимодействует только с представлением и инициирует различные события (нажатие кнопки, свайп пальцем и т.д.). События могут оказывать влияние на модель, это происходит не напрямую, а через контроллер. Практически, представление — это виджеты: Text, Scaffold, AppBar, ListView и другие.

  3. Контроллер (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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *