Добавление функционала в приложение
Сначала немного теории:
Flutter поддерживает множество разных архитектурных подходов, и все они могут уживаться в одном приложении. Конечно, есть библиотеки, одновременное использование которых приведет к ошибке, но таких очень мало. Связано это с тем, что каждый виджет, каждый экран, в каком-то смысле сам по себе - когда Flutter отрисовывает виджет на экране, ему в этот момент не важно, с помощью чего был отрисован предыдущий виджет. Именно поэтому мы можем сочетать элементы управления Android (material) и iOS (cupertino) на одном экране.
Конфликт возникает в основном тогда, когда мы пытаемся одновременно использовать библиотеки, содержащие одинаковые методы - но и в этом случае можно дать им псевдонимы и все будет работать.
Единственное, с чем приходится считаться с самого начала и от чего нельзя отступать во избежание путаницы в работе программы - это способ организации маршрутизации. К счастью, их всего штук пять, и все они сделаны только с одной целью - упростить громоздкую "родную" систему. Если не поняли еще, то маршрутизация - это способ, которым во Flutter происходит переход с экрана на экран.
Это всегда выглядит как колода карт - когда мы переходим на новый экран, он как бы ложится поверх предыдущего, и когда мы возвращаемся "назад" по кнопке или программно - то мы убираем "верхний" экран обратно в колоду.
Способы поиска и "извлечения карты из колоды" и есть маршрутизация.
Хорошо написанный виджет, экран или библиотека работают опираясь на свою логику, а не на ту, которую вы организовали в своем приложении.
Как это выглядит на практике? Допустим, вы нашли подходящий экран в составе другого приложения на GitHub. На нем уже есть какие-то виджеты, он что-то делает и вроде бы корректно. Чтобы нам его безболезненно вставить в наше приложение, надо его максимально изолировать от остального кода, сделать так, чтобы исходное приложение - то, откуда вы его забираете - не содержало ничего кроме этого экрана и сопутствующей логики. То есть максимально убрать код, связанный с ненужными вам экранами и виджетами.
К сожалению, большинство того, что вам повстречается в открытых источниках, написано не очень качественно. Даже выбирая код, у которого много звезд и подписчиков, вы рискуете столкнуться с трудностями - например, оверинжинирингом. Это когда человек настолько стремится выглядеть круто в глазах коллег-программистов, что начинает использовать слишком сложные подходы в работе, зачастую в ущерб скорости работы и читаемости кода.
Еще есть риск нарваться на код, сделанный по тем же принципам, которые я вам сейчас пытаюсь показать - когда нет особого понимания или опыта, но есть настойчивость. В таком случае рождаются грифоны: голова орла, жопа льва, хвост дракона, ноги лошади. К счастью, в мире Flutter это обычно обозначает, что он действительно будет и бегать, и летать, и красиво выглядеть, и если вы нарвались на исходный код такого приложения - вам будет несложно забрать нужную "запчасть" себе.
Успокоительное: если вы думаете, что обязательно лошадь должна быть лошадью, а лев - львом и никто не смешивает травоядных с плотоядными, то хочу заметить, что во многих студиях вам создают еще более страшных химер. Если, делая что-то для себя, вы еще будете стараться, то задача студии сделать так, чтобы было выполнено ТЗ. Написано "может ходить" - вот оно и ходит, а про бег ни слова не было.
Еще один момент, связанный с разработкой - многие программисты как огня боятся работать с чужим кодом. Причина проста - придется знакомиться с подходами, которые они не используют, а это дискомфорт, сравнимый с религиозной нетерпимостью. Просто потому, что их учили иначе.
У нас с вами есть шанс не опускаться во все это болото "правильных" и "неправильных" подходов, а просто делать свое дело, не жертвуя миллионы рублей инвалидам умственного труда.
Теория закончилась :)
Давайте в наше приложение добавим кнопку "заказать". Естественно, это должна быть не просто кнопка, а экран, где мы должны собрать базовую информацию о заказчике - нас как минимум интересует имя, телефон и почта.
Я предлагаю воспользоваться специально подготовленным для этого кодом, написанным мной лично. Слушатели марафона получат его бесплатно, а если вы попали на эту страницу случайно, то исходный код можно взять здесь: https://boosty.to/flutterbox/posts/be291f2b-0f25-4791-8ee7-0198b786f694?share=post_link
Напоминаю, что моя главная задача - научить вас анализировать код в отрыве от синтаксиса, поэтому я постарался показать некоторые ошибки и способы их решения.
1. Добавляем новый код в свой проект и настраиваем его
Скачиваем архив, нажимаем правой кнопкой мыши и распаковываем его:
Распаковать желательно в отдельную папку, чтобы потом самому не собирать все в кучу:
Дальше нам надо этот код добавить к себе. Для этого находим и открываем в Проводнике наш проект
И переходим в папку lib, где и лежит исходный код на Flutter
Здесь, чтобы придерживаться структуры, переходим в папку pages (страницы)
и просто создаем здесь новую папку
Назовем ее 3_order. Цифры в именах я изначально использовал, чтобы вам легче было ориентироваться в коде, но почему бы и не продолжить эту традицию?
Переходим в нее и копируем содержимое только что распакованного архива внутрь:
Все. Теперь открываем проект в Android Studio, либо просто переходим туда, если он уже открыт. Новая папка уже должна там появиться:
Красным она выделена потому, что в ней содержится какой-то файл с ошибкой. Зайдем в нее и посмотрим:
Это файл edit_profile_controller.dart и ругается он на то, что не может импортировать библиотеку intl. Узнать о том, что это такое, а, главное, как это к себе добавить, можно на главном сайте с расширениями для Flutter - https://pub.dev/
Открываем его и ищем intl:
Он найдет много чего, нам нужно наиболее точное совпадение
На первом же экране написано, что этот пакет отвечает за интернационализацию, направление текста, форматирование дат. Переходим на вкладку installing чтобы посмотреть, как его установить
Обычно добавление большинства библиотек (пакетов) к себе в приложение выглядит примерно так же, как и здесь:
Мы просто дописываем строку с названием и версией пакета в раздел dependencies файла pubspec.yaml, просто скопировав ее с сайта
Обратите внимание на важный момент: pubspec.yaml - это необычный в плане разметки файл, он чувствителен к отступам слева. Я красной линией показал, что все, что в разделе dependencies должно быть выровнено по вертикали, на два пробела глубже, чем заголовок. Если пробелов будет три или один - будет ошибка.
Парадокс в том, что несмотря на то, что мы вроде бы добавляем пакет, отвечающий за интернационализацию, он никак не влияет на диалоговое окно с календарем для выбора даты. Он настраивается иначе, для его работы надо добавить строки
flutter_localizations:
sdk: flutter
sdk: flutter
не забывая сохранять отступы, то есть в итоге ваш pubspec.yaml будет выглядеть так:
После этого нажимаем вверху на желтую надпись Pub get, чтобы Flutter скачал и подключил что нам нужно в программу. К сожалению, мы наблюдаем ошибку:
Если вглядеться, то мы понимаем, что у нас конфликт - мы добавляем intl версии 0.18.1, а flutter_localization хочет 0.17.0
Я не знаю, почему именно такое жесткое ограничение и чем оно обусловлено, но это частое явление мира Open Source: разработчики одной библиотеки, используя другую библиотеку, поленились обновить зависимости. Мы не будем сильно заморачиваться над этим, пока что попробуем разрешить Flutter самому выбирать версию устанавливаемой библиотеки. Для этого удалим цифры после intl: как на картинке:
Я не знаю, почему именно такое жесткое ограничение и чем оно обусловлено, но это частое явление мира Open Source: разработчики одной библиотеки, используя другую библиотеку, поленились обновить зависимости. Мы не будем сильно заморачиваться над этим, пока что попробуем разрешить Flutter самому выбирать версию устанавливаемой библиотеки. Для этого удалим цифры после intl: как на картинке:
Снова щелкнем Pub get наверху и наблюдаем, что в этот раз все прошло успешно, а ошибки в файле больше не найдены (он больше не подчеркнут красным):
Теперь, чтобы flutter_localization у нас заработал, надо добавить эти строки в main.dart внутрь виджета MaterialApp (или GetMaterialApp):
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en', 'US'),
Locale('ru', 'RU'), // русский
],
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en', 'US'),
Locale('ru', 'RU'), // русский
],
Чтобы это выглядело так:
Подчеркнуты красным они потому, что Flutter не знает, что это такое. Допишем наверх строку
import 'package:flutter_localizations/flutter_localizations.dart';
и все встанет на свои места:
Переходим к следующему этапу
2. Добавляем кнопку
Просто скопировать код в отдельную папку проекта - это еще не все. Надо сделать так, чтобы он начал работать, то есть нам надо его в нужном месте вызвать.
Поскольку у нас это отдельный экран, то вызывать мы будем класс с виджетом из edit_profile_view:
А вызывать его мы будем по нажатию на кнопку, которую, видимо, надо разместить внизу экрана детальной информации о товаре. У нас это details_view.dart, а кнопку мы поместим внутрь виджета Column под всеми другими, сюда:
У меня есть несколько заготовок кнопок на отдельной странице, https://boosty.to/flutterbox/posts/1ae3548e-8643-4484-b3cd-ec956c5d5dd5?share=post_link,
мы же пока возьмем самую простую. Вот ее код:
ElevatedButton(
onPressed: () { },
child: Text(
"Button 1",
style: TextStyle(
fontWeight: FontWeight.w600, color: Colors.white, fontSize: 18),
),
style: ElevatedButton.styleFrom(
minimumSize: const Size(170, 55),
backgroundColor: Color(0xff4b84f5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7.0),
),
),
)
onPressed: () { },
child: Text(
"Button 1",
style: TextStyle(
fontWeight: FontWeight.w600, color: Colors.white, fontSize: 18),
),
style: ElevatedButton.styleFrom(
minimumSize: const Size(170, 55),
backgroundColor: Color(0xff4b84f5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7.0),
),
),
)
Копируем его и вставляем, куда решили. И видим ошибку:
Если навести на предполагаемое место ошибки курсор мыши, то увидим, что вместо слова ElevatedButton ожидалось увидеть запятую. Тут все просто: виджет Column, в который мы сейчас добавили этот код, все виджеты хранит в виде списка (квадратные скобки, помните?) где все элементы отделены запятыми. Мы добавили новый элемент, но не добавили запятую перед ним.
Другими словами, поставьте запятую в конце предыдущей строки, и ошибка пропадет.
Настало время настроить цвет. Цвет в той кнопке, которую вы добавили, добавлен как хардкод - то есть не настраивается где-то в файле конфигурации, а указан прямо внутри нее. Мы не будем это исправлять, но цвет поменяем.
Flutter имеет два подхода к заданию цвета. С помощью собственной палитры Colors - тогда цвет указывается как свойство объекта, то есть мы его выбираем из конкретного набора:
чтобы выпала такая подсказка - удалите все вместе с точкой, чтобы осталось только слово Colors, затем поставьте точку и появится выбор, вы уже такое делали:
Второй вариант задания цвета - с помощью Color, в таком случае цвет может быть произвольным, а быстро поменять его можно, нажав на квадратик с цветом слева:
После того, как все сделали, запустим приложение. Если оно уже работало на этот момент, потребуется именно перезапуск всего приложения, горячая перезагрузка не подойдет - потому что мы добавили новые файлы.
Как видим, кнопка на месте, хотя и сильно близко от текста.
Давайте еще раз потренируемся с контекстными действиями и обертками:
Давайте еще раз потренируемся с контекстными действиями и обертками: