Репозитории: Flutter Interview, Flutter Roadmap, Flutter Acrticles, Flutter Best Packages, Flutter Tools
- Общие
- Dart
- final и const
- JIT и AOT
- Hot Restart и Hot Reload
- HashCode
- Extension
- Mixin
- Sound Null Safety
- Система типов
- Late
- Generics
- Dart VM
- Зоны
- Типы ошибок
- Правила именования
- Never
- Covariant
- Аннотации
- int8, uint8, int16, uint16...
- Future
- Конструкторы Future
- Await под капотом
- Event Loop
- Completer
- Stream
- Генераторы (sync* / async*)
- Многопоточность в Dart и Flutter
- Isolate
- Compute
- Проблемы многопоточности
- Flutter
- Stateless и Stateful виджеты
- Жизненный цикл Stateful виджета
- BuildContext
- InheritedWidget
- Деревья
- Widget
- Element
- RenderObject
- Виды виджетов
- Виды элементов
- Жизненный цикл Element-а
- GlobalKeys
- LocalKeys
- Устройство Flutter под капотом
- Модель выполнения во Flutter
- CustomPaint
- WidgetsFlutterBinding
- Bindings
- Каналы платформы
- Режимы сборки
- Package и Plugin
- FFI Plugin
- Этапы анимации
- Виды анимаций
- Что такое Tween
- Tween анимации
- Построение кадра
- Расчёт макета
- BuildOwner
- PipelineOwner
- Garbage Collector
- Task Runners
- Архитектура
- Тестирование
- Паттерны разработки
Объектно-ориентированное программирование
— это парадигма программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования.
Абстракция
Моделирование взаимодействий сущностей в виде абстрактных классов и интерфейсов для представления системыИнкапсуляция
Объединение данных и методов, работающих с ними, в классеНаследование
Создания новых классов на основе существующихПолиморфизм
Использование объектов с одинаковым интерфейсом без информации о типе и внутренней структуре объекта
Single Responsibility Principle (Принцип единственной ответственности)
Класс должен отвечать только за что-то одно.Open-Closed Principle (Принцип открытости-закрытости)
Программные сущности должны быть открыты для расширения, но закрыты для модификации.Liskov Substitution Principle (Принцип подстановки Барбары Лисков)
Наследующий класс должен дополнять, а не замещать поведение базового класса.Interface Segregation Principle (Принцип разделения интерфейса)
Клиенты не должны имплементировать логику, которую они не используют.Dependency Inversion Principle (Принцип инверсии зависимостей)
Модули верхних уровней не должны зависеть от модулей нижних уровней. Классы и верхних, и нижних уровней должны зависеть от одних и тех же абстракций (при чём абстракции не должны знать о деталях).
Фича
Начало новой фичи
. Создаём новую веткуfeature/future_name
изdevelop
Завершение фичи
. Сливаемfeature/future_name
вdevelop
, удаляемfeature/future_name
Начало релиза
. Создаём ветку релизаrelease/vX.X.X
, ответляя от веткиdevelop
Завершение релиза
. Ветка релизаrelease/vX.X.X
сливается вmaster
, релиз помечается тегом, ветка релиза сливается вdevelop
, ветка релиза удаляется
Фикс
Начало исправления
. От веткиmaster
создаёмhotfix/fix_name
Завершение исправления
. Из веткиhotfix/fix_name
исправление сливается вdevelop
иmaster
, ветка фикса удаляется
Структуры данных нужны для хранения данных в подходящем виде
Массивы
Стеки
(LIFO - последний вошёл, первый вышел)Очереди
(FIFO - первый вошёл, первый вышел)Связные списки
Деревья
Графы
Хеш-таблицы
Императивный стиль
- описываем, как добиться желаемого результатаДекларативный стиль
- описываем, какой именно результат нам нужен
Стек
— это область оперативной памяти, в которой хранятся временные данные, таких как локальные переменные и адреса возврата функций. Объем памяти, выделенный под стек, ограничен. Стек работает в порядке LIFOКуча
— это область оперативной памяти, в которой хранятся данные, созданные во время выполнения программы. Куча используется для динамического выделения памяти для объектов, которые могут изменять размер во время выполнения программы. Размер кучи задаётся при запуске приложения, но, в отличие от стека, он ограничен лишь физически. Выделение памяти в куче происходит медленнее, чем в стеке.
DAO (Data Access Object, объект доступа к данным)
— абстрактный интерфейс к какому-либо типу базы данных или иному механизму храненияDTO (Data Transfer Object, объект переноса данных)
- это объект для передачи данных (объектов без поведения) между слоямиVO (Value Object, объект-значение)
⎼ это объект без специальных методов, имеющий набор свойств (полей) примитивных типов данных или тоже Value objectBO (Business Object, объект бизнеса)
- это объект, который представляют некую сущность из определенного «домена», то есть отрасли, для которой разработано приложение
DI
- передача зависимостей класса через параметры конструктораService Locator
- синглтон / класс с набором статических методов
Доступ к Service Locator
может производиться из любого место в коде. В этом заключается его основной минус
final
вычисляется в runtime-е. Константно только значение экземпляра. При использовании экземпляра final в памяти выделяется новая область памяти, даже если значение объекта будет идентично.const
вычисляется во время компиляции. Константно не только значение, но и сам экземпляр. При использовании const переменной новая область памяти не выделяется, а используется ссылка на уже существующий экземпляр
Just-in-time (JIT) компиляция
- это вид компиляции, который выполняется непосредственно во время работы программы, что существенно ускоряет цикл разработки. Но стоит учитывать, что программа может притормаживать и выполняться медленнееAhead-of-time (AOT) компиляция
- это вид компиляции, который полностью выполняется перед запуском программы. Код на Dart преобразуется в нативный машинный код, который затем упаковывается в бинарный файл с расширением.so
дляAndroid
или.dylib
дляiOS
.AOT
Занимает больше времени, чемJIT
, но в результате программа работает куда быстрее.
Hot Reload
загружает изменения вDart VM
и ребилдит дерево виджетов, сохраняя состояние. Не перезапускаетmain()
иinitState()
Hot Restart
загружает изменения вDart VM
и перезагружает всё приложение. Перезапускаетmain()
иinitState()
. Состояние не сохраняется
Хэш-код
- геттер, у любого объекта, который возвращает int
. Нужен при сохранении объекта в map
или set
. Хэш-коды должны быть одинаковыми для объектов, которые равны друг другу в соответствии с оператором ==
int get hashCode => Object.hash(runtimeType, ..., ...);
Extension
— это синтаксический сахар, который позволяет расширить существующий класс (добавить методы, операторы, сеттеры и геттеры)
Миксин
- это механизм множественного наследования, который позволяет классам использовать функциональность других классов без явного наследования.
Миксины в Dart определяются ключевым словом mixin
. Они могут содержать методы, поля и геттеры/сеттеры, но не могут иметь конструкторов. Вместо этого, миксины инициализируются автоматически, когда они применяются к классу. Для использования миксинов применяется оператор with
Если у миксинов будет метод с одинаковым названием, то останется реализация, которая указана в последнем миксине. Так как миксины будут переопределять этот метод
Sound Null Safety
– это дополнение к языку Dart, которое усиливает систему типов, отделяя типы, допускающие значение Null
, от типов, не допускающих значения Null
. Это позволяет разработчикам предотвращать ошибки, связанные с Null
.
С появлением null safety в Dart, иерархия классов и интерфейсов была изменена для учета новых требований по безопасности типов. Вот основные изменения:
-
Добавление non-nullable типов:
- Non-nullable типы обозначают, что значение не может быть null.
- Все существующие типы были разделены на non-nullable и nullable версии. Например,
int
сталint
(non-nullable) иint?
(nullable)
-
Новый корень иерархии - "Object?":
- Введен новый корневой класс
Object?
, который может быть null. В предыдущих версиях Dart, корневым классом былObject
- Введен новый корневой класс
-
Изменения в иерархии ошибок:
- Введен новый класс
NullThrownError
, который представляет собой ошибку, возникающую при попытке выброситьnull
исключение
- Введен новый класс
-
late
иrequired
:- Введены ключевые слова
late
иrequired
для обозначения переменных, которые могут быть инициализированы позднее и обязательно должны быть проинициализированы при объявлении, соответственно.
- Введены ключевые слова
Late
- это ключевое слово в dart, которое позволяет объявить non-nullable переменную и при этом не установить для нее значение. Значение инициализируется только тогда, когда мы к нему обращаемся
Generics
- это параметризованные типы. Они позволяют программе уйти от жесткой привязки к определенным типам, определить функционал так, чтобы он мог использовать данные любых типов и обеспечить их безопасность. Так же обобщения снижают повторяемость кода, дают вам возможность предоставить единый интерфейс и реализацию для многих типов.
Dart VM (Dart virtual machine)
- среда выполнения Dart
Компоненты:
Среда исполнения
Сборщик мусора
Основные библиотеки и нативные методы
Система отладка
Профилировщик
Симулятор ARM архитектуры
Зона - это механизм, который позволяет управлять и обрабатывать ошибки и другие события, происходящие в определенных областях кода.
- Защита вашего приложения от завершения из-за необработанного исключения
- Ассоциирование данных, известных как
zone-local values
, с отдельными зонами - Переопределение ограниченного набора методов, таких как print() и scheduleMicrotask(), внутри части или всего кода
- Выполнение операции каждый раз, когда код входит или выходит из зоны. Эти операции могут включать в себя запуск или остановку таймера или сохранение stacktrace-а
Exception
- это общий класс для исключений, которые обычно возникают из-за ошибок в программе, и их можно обработать и восстановиться от них:
- DeferredLoadException
- FormatException
- IntegerDivisionByZeroException (помечен как устаревший)
- IOException
- FileSystemException
- PathNotFoundException
- HttpException
- RedirectException
- ProcessException
- SignalException
- SocketException
- StdinException
- StdoutException
- TlsException
- CertificateException
- HandshakeException
- WebSocketException
- IsolateSpawnException
- TimeoutException
- NullRejectionException
Error
- это класс для ошибок, которые обычно не могут быть восстановлены, и они указывают на серьезные проблемы в программе или системе:
- OSError
- ArgumentError
- IndexError
- RangeError
- AssertionError
- AsyncError
- ConcurrentModificationError
- JsonUnsupportedObjectError
- JsonCyclicError
- NoSuchMethodError
- OutOfMemoryError
- RemoteError
- StackOverflowError
- StateError
- TypeError
- UnimplementedError
- UnsupportedError
- Переменные и константы -
lowerCamelCase
- Классы, миксины, enum-ы -
UpperCamelCase
- Файлы -
snake_case
Never
- это тип, означающий, что ни один тип не разрешен и Never
сам по себе не может быть создан. Используется как возвращаемый тип при гарантированной ошибке.
Covariant
- это ключевое слово в dart, которое указывает на то, что тип возвращаемого значения может быть изменен на более узкий тип в подклассе.
Аннотации
— это синтаксические метаданные, которые могут быть добавлены к коду. Другими словами, это возможность добавить дополнительную информацию к любому компоненту кода, например, к классу или методу. Аннотации всегда начинаются с символа @
(@override
, @required
). Любой класс может служить аннотацией, если в нем определен const конструктор.
Спецификатор | Общий эквивалент | Байты | Минимальное значение | Максимальное значение |
---|---|---|---|---|
int8 | signed char | 1 | -128 | 127 |
uint8 | unsigned char | 1 | 0 | 255 |
int16 | short | 2 | -32,768 | 32,767 |
uint16 | unsigned short | 2 | 0 | 65,535 |
int32 | long | 4 | -2,147,483,648 | 2,147,483,647 |
uint32 | unsigned long | 4 | 0 | 4,294,967,295 |
int64 | long long | 8 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
uint64 | unsigned long long | 8 | 0 | 18,446,744,073,709,551,615 |
Future
- это обёртка над результатом выполнения асинхронной операции. Код Future НЕ выполняется параллельно, а выполняется в последовательности, определяемой Event Loop.
Состояния Future:
- Uncompleted - операция не завершена
- Completed with Result - операция завершена успешно
- Completed with Error - операция завершена с ошибкой
Future(FutureOr<T> computation())
: создает объект future, который с помощью метода Timer.run запускает функцию computation асинхронно и возвращает ее результат.FutureOr<T>
: указывает, что функция computation должна возвращать либо объект Future либо объект типа T. Например, чтобы получить объект Future, функция computation должна возвращать либо объект Future, либо объект intFuture.delayed(Duration duration, [FutureOr<T> computation()])
: создает объект Future, который запускается после временной задержки, указанной через первый параметр Duration. Второй необязательный параметр указывает на функцию, которая запускается после этой задержки.Future.error(Object error, [StackTrace stackTrace])
: создает объект Future, который содержит информацию о возникшей ошибке.Future.microtask(FutureOr<T> computation())
: создает объект Future, который с помощью функции scheduleMicrotask запускает функцию computation асинхронно и возвращает ее результат.Future.sync(FutureOr<T> computation())
: создает объект Future, который содержит результат немедленно вызываемой функции computation.Future.value([FutureOr<T> value])
: создает объект Future, который содержит значение value.
Под капотом await
перемещает весь последующий код в then
у Future
, которую мы дожидаемся
Event Loop
- вечный цикл, выполняющий все поступающие в изолят задачи.
В нём есть две FIFO очереди задач:
Очередь MicroTask
Используется для очень коротких действий, которые должны быть выполнены асинхронно, сразу после завершения какой-либо инструкции перед тем, как передать управление обратно Event Loop. Очередь MicroTask имеет приоритет перед очередью Event
Очередь Event
Используется для планирования операций, которые получают результат от внешних событий (операции ввода/вывода, жесты, рисование, таймеры, потоки)
Completer
позволяет поставлять Future, отправлять событие о выполнении или событие об ошибке. Это может быть полезно, когда нужно сделать цепочку Future и вернуть результат.
Stream
- это последовательность асинхронных событий. Stream сообщает вам, что есть событие и когда оно будет готово
Single subscription
- это вид потока, при котором может быть только один подписчик.Broadcast
- это вид потока, при котором может быть много подписчиков. При этом Broadcast стримы отдают свои данные вне зависимости от того, подписан ли кто-нибудь на них или нет. Подписчики стрима получают события только с момента подписки, а не с момента старта жизни стрима
Генератор
это ключевое слово, которое позволяет создавать последовательность значений с помощью yield
- sync* - это синхронный генератор. Возвращает
Iterable
- async* - это aсинхронный генератор. Возвращает
Stream
Dart
— однопоточный язык программирования. Он исполняет одновременно одну инструкцию. Но при этом мы можем запустить код в отдельном поток с помощью Isolate
Isolate
- это легковесный процесс (поток исполнения), который выполняется параллельно с другими потоками и процессами в приложении. Каждый Isolate
в Dart имеет свой собственный экземпляр виртуальной машины Dart, собственную память и управляется с помощью своего Event Loop
.
Compute
- это функция, которая создаёт изолят и запускает переданный код.
Deadlock
— каждый из потоков ожидают событий, которые могут предоставить другие потокиRace conditions
— проявление недетерминизма исполнителя программы при различном относительном порядке исполнения команд в различных потокахLock Contention
— основное время потока проводится не в исполнении полезной работы, а в ожидании блокированного другим потоком ресурсаLive Lock
— поток захватывает ресурс, но после того, как убедится, что завершить работу не может, освобождает ресурс, аннулируя результаты
StatelessWidget
- это виджет, который не имеет состояния, в процессе работы приложения не изменяет своих свойств. Они могут изменяться лишь посредством внешних событий, которые возникают в родительских виджетахStatefulWidget
- это виджет, который хранит состояние, в процессе работы приложения он может его изменять динамически с помощьюsetState()
.
createState()
вызывается единожды и создает изменяемое состояние для этого виджета в заданном месте в деревеmounted is true
initState()
вызывается единожды при инициализацииdidChangeDependencies()
вызывается единожды после инициализации и далее при уведомлениях от Inhherited-виджетов вверху по дереву, от которых зависит виджетbuild()
вызывается каждый раз при перерисовкеdidUpdateWidget(Widget oldWidget)
вызывается каждый раз при обновлении конфигурации виджетаsetState()
вызывается императивно для перерисовкиdeactivate()
вызывается, когда ранее активный элемент перемещается в список неактивных элементов, при этом удаляясь из дереваdispose()
вызывается, когда этот объект удаляется из дерева навсегдаmounted is false
BuildContext
- это интерфейс, который имплементирует Element
.
BuildContext
может быть полезен, когда нужно:
- Получить ссылку на объект
RenderObject
, соответствующий виджету (или, если виджет не является Renderer, то виджету-потомку) - Получить размер
RenderObject
- Обратиться к дереву и получить ближайший родительский
InheritedWidget
. Это используется фактически всеми виджетами, которые обычно реализуют метод of (например,MediaQuery.of(context)
,Theme.of(context)
и т.д.)
InheritedWidget
— это виджет, который предоставляет своим потомкам возможность взаимодействовать с данными, хранящимися в нём. Решает проблему с передачей данных через конструкторы. Может уведомлять виджетов внизу по дереву об изменениях в собственных данных, тем самым провоцируя их перерисовку.
Для получения Inherited виджета необходимо вызвать context.dependOnInheritedWidgetOfExactType<T extends InheritedWidget>()
в didChangeDependencies()
Сложность у операции получения InheritedWidget - O(1). Такая скорость достигается за счёт того, что Inherited виджеты хранятся в виде хэш-таблицы в Element
-е
Widget Tree
состоит изWidget
, которые используются для описания пользовательского интерфейсаElement Tree
состоит изElement
, которые управляют жизненым циклом виджета и связывают виджеты и объекты рендеринга.Render Tree
состоит изRenderObject
, которые используются для определения размеров, положения, геометрии, определения зон экрана, на которые могут повлиять жесты
Widget
- это иммутабельное описание части пользовательского интерфейса. Виджет связан с элементом, который управляет рендерингом. Виджеты образуют сруктуру, а не дерево
Element
- это мутабельное представление виджета в определенном месте дерева. Управляют жизненым циклом, связывают виджеты и объекты рендеринга.
RenderObject
- это мутабельный объект дерева визуализации. У него есть родительский объект, а также поле с данными, которое родительский объект использует для хранения специфичной информации, касающейся самого этого объекта, например, его позицию. Данный объект отвечает за отрисовку, учёт размеров и ограничений, прослушивание и обработку нажатий. При необходимости перерисовки помечается как dirty
. Перерисовывается, используя свой метод layer
Proxy
- это виджеты, которые хранят некоторую информацию и делают её доступной для потомков. Эти виджеты не принимают непосредственного участия в формировании пользовательского интерфейса, но используются для получения информации, которую они могут предоставить.
InheritedWidget
ParentDataWidget
(LayoutId
,Flexible
,KeepAlive
и т.д.)NotificationListener
Renderer
- это виджеты, которые имеют непосредственное отношение к компоновке экрана, поскольку они определяют размеры, положение, отрисовку
Row
Column
Stack
Padding
Align
Opacity
Component
- это виджеты, которые предоставляют непосредственно не окончательную информацию, связанную с размерами, позициями, внешним видом, а скорее данные (или подсказки), которые будут использоваться для получения той самой финальной информации
RaisedButton
Scaffold
Text
GestureDetector
Container
ComponentElement
- компоновочный элемент, который явно не содержит логику рисования/отображения. Есть метод build()
, который возвращает виджет. Образуется только при создании виджетов StatelessWidget
, StatefulWidget
, InheritedWidget
.
ProxyElement
StatelessElement
StatefulElement
RenderObjectElement
- отображающий элемент, явно участвующий в рисовании компонентов на экране. Содержит renderObject
и наследуется от класса Element
. Образуется при создании виджетов Padding
, Column
, Row
, Center
и др.
LeafRenderObjectElement
ListWheelElement
MultiChildRenderObjectElement
RootRenderObjectElement
SingleChildRenderObjectElement
SliverMultiBoxAdaptorElement
SlottedRenderObjectElement
- Элемент создаётся посредством вызова метода
Widget.createElement
и конфигурируется экземпляром виджета, у которого был вызван метод. - С помощью метода
mount
созданный элемент добавляется в заданную позицию родительского элемента. При вызове данного метода также ассоциируются дочерние виджеты и элементам сопоставляются объекты дерева рендеринга. - Виджет становится активным и должен появиться на экране.
- В случае изменения виджета, связанного с элементом (например, если родительский элемент изменился), есть несколько вариантов развития событий. Если новый виджет имеет такой же
runtimeType
иkey
, то элемент связывается с ним. В противном случае, текущий элемент удаляется из дерева, а для нового виджета создаётся и ассоциируется с ним новый элемент. - В случае, если родительский элемент решит удалить дочерний элемент, или промежуточный между ними, это приведет к удалению объекта рендеринга и переместит данный элемент в список неактивных, что приведет к деактивации элемента.
- Когда элемент считается неактивным, он не находится на экране. Элемент может находиться в неактивном состоянии только до конца текущего фрейма, если за это время он остается неактивным, он демонтируется, после этого считается несуществующим и больше не будет включен в дерево.
- При повторном включении в дерево элементов, например, если элемент или его предки имеют глобальный ключ, он будет удален из списка неактивных элементов, будет вызван метод
activate
, и рендер объект, сопоставленный данному элементу, снова будет встроен в дерево рендеринга. Это означает, что элемент должен снова появиться на экране.
GlobalKeys
- это ключи, которые предоставляют доступ к виджетам. Для виджетов с отслеживанием состояния глобальные ключи также предоставляют доступ к состоянию. Позволяют виджетам менять родителей в любом месте приложения без потери состояния. Должны быть уникальны для всего приложения.
LocalKeys
- это ключи, которые нужны для идентификации виджетов в коллекции с одинаковыми значениеми должны быть уникальными среди виджетов с одним и тем же родительским виджетом. Могут использоваться для тестов:
ValueKey
- это ключ, который использует значение определенного типа для идентификации самого себя. Переопределяет оператор сравнения. Если value одниковое, то ключи одинаковыеUniqueKey
- это ключ, который равен только самому себеObjectKey
- это ключ, который используется для привязки идентификатора виджета к идентификатору объекта, используемого для создания этого виджета
Уровень фреймворка
— всё, с чем мы работаем в момент написания приложения, и все служебные классы, позволяющие взаимодействовать написанному нами с уровнем движка. Всё, относящееся к данному уровню написано на Dart. Flutter Framework
взаимодействует с Flutter Engine
через слой абстракции, называемый Window
Уровень движка
— более низкий уровень, чем уровень фреймворка, содержит классы и библиотеки, позволяющие работать уровню фреймворка. В том числе виртуальная машина Dart
, Skia
и тд.
Уровень платформы
— специфичные механизмы, относящиеся к конкретной платформе запуска.
Flutter Engine
уведомляет Flutter Framework
, когда:
- Событие, представляющее интерес, происходит на уровне устройства (изменение ориентации, изменение настроек, проблема с памятью, состояние работы приложения…)
- Какое-то событие происходит на уровне стекла (жест)
- Канал платформы отправляет некоторые данные
- Но также и в основном, когда Flutter Engine готов к рендерингу нового кадра
- Создается и запускается новый процесс —
Thread (Isolate)
. Это единственный процесс, в котором будет выполняться ваше приложение. - Инициализируются две очереди с
MicroTask
иEvent
, тип очередейFIFO
(прим.: first in first out, т.е. сообщение, пришедшие раньше, будут раньше обработаны) - Исполняется функция
main()
- Запускается
Event Loop
. Он управлет порядком исполнения вашего кода, в зависимости от содержимого двух очередей:MicroTask
иEvent
. Представляет собой "бесконечный" цикл. Event Loop
с определённой частотой проверяетMicroTask
иEvent
. Если есть что-то вMicroTask
, то оно выполняется перед очередьюEvent
.
CustomPaint
- это класс, который создает «холст» для рисования. В методе paint
в качестве аргументов поступает canvas
, который позволяет рисовать различные фигуры
WidgetsFlutterBinding
— конкретная реализация привязки приложений на основе инфраструктуры виджетов. По сути своей — это клей, соединяющий фреймворк и движок Flutter. WidgetsFlutterBinding
состоит из множества связей: GestureBinding
, ServicesBinding
, SchedulerBinding
, PaintingBinding
, SemanticsBinding
, RendererBinding
, WidgetsBinding
.
Метод scheduleAttachRootWidget
является отложенной реализацией attachRootWidget
. Принадлежит данный метод WidgetsBinding
. В описании к нему сказано, что он присоединяет переданный виджет к renderViewElement
— корневому элементу дерева элементов.
Метод scheduleWarmUpFrame
принадлежит SchedulerBinding
и используется для того, чтобы запланировать запуск кадра как можно скорее, не ожидая системного сигнала Vsync
.
Bindings
- это классы для обмена данными между Flutter Framework
и Flutter Engine
. Каждая привязка отвечает за обработку набора конкретных задач, действий, событий, сгруппированных по области деятельности.
BaseBinding
— базовый абстрактный класс, давайте тогда рассмотрим конкретные реализации биндингов. Среди них мы увидим:
ServicesBinding
отвечает за перенаправление сообщений от текущей платформы в обработчик данных сообщений (BinaryMessenger
);
PaintingBinding
отвечает за связь с библиотекой отрисовки.
RenderBinding
отвечает за связь между деревом рендеринга и движком Flutter.
WidgetBinding
отвечает за связь между деревом виджетов и движком Flutter.
SchedulerBinding
— планировщик очередных задач, таких как:
- вызовы приходящих колбеков, которые инициирует система в
Window.onBeginFrame
— например события тикеров и контроллеров анимаций; - вызовы непрерывных колбеков, которые инициирует система
Window.onDrawFrame
, например, события для обновления системы отображения после того, как отработают приходящие колбеки; - посткадровые колбеки, которые вызываются после непрерывных колбеков, перед возвратом из
Window.onDrawFrame
; - задачи не связанные с рендерингом, которые должны быть выполнены между кадрами.
SemanticsBinding
отвечает за связь слоя семантики и движком Flutter.
GestureBinding
отвечает за работу с подсистемой жестов.
(!) Платформенные взаимодействия возможны только в главном изоляте. Этот тот изолят, который создается при запуске вашего приложения.
Канал платформы
— это двусторонний канал связи между кодом на dart и нативом, который объединяет имя канала и кодек для кодирования сообщений в двоичную форму и обратно. Вызовы асинхронны. Каждый канал должен иметь уникальный идентификатор.
Каналы сообщений
- это каналы платформы, предназначенные для обмена сообщениями между нативным кодом и flutter-приложением.
Кодеки сообщений
:
BinaryCodec
Реализуя сопоставление идентификаторов в байтовых буферах, этот кодек позволяет вам наслаждаться удобством объектов канала в тех случаях, когда вам не требуется кодирование/декодирование. Каналы сообщений Dart с этим кодеком имеют тип BasicMessageChannel.JSONMessageCodec
Работает с «JSON-подобными» значениями (строки, числа, логические значения, null, списки этих значений и мапы строка-ключ с этими данными). Списки и мапы неоднородны и могут быть вложены друг в друга. Во время кодирования значения преобразуются в строки JSON, а затем в байты с использованием UTF-8. Каналы сообщений Dart имеют тип BasicMessageChannel с этим кодеком.StandardMessageCodec
Работает с несколько более обобщенными значениями, чем кодек JSON, поддерживая также однородные буферы данных (UInt8List, Int32List, Int64List, Float64List) и мапы с нестроковыми ключами. Обработка чисел отличается от JSON тем, что целые числа Dart поступают на платформу как 32- или 64-битные целые числа со знаком, в зависимости от величины никогда как числа с плавающей запятой. Значения кодируются в специальном, достаточно компактном и расширяемом двоичном формате. Стандартный кодек предназначен для выбора по умолчанию для канала связи во Flutter. Что касается JSON, каналы сообщений Dart, созданные с использованием стандартного кодека, имеют тип BasicMessageChannel.
Каналы методов
— это каналы платформы, предназначенные для вызова нативного кода из flutter-приложения.
Кодеки методов
:
StandardMethodCodec
делегирует кодирование значений полезной нагрузки (payload) вStandardMessageCodec
. Поскольку последний является расширяемым, то же самое можно сказать и о первом.JSONMethodCodec
делегирует кодирование значений полезной нагрузки (payload) вJSONMessageCodec
.
Каналы событий
— это специализированные каналы платформы, предназначенные для использования в случае представления событий платформы Flutter в виде потока Dart. Работает как обычный Stream
Debug
(JIT
) для разработкиRelease
(AOT
) для публикации приложенияProfile
(AOT
) для анализа производительности
Package
написан только на dartPlugin
использует dart и специфичный код для платформы
FFI Plugin
- плагин, в котором для написания специфичных платформенных частей используется Dart FFI. Позволяет запустить код на C / C++
Ticker
проситSchedulerBinding
зарегистрировать обратный вызов и сообщитьFlutter Engine
, что надо разбудить его, когда появится новый обратный вызов.- Когда
Flutter Engine
готов, он вызываетSchedulerBinding
через запросonBeginFrame
. SchedulerBinding
обращается к списку обратных вызововticker
и выполняет каждый из них.- Каждый
tick
перехватывается "заинтересованным" контроллером для его обработки. - Если анимация завершена, то
ticker
"отключён", иначеticker
запрашиваетSchedulerBinding
для планирования нового обратного вызова. - ...
Tween animation
. Начало, конец, время, скорость заранее определенноPhysics-based animation
. Имитируют реальное поведение
Tween
- это объект, который описывает между какими значениями анимируется виджет и отвечает за вычисление текущего значения анимации
Implicit Animations
- это наборImplicitly Animated Widgets
, которые анимируются самостоятельно при их перестройке с новыми аргументами. (AnimatedAlign
,AnimatedContainer
,AnimatedPadding
и т.д.)Explicit Animations
- это набор элементов управления анимационными эффектами. Предоставляют куда больше контроля над анимацией, чемImplicit Animations
. Для использования необходимо подмешать к стейту вашего виджетаSingleTickerProviderStateMixin
/TickerProviderStateMixin
, создатьAnimationController
и зависящие от негоAnimation
, передать анимацию вTransition Widget
(AlignTransition
,DecoratedBoxTransition
,SizeTransition
и т.д.)SingleTickerProviderStateMixin
/TickerProviderStateMixin
создаетTicker
Ticker
вызывает callback на каждый фрейм анимации
AnimationController
пределяет все фреймы анимации - управляет анимацией (forward, reverse, repeat, stop, reset и т.д.)
Animation
отдает текущее значение анимации, а также позволяет подписаться на обновления значения/статуса анимации
- Некоторые внешние события приводят к необходимости обновления отображения.
Schedule Frame
отправляется кFlutter Engine
- Когда
Flutter Engine
готов приступить к обновлению рендеринга, он создаетBegin Frame
запрос - Этот
Begin Frame
запрос перехватываетсяFlutter Framework
, который выполняет задачи, связанные в основном сTickers
(например, анимацию) - Эти задачи могут повторно создать запрос для более поздней отрисовки (пример: анимация не закончила своё выполнение, и для завершения ей потребуется получить еще один
Begin Frame
на более позднем этапе) - Далее
Flutter Engine
отправляетDraw Frame
, который перехватываетсяFlutter Framework
, который будет искать любые задачи, связанные с обновлением макета с точки зрения структуры и размера - После того, как все эти задачи выполнены, он переходит к задачам, связанным с обновлением макета с точки зрения отрисовки
- Если на экране есть что-то, что нужно нарисовать, то новая сцена для визуализации отправляется в
Flutter Engine
, который обновит экран - Затем
Flutter Framework
выполняет все задачи, которые будут выполняться после завершения рендеринга (PostFrame callbacks
), и любые другие последующие задачи, не связанные с рендерингом - …
- Ограничения спускаются вниз по дереву, от родителей к детям.
- Размеры идут вверх по дереву от детей к родителям.
- Родители устанавливают положение детей.
BuildOwner
— менеджер сборки и обновления дерева элементов. Он активно участвует в двух фазах — сборки и завершения сборки. Поскольку BuildOwner
управляет процессом сборки дерева, в нем хранятся списки неактивных элементов и списки элементов, нуждающихся в обновлении.
Методы:
scheduleBuildFor
даёт возможность пометить элемент как нуждающийся в обновлении.lockState
защищает элемент от неправильного использования, утечек памяти и пометки на обновления в процессе уничтожения.buildScope
осуществляет пересборку дерева. Работает с элементами, которые помечены как нуждающиеся в обновлении.finalizeTree
завершает построение дерева. Удаляет неиспользуемые элементы и осуществляет дополнительные проверки в режиме отладки — в том числе на дублирование глобальных ключей.reassemble
обеспечивает работу механизмаHotReload
. Этот механизм позволяет не пересобирать проект при изменениях, а отправлять новую версию кода наDartVM
и инициировать обновление дерева.
PipelineOwner
— менеджер сборки, который занимается работой с деревом отображения.
Garbage Collector
- это алгоритм, наблюдает за ссылками и очищает память с целью предотвращения её переполнения.
(!) В процессе сборки мусора слой Dart Framework
создает канал взаимодействия со слоем Flutter Engine
, посредством которого узнает о моментах простоя приложения и отсутствия пользовательского взаимодействия. В эти моменты Dart Framework
запускает процесс оптимизации памяти, что позволяет сократить влияния на пользовательский опыт и стабильность приложения.
Используемый объём памяти можно разделить на два пространства: активное и неактивное. Новые объекты располагаются в активной части, где по мере её заполнения, живые объекты переносятся из активной области памяти в неактивную, игнорируя мёртвые объекты. Затем неактивная половина становится активной. Этот процесс имеет цикличный характер.
Сборщик старого мусора (Parallel Marking and Concurrent Sweeping)
- Осуществляется обход дерева объектов, используемые объекты помечаются специальной меткой.
- Во время второго этапа происходит повторный проход по дереву объектов, в ходе которого непомеченные в первом этапе объекты перерабатываются
- Все метки стираются
Platform Task Runner
: Основной поток платформы. Здесь выполняется код плагинов. Для получения дополнительной информации см. документацию по UIKit для iOS или документацию по MainThread для Android. Этот поток не отображается в наложении производительности.UI Task Runner
: Поток UI выполняет код Dart в виртуальной машине Dart VM. Этот поток включает в себя код, написанный вами, и код, выполняемый фреймворком Flutter от имени вашего приложения. Когда ваше приложение создает и отображает сцену, поток UI создает дерево слоев - легкий объект, содержащий команды рисования, не зависящие от устройства, и отправляет дерево слоев в растровый поток для отображения на устройстве. Не блокируйте этот поток! Показан в нижней строке оверлея производительности.Raster Task Runner
: Растровый поток получает дерево слоев и отображает его, обращаясь к GPU (графическому процессору). Вы не можете напрямую обращаться к растровому потоку или его данным, но если этот поток работает медленно, то это результат того, что вы сделали в коде Dart. В этом потоке работают графические библиотеки Skia и Impeller. Они показаны в верхней строке оверлея производительности. Ранее этот поток был известен как "GPU-поток", поскольку он выполняет растеризацию для GPU. Однако он выполняется на центральном процессоре. Мы переименовали его в "растровый поток", поскольку многие разработчики ошибочно (но вполне обоснованно) полагали, что этот поток работает на GPU.IO Task Runner
: Выполняет дорогостоящие задачи (в основном ввод-вывод), которые в противном случае блокировали бы работу потоков пользовательского интерфейса или растровых потоков. Этот поток не отображается в оверлее производительности.
Архитектура - это набор решений по организации программы. Таких, как деление программы на слои, построение связей между ними, управление состоянием, связь с UI. Хорошая архитектура делает слои в приложении слабо связанными, что упрощает внесение изменений, повышает тестируемость кода, упрощает систему
Чистая архитектура - архитектура, которая следует SOLID
и делится на три независимых слоя:
Data (datasources, models, repositories)
получение данных извнеDomain (entities, repositories interfaces, usecases)
бизнес правилаPresentation (bloc, pages, widgets)
отображение
Vanilla
Плюсы
- Низкий порог вхождения.
- Не требуются сторонние библиотеки.
Минусы
- При изменении состояния виджета дерево виджетов каждый раз целиком пересоздается.
- Нарушает принцип единственной ответственности. Виджет отвечает не только за создание UI, но и за загрузку данных, бизнес-логику и управление состоянием.
- Решения о том как именно отображать текущее состояние принимаются прямо в UI коде. Если состояние станет более сложным, то читаемость кода сильно понизится.
Использование:
- Widget State
BLoC
Плюсы
- Четкое разделение ответственности
- Предсказуемые преобразования Event to State
- Реактивность. Нет необходимости в вызове дополнительных методов
Минусы
- Обязательность состояния в Bloc-е
- Частые изменения библиотеки
- Зависимость от сторонней библиотеки
Использование:
- Widget State
- App State
Redux
Плюсы
- Единое состояние
- Все экшены доступны всем
Минусы
- Единое состояние
- Зависимость от сторонней библиотеки
- Большое количество boilerplate кода
- Все экшены доступны всем
Использование:
- Widget State
- App State
Provider
Плюсы
- Скоупы на поддеревья
- Flutter ориентирован
- Нет статики
- Готовые провайдеры
Минусы
- Завязка на фреймворк
- Только 1 провайдер одного типа
- Зависимость от сторонней библиотеки
Использование:
- App State
- Частично DI
Riverpod
Плюсы:
- Скоупы на поддеревья
- Flutter ориентирован
- Нет статики
- Готовые провайдеры
Минусы:
- Зависимость от сторонней библиотеки
- Циклические зависимости падают в runtime-е
Использование:
- Widget State
- App State
- DI
Dependency injection (DI)
- это механизм, который позволяет сделать взаимодействующие в приложении объекты слабосвязанными с помощью интерфейсов. Это делает всю систему более гибкой, более адаптируемой и расширяемой
Части:
Model
содержит в себе всю логику приложения, она хранит и обрабатывает данные, при этом не взаимодействуя с пользователем напрямуюView
отображает данные, которые ему передалиViewModel
связывает модель и представление (передаёт данные между ними)
Использование:
- Используется в ситуации, когда возможно связывание данных без необходимости ввода специальных интерфейсов представления (т.е. отсутствует необходимость реализовывать IView);
- Частым примером является технология WPF.
MVC
Части:
Model
содержит в себе всю логику приложения, она хранит и обрабатывает данные, при этом не взаимодействуя с пользователем напрямуюView
отображает данные, которые ему передалиController
перехватывает событие извне и в соответствии с заложенной в него логикой, реагирует на это событие изменяя Mодель, посредством вызова соответствующего метода. После изменения Модель использует событие о том что она изменилась, и все подписанные на это события Представления, получив его, обращаются к Модели за обновленными данными, после чего их и отображают
Использование:
- Используется в ситуации, когда невозможно связывание данных (нельзя использовать Binding);
MVP
Части:
Model
содержит в себе всю логику приложения, она хранит и обрабатывает данные, при этом не взаимодействуя с пользователем напрямуюView
отображает данные, которые ему передалиPresenter
подписывается на события представления, по запросу изменяет модель, обновляет представление
Использование:
- Используется в ситуации, когда связь между представление и другими частями приложения невозможна (и Вы не можете использовать MVVM или MVP);
Navigator
- Идёт из коробки
Go Router
- Парсинг пути и параметров запроса (например, "user/:id")
- Поддержка deep-links
- Поддержка перенаправления - вы можете перенаправить пользователя на другой URL-адрес в зависимости от состояния приложения
- Именованные роуты
Auto Route
- Парсинг пути и параметров запроса (например, "user/:id")
- Поддержка deep-links
- Защищённые маршруты
- Именованные роуты
- Разные варианты анимаций
Нереляционные (NoSQL):
Hive
Плюсы:
- Реализация на чистом Dart, без платформенного кода, за счёт чего одинаково работает на разных платформах
- Прост в использовании
- Быстрая запись / чтение
- Мало boilerplate кода
- Наличие кодогенерации
- Поддерживается на всех платформах
Минусы:
- Связи между объектами нужно поддерживать вручную
- Ограничение по количеству адаптеров для объектов - 224
- Ограничение по количеству полей в адаптере - 255
- Плохо подходит для работы с большим объемом данных
- Выгружает всю бд в память
- Нельзя получить доступ к box-у, созданному в другом изоляте
- Наличие кодогенерации
- Нет миграций
Использование:
- Небольшой объём данных
- Необходимость в сохранении своих дата моделей
Shared Preferences
Плюсы:
- Прост в использовании
- Синхронное чтение из кэша в памяти
- Поддерживается на всех платформах
Минусы:
- Разные реализации для Android/iOS и других платформ
- Обращение к платформенному коду
- Не гарантируется запись на диск после успешного выполнения метода
- Нельзя сохранять сложные объекты из коробки
Использование:
- Небольшой объём данных
- Необходимость в быстром внедрении решения
- Сохранение настроек приложения в виде примитивных данных
Реляционные (SQL):
SQFLite
Плюсы
- Есть миграции
- Поддерживает связи между сущностями
- Не выгружает бд в оперативную память
- Возможность написания сложных запросов на SQL
Минусы:
- Сложен в использовании
- Не поддерживается в Web, Linux, Windows
- Скорость работы ниже чем у NoSQL
- На разных OS и версиях могут быть разные версии SQLite
Использование:
- Большой объём данных
- Хранение сложно структурированных данных
Drift
- Есть миграции
- Поддерживает связи между сущностями
- Не выгружает бд в оперативную память
- Возможность написания сложных запросов на SQL
Плюсы
- Есть миграции
- Быстрый
- Поддерживается на всех платформах
Минусы:
- Сложен в использовании
- Скорость работы ниже чем у NoSQL
Использование:
- Большой объём данных
- Хранение сложно структурированных данных
Модульный тест
тестирует одну функцию, метод или класс. Его цель - проверить правильность работы определенной функции, метода или класса. Внешние зависимости для тестируемого модуля обычно передаются как параметр.Виджет тест
тестирует один виджет. Цель такого теста — убедиться, что пользовательский интерфейс виджета выглядит и взаимодействует, как запланировано. Тестирование виджета происходит в тестовой среде, которая обеспечивает контекст жизненного цикла виджета. Также тестируемый виджет должен иметь возможность получать действия и события пользователя и отвечать на них .Интеграционный тест
тестирует все приложение или его большую часть. Цель интеграционного теста — убедиться, что все тестируемые виджеты и сервисы работают вместе, как ожидалось. Кроме того, вы можете использовать интеграционные тесты для проверки производительности вашего приложения. Как правило, интеграционный тест выполняется на реальном устройстве или эмуляторе.
TDD
— это методика разработки приложений, при которой сначала пишется тест, покрывающий желаемое изменение, а затем — код, который позволит пройти тест.
Порождающие. Отвечают за удобное и безопасное создание новых объектов или даже целых семейств объектов.
- Factory Method (Фабричный Метод). Порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.
- Abstract Factory (Абстрактная Фабрика). Порождающий паттерн проектирования, который позволяет создавать семейства связанных объектов, не привязываясь к конкретным классам создаваемых объектов.
- Builder (Строитель). Порождающий паттерн проектирования, который позволяет создавать сложные объекты пошагово. Строитель даёт возможность использовать один и тот же код строительства для получения разных представлений объектов.
- Prototype (Прототип). Порождающий паттерн проектирования, который позволяет копировать объекты, не вдаваясь в подробности их реализации.
- Singleton (Одиночка). Порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.
Структурные. Отвечают за построение удобных в поддержке иерархий классов.
- Adapter (Адаптер). Структурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе.
- Bridge (Мост). Структурный паттерн проектирования, который разделяет один или несколько классов на две отдельные иерархии — абстракцию и реализацию, позволяя изменять их независимо друг от друга.
- Composite (Компоновщик). Структурный паттерн проектирования, который позволяет сгруппировать множество объектов в древовидную структуру, а затем работать с ней так, как будто это единичный объект.
- Decorator (Декоратор). Структурный паттерн проектирования, который позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обёртки».
- Facade (Фасад). Структурный паттерн проектирования, который предоставляет простой интерфейс к сложной системе классов, библиотеке или фреймворку.
- Flyweight (Легковес). Паттерн проектирования, который позволяет вместить бóльшее количество объектов в отведённую оперативную память. Легковес экономит память, разделяя общее состояние объектов между собой, вместо хранения одинаковых данных в каждом объекте.
- Proxy (Заместитель). Структурный паттерн проектирования, который позволяет подставлять вместо реальных объектов специальные объекты-заменители. Эти объекты перехватывают вызовы к оригинальному объекту, позволяя сделать что-то до или после передачи вызова оригиналу.
Поведенческие. Решают задачи эффективного и безопасного взаимодействия между объектами программы.
- Chain of Responsibility (Цепочка Обязанностей). Поведенческий паттерн проектирования, который позволяет передавать запросы последовательно по цепочке обработчиков. Каждый последующий обработчик решает, может ли он обработать запрос сам и стоит ли передавать запрос дальше по цепи.
- Command (Команда). Поведенческий паттерн проектирования, который превращает запросы в объекты, позволяя передавать их как аргументы при вызове методов, ставить запросы в очередь, логировать их, а также поддерживать отмену операций.
- Iterator (Итератор). Поведенческий паттерн проектирования, который даёт возможность последовательно обходить элементы составных объектов, не раскрывая их внутреннего представления.
- Mediator (Посредник). Поведенческий паттерн проектирования, который позволяет уменьшить связанность множества классов между собой, благодаря перемещению этих связей в один класс-посредник.
- Memento (Снимок). Поведенческий паттерн проектирования, который позволяет сохранять и восстанавливать прошлые состояния объектов, не раскрывая подробностей их реализации.
- Observer (Наблюдатель). Поведенческий паттерн проектирования, который создаёт механизм подписки, позволяющий одним объектам следить и реагировать на события, происходящие в других объектах.
- State (Состояние). Поведенческий паттерн проектирования, который позволяет объектам менять поведение в зависимости от своего состояния. Извне создаётся впечатление, что изменился класс объекта.
- Strategy (Стратегия). Поведенческий паттерн проектирования, который определяет семейство схожих алгоритмов и помещает каждый из них в собственный класс, после чего алгоритмы можно взаимозаменять прямо во время исполнения программы.
- Template Method (Шаблонный Метод). Поведенческий паттерн проектирования, который определяет скелет алгоритма, перекладывая ответственность за некоторые его шаги на подклассы. Паттерн позволяет подклассам переопределять шаги алгоритма, не меняя его общей структуры.
- Visitor (Посетитель). Поведенческий паттерн проектирования, который позволяет добавлять в программу новые операции, не изменяя классы объектов, над которыми эти операции могут выполняться.