Подробный обзор образца приложения шаблона проекта MVC

Это продолжение бесплатной статьи Ваш следующий проект Flutter, где я представил Шаблон проекта, который вы можете использовать для своего следующего проекта Flutter. Он содержит основу, файлы и каталоги, составляющие приложение Flutter на основе шаблона проектирования MVC, представленного пакетом библиотеки mvc_application. Вы должны взять его и заполнить своим кодом для следующего приложения Flutter. Я надеюсь когда-нибудь сделать такой шаблон опцией Новый проект в IntelliJ и Android Studio, но это уже другая история.

В этой статье я хочу дополнительно продемонстрировать предлагаемую реализацию, используя образец приложения Контакты, который в настоящее время включен в один из шаблонов проекта. Загрузите этот zip-файл и запустите его. Вас встретит приложение "Контакты". Предлагается использовать отладчик вашей IDE и пройтись по коду, чтобы лучше понять, как работает среда MVC и, более того, как работает сама среда Flutter.

Мне нравятся скриншоты. Щелкните для Gists.

Как всегда, я предпочитаю использовать скриншоты, а не суть, чтобы показать концепции, а не просто показывать код в своих статьях. Я считаю, что с ними легче работать и легче читать. Однако вы можете щелкнуть / коснуться их, чтобы увидеть код в сущности или в Github. По иронии судьбы, эту статью о мобильной разработке лучше читать на компьютере, чем на телефоне. Кроме того, мы программируем на наших компьютерах; не на наших телефонах. Теперь.

Никаких движущихся изображений, никаких социальных сетей

В этой статье будут файлы gif, демонстрирующие аспекты рассматриваемой темы. Однако при чтении этой статьи на таких платформах, как Instagram, Facebook и т. Д., Просмотр таких файлов gif невозможен. Они могут выглядеть как статические изображения или просто пустые поля-заполнители. Помните об этом и, возможно, прочтите эту статью на medium.com.

Давай начнем.

Давай добавим контакты

Когда вы начнете использовать этот образец приложения, вы, несомненно, начнете вводить контакты. Давайте заглянем «под капот», чтобы увидеть, что происходит, когда после ввода определенного контакта вы нажимаете кнопку «Сохранить». Теперь в шаблоне проектирования MVC аспект View здесь, в данном случае, это функция build (), которая находится в объекте _AddContactState, перечисленном ниже с левой стороны.

С первого взгляда вы можете увидеть, что событие «Сохранить» обрабатывается, и это правильно, объектом контроллера с именем con. Затем вызывается навигатор, чтобы вернуться к предыдущему экрану. Справа находится снимок экрана этой функции onPressed (). Мы видим, что функция выполняет несколько процедур проверки перед вызовом своей собственной процедуры «добавления». После этого он вызывает функцию refresh (). Здесь много чего происходит. Также обратите внимание, что имя функции имитирует API, используемый самим Flutter. Другими словами, функция названа в честь указанного параметра, onPressed. Скоро мы поговорим об этом подробнее.

Давайте проясним, что этой функции нет в самом контроллере. Контроллер вызывает функцию onPressed (), но она находится в классе «Контакты Добавить». Слева внизу выделены Контроллер и его получатель, add, , обращающийся к экземпляру этого другого класса . Ниже с правой стороны находится эта функция, снова явно принадлежащая к классу ContactAdd.

Граф из трех

Давайте продолжим и заглянем внутрь собственной функции ContactAdd add (). Он находится в родительском классе ContactEdit и, в свою очередь, вызывает функцию из класса Model приложения. В этом есть смысл. Мы сохраняем новые данные, и за эти данные отвечает «Модель» в архитектуре MVC. Видите, как в этом приложении есть такое разделение обязанностей? Такое расположение обеспечивает лучшую масштабируемость и, откровенно говоря, упрощает обслуживание этого приложения.

Наконец, обратите внимание, что класс ContactEdit включает еще один родительский класс, ContactList. Фактически, мы видим, что в этом приложении есть три разных класса для трех разных задач: для добавления контакта, для редактирования контакта и для составления списка контактов. Само собой разумеется, что каждый класс имеет индивидуальные функции, уникальные для той роли, которую они играют. Однако каждая из них строится на основе наследования. Интересно, а?

Я написал бесплатную статью об этом подходе еще в январе 2019 года. Несмотря на множество изменений, внесенных в реализацию MVC, эта статья все еще актуальна, давая вам более глубокое понимание этого предмета.

Итак, у класса ContactEdit есть функция add (), которая вызывает класс Model и его функцию addContact (). В собственном контроллере приложения «Контакты» нет ссылки и понятия об этой функции addContact (), и это нормально. Этот уровень абстракции позволяет, например, изменять имя функции со временем. Возможно, приложение со временем будет управлять совершенно другой базой данных. У вас есть такая возможность. Слева внизу находится функция add () в классе ContactEdit. С правой стороны, это «Модель», которая на самом деле делает всю грязную работу и добавляет новый Контакт в базу данных.

Вернувшись к функции onPressed () в классе ContactAdd, мы видим, что после добавления нового контакта в базу данных функция завершается вызовом своего обновить (). Теперь обратите внимание, этот класс принимает контроллер «Контакт» из своего конструктора, и поэтому мы знаем, что прямо здесь и сейчас он (и его родительские классы) имеет доступ к контроллеру - и, следовательно, к представлению (StateMVC) при выполнении своих операций. Так обстоит дело и с этим «обновлением».

Обновите свои контакты

Когда мы переходим к функции refresh (), мы обнаруживаем, что она находится в классе ContactList. Имеет смысл - «обновить» список контактов. Однако посмотрите, как это достигается. Это делается с помощью функций, найденных в контроллере «Контакт». Сначала он извлекает свежий список из базы данных (с новым добавленным контактом), а затем перерисовывает, перестраивает или обновляет экран. Обратите внимание, что на снимке экрана ниже справа показано, что класс Model снова делегирован для получения нового списка контактов. Опять же, в этом есть смысл. «Модель» связана с данными.

Помните, что контроллер, Contact, был взят объектом View (StateMVC), ContactListState (внизу слева). Итак, когда Контроллер вызывает свою функцию refresh (), это приведет к повторному вызову функции build () объекта State, что приведет к обновлению его списка контактов. Ты знаешь почему?

Это потому, что будет вызвана функция setState () объекта State. Вот почему. Во Flutter для «обновления текущего дерева виджетов» вы вызываете функцию setState () объекта State. Название этой функции, конечно же, подразумевает, что эта функция является частью процесса «Управление состоянием», так легко узнаваемого разработчиками, переходящими на Flutter из мира веб-приложений. По правде говоря, управление состоянием больше не является первостепенной проблемой для разработчиков, поскольку фреймворк Flutter теперь берет на себя эти обязанности. Эту функцию так же легко можно было бы вызвать… ну… refresh (). Но я отвлекся.

Ниже, на скриншоте слева, мы видим функцию контроллера refresh (). Вы можете видеть, что, в свою очередь, он вызывает связанный с ним объект View (StateMVC), вызывая его функцию refresh (). Теперь вы, возможно, знаете, что у объекта State нет функции refresh () во Flutter, но это версия объекта State (StateMVC) фреймворка MVC. На снимке экрана ниже справа показана эта версия MVC-фреймворка объекта State. Без ведома типичного разработчика, просто использующего инфраструктуру MVC, а не «заглядывающего под капот», как мы делаем здесь, сегодня в конечном итоге вызывается исходная функция setState (), но не раньше, чем через некоторые дополнительные функции. Хорошая штука, правда?

Это работа фреймворка. В большинстве случаев вы ничего не понимаете. Пусть все заботы за вас сделает фреймворк. Вы знаете, что во Flutter вы должны использовать setState () - так что используйте его в этой структуре! Однако на самом деле это не та функция, которую вы думаете. Он вызывает исходную функцию setState (), но не раньше, чем убедитесь, что вы не вызываете инвертированную ошибку, например, например, вызов функции setState (), когда вы не должны т. Хороший фреймворк должен быть «ненавязчивым» для пользователя, но тем не менее выполнять свою работу. Пусть пользователь продолжает свою работу, а всю грязную работу он выполняет в фоновом режиме.

Кстати, на картинке ниже представлен список классов, задействованных, когда пользователь нажимает кнопку «Сохранить». Он возвращается на полный цикл к объекту State _AddContactState и даже к его родительскому классу StateMVC, чтобы снова вызвать функцию build () объекта State и обновить экран с недавно введенным контактом. Все в одно мгновение.

Между прочим, с этим процессом, поскольку контроллер «обновляет» список контактов после добавления нового контакта, это означает, что представление может «разговаривать» с контроллером, а контроллер может «разговаривать» с представлением. Это потому, что Контроллер в конце процесса сообщает View вызвать его функцию setState () для обновления списка. Прекрасно. Эта конфигурация постоянно используется в моих собственных приложениях и, возможно, также во многих ваших.

Давайте разберемся

Давайте еще раз пройдемся по коду. На этот раз это код, который используется для «сортировки» списка контактов в алфавитном порядке. Мы надеемся, что это даст вам более полное представление об используемом подходе MVC, а также о встроенных функциях и возможностях инфраструктуры MVC, поскольку некоторые из них будут использоваться здесь для этой конкретной возможности. Файл gif ниже слева демонстрирует возможность сортировки, когда вы нажимаете значок «сортировка», расположенный на панели приложения. Код, отображаемый справа, выделяет саму выполненную подпрограмму.

Настроить сортировку

Давайте прокрутим немного вверх от функции build (), показанной выше, и сделаем небольшой обзор. Как мы теперь знаем, список контактов поступает из функции build () объекта State - как предписано собственной структурой Flutter. Это скриншот слева. Объект State происходит из собственного класса платформы MVC, StateMVC. Таким образом, он может принимать объект Controller своим конструктором и делает это с помощью класса Contact.

Вы будете видеть этот шаблон снова и снова при использовании этого конкретного фреймворка. В соответствии с подходом MVC компонент View отвечает за «интерфейс» в любой заданный момент и время, когда приложение запущено, в то время как Контроллер отвечает за реагирование на любые и все «события», вызванные системой или пользователь в течение жизненного цикла этого приложения. То, что вы видите, - это один из способов связать конкретный контроллер с определенным представлением. Опять же, в этой интерпретации MVC для Flutter содержимое функции build () объекта State рассматривается как представление. Здесь и там в этой функции есть ссылки на контроллер, готовый реагировать на события, полученные этим представлением.

Чтобы продолжить, мы видим, что функция initAsync () вызывается в функции initState () объекта State и традиционно содержит все без исключения «асинхронные» операции, которые необходимо выполнить перед Просмотр (функция build ()) готов к приему входных данных (событий) от пользователя или от системы. Во многих случаях виджет FutureBuilder в функции build () будет использоваться вместо этого для вызова функции initAsync (), но, как правило, операции в нем выполнялись «быстро». достаточно ', и мы могли бы начать операции изнутри функции initState (). Должны ли мы применять стандартный подход и в любом случае всегда использовать FutureWidget? Может быть. Конечно, как и у любого хорошего фреймворка, у вас есть такая возможность.

Обратите внимание, в свою очередь, initAsync () объекта State вызывает любые и все собственные функции initAsync () связанных Контроллеров. На снимке экрана ниже справа показана функция initAsync () в контроллере, Contact.

Опять же, этот объект состояния MVC при вызове его функции initAsync () проходит через все функции initAsync () связанных с ним контроллеров, если таковые имеются. Ниже приведен снимок экрана класса StateMVC и его функции initAsync ().

Ваши первоначальные предпочтения

Давайте подробнее рассмотрим функцию контроллера initAsync (). Мы видим, что этот Контроллер вызывает собственную функцию initAsync () своей Модели. Затем он вызывает служебный класс Prefs, чтобы определить, будут ли отображаемые контакты перечислены в алфавитном порядке или нет при запуске приложения. Это определяется из логического значения, предоставленного сохраненными настройками устройства. Я чувствовал, что предоставление настроек приложения - это функция, которая явно необходима для мобильных приложений. Следовательно, такая возможность легко доступна вам в самой структуре MVC. Объект класса Prefs следует подходу Singleton, поэтому вы получаете доступ к сохраненным настройкам устройства из любой точки приложения.

Образцовый кандидат

Быстрый взгляд на класс Model, используемый в этом приложении, показывает, что его функция initAsync () инициализирует базу данных с именем Контакты. База данных контактов открыта и готова к использованию. До этого момента вы понятия не имели, что здесь есть база данных под названием «Контакты». Контроллер «обращается» к модели, а не напрямую к базе данных контактов. Контроллер даже не подозревает, что эта база данных контактов существует. Опять же, с помощью этого уровня абстракции вы можете легко масштабировать или легко изменять базу данных, если и когда это необходимо, не затрагивая остальную часть приложения. Отлично.

Обратите внимание, что контроллер, в свою очередь, вызывает класс ContactList, поскольку на него есть ссылка в методе получения контроллера, list. Опять же, Контроллер ссылается на три разных класса через геттеры: ContactAdd, ContactEdit и ContactList. В функции контроллера initState () эти ссылки блестяще задуманы (если я сам так говорю) путем создания экземпляра одного объекта класса, называемого ContactAdd.

Конечно, это возможно, потому что каждый класс расширяет другой, пока мы не перейдем к классу ContactFields. На картинке ниже изображено наследование.

Свежие контакты

Итак, вернемся к обновлению списка контактов. Когда вы вызываете функцию refresh () в классе list, она вызывает контроллер, чтобы снова получить контакты (вызывая свою модель), а затем вызывает свое обновление. (), чтобы перестроить экран (вызвать его View) и отобразить новую запись контакта. Вы можете увидеть в функции getContacts () эту частную переменную экземпляра, которая называется _sortedAlpha. Это логическое значение, и если это правда, список контактов сортируется. Очень просто.

Сортировать это

Итак, я думаю, вы можете легко увидеть на скриншоте выше, когда нажимается значок «сортировка» на панели приложения, вызывается функция Контроллера sort (). Функция сортировки «включается» и выключается при каждом нажатии. Вы можете увидеть, как это реализовано, с помощью переменной экземпляра _sortedAlpha. Это переключаемое значение затем немедленно сохраняется в сохраненных настройках устройства, а список контактов извлекается еще раз. Видишь, как это работает?

ContactList расширяет класс ContactFields. Класс ContactFields содержит функции и возможности, специфичные для тех, которые требуются для хорошего списка приложений… Контакты. Однако обратите внимание, что все поля (переменные частного экземпляра), определенные в этом классе, имеют тип FieldWidgets. Вы найдете этот класс во фреймворке MVC. Таким образом, вы можете сами опробовать подход «три занятия для трех задач».

Класс предмета

Класс FieldWidgets в структуре MVC расширяет класс DataFieldItem. Как видите, этот класс занимается только значением (независимо от того, что это за тип данных динамический) и меткой, которая должна быть связана с этим элементом. Вот и все. Остальное вы должны создать с помощью этих трех других классов, чтобы составить поля данных для вашего приложения. Вы обнаружите, что его потомок, FieldWidgets, действительно занят - с его почти 80 параметрами и всем остальным. Опять же, вы можете узнать больше об этом в упомянутой выше бесплатной статье Убери весь флаттер!.

Вы знаете, мне нужно на этом остановиться и пока опубликовать эту статью.

TL;DR

У меня есть еще кое-что, что я могу сказать о фреймворке MVC, поскольку он относится к функциям и возможностям, обнаруженным, например, в Scoped Model и других архитектурах, но я хочу опубликовать это сейчас, поскольку я поставил себя на график - предприниматели нуждаются в чтобы сделать это, разве вы не знаете. Это часть тайм-менеджмента и все такое.

Однако эта статья еще не закончена. Например, класс ScopedModelDescendant, используемый архитектурой Scoped Model, заменяется в структуре MVC классом с именем SetState.

Как и класс ScopedModelDescendant, здесь используется тот факт, что всякий раз, когда InheritedWidget во Flutter сам «перестраивается», любой виджет, имеющий доступ к этому InheritedWidget, также будет «перестроен». Например, на скриншоте ниже виджет SetState позволяет Виджет скаффолда, который нужно перестроить - даже в StatelessWidget.

Я буду добавлять больше в этот раздел TL; DR в ближайшие несколько дней. Пожалуйста, добавьте эту статью в закладки и следите за обновлениями.

Ваше здоровье.

→ Другие рассказы Грега Перри