Repositories: Flutter Interview, Flutter Roadmap, Flutter Acrticles, Flutter Best Packages, Flutter Tools
- General
- Dart
- final and const
- JIT and AOT
- Hot Restart and Hot Reload
- HashCode
- Extension
- Mixin
- Sound Null Safety
- Type system
- Late
- Generics
- Dart VM
- Zones
- Types of errors
- Naming rules
- Never
- Covariant
- Annotations
- int8, uint8, int16, uint16...
- Future
- Future Constructors
- Await under the hood
- Event Loop
- Completer
- Stream
- Generators (sync* / async*)
- Multithreading in Dart and Flutter
- Isolate
- Compute
- Multithreading issues
- Flutter
- Stateless and Stateful widgets
- Stateful Widget Lifecycle
- BuildContext
- InheritedWidget
- Trees
- Widget
- Element
- RenderObject
- Types of widgets
- Types of elements
- The life cycle of an Element
- GlobalKeys
- LocalKeys
- The Flutter device is under the hood
- Execution model in Flutter
- CustomPaint
- WidgetsFlutterBinding
- Bindings
- Platform Channels
- Build Modes
- Package and Plugin
- FFI Plugin
- Animation stages
- Types of animations
- What is a Tween
- Tween animations
- Frame construction
- Layout calculation
- BuildOwner
- PipelineOwner
- Garbage Collector
- Task Runners
- Architecture
- Testing
- Development patterns
Object-oriented programming
— this is a programming paradigm based on the representation of a program as a collection of objects, each of which is an instance of a certain class, and classes form an inheritance hierarchy.
Abstraction
Modeling the interactions of entities in the form of abstract classes and interfaces to represent the systemEncapsulation
Combining data and methods working with it in a classInheritance
Creating new classes based on existing onesPolymorphism
Using objects with the same interface without information about the type and internal structure of the object
Single Responsibility Principle
The class should only be responsible for one thing.Open-Closed Principle
Software entities should be open for expansion, but closed for modification.Liskov Substitution Principle (Barbara Liskov Substitution Principle)
The inheriting class should complement, not replace, the behavior of the base class.Interface Segregation Principle
Clients should not implement logic that they do not use.Dependency Inversion Principle
The modules of the upper levels should not depend on the modules of the lower levels. Classes of both upper and lower levels should depend on the same abstractions (and abstractions should not know about the details).
Feature
The beginning of a new feature
. Creating a new branchfeature/future_name
fromdevelop
Completion of the feature
. Mergefeature/future_name
intodevelop
, deletefeature/future_name
The beginning of the release
. Creating a release branchrelease/vX.X.X
, responding from thedevelop
branchRelease Completion
. The release branchrelease/vX.X.X
merges intomaster
, the release is tagged, the release branch merges intodevelop
, the release branch is deleted
Fix
The beginning of correction
. From themaster
branch, create ahotfix/fix_name
Completion of correction
. From thehotfix/fix_name
branch, the fix is merged intodevelop
andmaster
, the fix branch is deleted
Data structures are needed to store data in a suitable way
Arrays
Stacks
(LIFO - last in, first out)Queues
(FIFO - first in, first out)Linked lists
Trees
Graphs
Hash tables
Imperative style
- we describe how to achieve the desired resultDeclarative style
- we describe ** what kind of result ** we need
- A
stack
is an area of RAM that stores temporary data such as local variables and function return addresses. The amount of memory allocated for the stack is limited. The stack works in LIFO order - A
heap
is an area of RAM in which data created during program execution is stored. The heap is used to dynamically allocate memory for objects that can change size during program execution. The heap size is set when the application is launched, but, unlike the stack, it is limited only physically. Memory allocation on the heap is slower than on the stack.
DAO (Data Access Object, data access Object)
is an abstract interface to some type of database or other storage mechanismDTO (Data Transfer Object, data transfer object)
is an object for transferring data (objects without behavior) between layersVO (Value Object, value object)
⎼ is an object without special methods, having a set of properties (fields) of primitive data types, or also a Value objectBO (Business Object, business object)
is an object that represents an entity from a certain "domain", that is, the industry for which the application was developed
DI
- passing class dependencies through constructor parametersService Locator
- singleton / class with a set of static methods
The Service Locator
can be accessed from anywhere in the code. This is its main disadvantage
final
is calculated in runtime. Only the instance value is constant. When using a final instance, a new memory area is allocated in memory, even if the object value is identical.const
is calculated at compile time. Not only the value is constant, but also the instance itself. When using a const variable, a new memory area is not allocated, but a reference to an existing instance is used.
Just-in-time (JIT) compilation
is a type of compilation that is performed directly while the program is running, which significantly speeds up the development cycle. But it should be borne in mind that the program may slow down and run slowerAhead-of-time (AOT) compilation
is a type of compilation that is fully executed before running a program. The Dart code is converted to native machine code, which is then packaged into a binary file with the extension.so
forAndroid
or.dylib
foriOS
.AOT
Takes longer thanJIT
, but as a result, the program runs much faster.
Hot Reload
loads the changes to theDart VM
and reboots the widget tree, saving the state. Does not restartmain()
andinitState()
Hot Restart
loads the changes to theDart VM
and restarts the entire application. Restartsmain()
andinitState()
. The state is not saved
A hash code
is a getter for any object that returns an int
. It is needed when saving an object to map
or set
. Hash codes must be the same for objects that are equal to each other according to the == operator
int get hashCode => Object.hash(runtimeType, ..., ...);
Extension
is a syntactic sugar that allows you to extend an existing class (add methods, operators, setters and getters)
A mixin
is a multiple inheritance mechanism that allows classes to use the functionality of other classes without explicit inheritance.
Mixins in Dart are defined by the keyword mixin
. They can contain methods, fields, and getters/setters, but they cannot have constructors. Instead, mixins are initialized automatically when they are applied to a class. The with
operator is used to use mixins
If the mixins have a method with the same name, then the implementation that is specified in the last mixin will remain. Since mixins will override this method
Sound Null Safety
is an addition to the Dart language that strengthens the type system by separating types that allow the value of Null
from types that do not allow the value of Null
. This allows developers to prevent errors related to Null
.
With the advent of null safety in Dart, the hierarchy of classes and interfaces has been changed to accommodate new type safety requirements. Here are the main changes:
- Adding non-nullable types:
- Non-nullable types indicate that the value cannot be null.
- All existing types have been divided into non-nullable and nullable versions. For example,
int
becameint
(non-nullable) andint?
(nullable)
- All existing types have been divided into non-nullable and nullable versions. For example,
-
The new root of the hierarchy is "Object?":
- Has a new root class
Object
been introduced?, which can be null. In previous versions of Dart, the root class was
Object`
- Has a new root class
-
Changes in the error hierarchy:
- A new class
NullThrownError
has been introduced, which represents an error that occurs when trying to throw anull
exception
late
andrequired
:
- The keywords
late
andrequired
have been introduced to indicate variables that can be initialized later and must be initialized when declaring, respectively.
Late
is a keyword in dart that allows you to declare a non-nullable variable without setting a value for it. The value is initialized only when we access it.
Generics
are parameterized types. They allow the program to get away from being tightly bound to certain types, define the functionality so that it can use data of any type and ensure their security. Generalizations also reduce code repeatability and give you the opportunity to provide a single interface and implementation for many types.
Dart VM (Dart virtual machine)
- Dart runtime environment
Components:
Execution Environment
Garbage Collector
Basic libraries and native methods
System debugging
Profiler
ARM Architecture Simulator
A zone is a mechanism that allows you to manage and handle errors and other events that occur in certain areas of the code.
- Protecting your application from termination due to an unhandled exception
- Association of data known as
zone-local values
with individual zones - Redefining a limited set of methods, such as print() and scheduleMicrotask(), inside part or all of the code
- Perform the operation every time the code enters or exits the zone. These operations may include starting or stopping a timer or saving a stacktrace.
Exception
is a general class for exceptions that usually occur due to errors in the program, and they can be handled and recovered from:
- DeferredLoadException
- FormatException
- IntegerDivisionByZeroException (marked as deprecated)
- IOException
- FileSystemException
- PathNotFoundException
- HttpException
- RedirectException
- ProcessException
- SignalException
- SocketException
- StdinException
- StdoutException
- TlsException
- CertificateException
- HandshakeException
- WebSocketException
- IsolateSpawnException
- TimeoutException
- NullRejectionException
Error
is a class for errors that usually cannot be repaired, and they indicate serious problems in the program or system:
- OSError
- ArgumentError
- IndexError
- RangeError
- AssertionError
- AsyncError
- ConcurrentModificationError
- JsonUnsupportedObjectError
- JsonCyclicError
- NoSuchMethodError
- OutOfMemoryError
- RemoteError
- StackOverflowError
- StateError
- TypeError
- UnimplementedError
- UnsupportedError
- Variables and constants -
lowerCamelCase
- Classes, mixins, enums -
UpperCamelCase
- Files -
snake_case
Never
is a type, meaning that no type is allowed and Never
itself cannot be created. It is used as a return type in case of a guaranteed error.
Covariant
is a keyword in dart that indicates that the type of the return value can be changed to a narrower type in the subclass.
Annotations
are syntactic metadata that can be added to the code. In other words, it is an opportunity to add additional information to any component of the code, for example, to a class or method. Annotations always start with the character @
(@override
, @required
). Any class can serve as an annotation if a const constructor is defined in it.
Specifier | Common Equivalent | Bytes | Minimum value | Maximum value |
---|---|---|---|---|
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
is a wrapper over the result of an asynchronous operation. The Future code is NOT executed in parallel, but is executed in a sequence defined by the Event Loop.
Future states:
- Incomplete - the operation is not completed
- Completed with Result - the operation was completed successfully
- Completed with Error - the operation was completed with an error
Future(FutureOr<T> computation())
: Creates a future object that uses the Timer.run method to run the computation function asynchronously and returns its result.FutureOr<T>
: specifies that the calculation function should return either a Future object or an object of type T. For example, to get a Future object, the calculation function should return either a Future object or an int objectFuture.delayed(Duration duration, [FutureOr<T> computation()])
: creates a Future object that runs after the time delay specified through the first Duration parameter. The second optional parameter indicates the function that runs after this delay.Future.error(Object error, [StackTrace stackTrace])
: Creates a Future object that contains information about the error that occurred.Future.microtask(FutureOr<T> computation())
: Creates a Future object that uses the scheduleMicrotask function to run the computation function asynchronously and return its result.Future.sync(FutureOr<T> computation())
: Creates a Future object that contains the result of an immediately called computation function.Future.value([FutureOr<T> value])
: creates a Future object that contains the value value.
Under the hood, await
moves all subsequent code to then
at Future
, which we are waiting for
The Event Loop
is an eternal loop that executes all incoming tasks to the isolate.
It has two FIFO task queues:
MicroTask Queue
It is used for very short actions that must be performed asynchronously, immediately after completing any instruction before passing control back to the Event Loop. The MicroTask queue takes precedence over the Event queue
Event Queue
It is used for scheduling operations that receive results from external events (I/O operations, gestures, drawing, timers, threads)
The Completer
allows you to supply a Future, send an execution event, or an error event. This can be useful when you need to make a Future chain and return the result.
A stream
is a sequence of asynchronous events. Stream tells you that there is an event and when it will be ready
Single subscription
is a type of stream where there can be only one subscriber.Broadcast
is a type of stream where there can be many subscribers. At the same time, Broadcast streams give their data regardless of whether someone is subscribed to them or not. Stream subscribers receive events only from the moment of subscription, and not from the moment of the start of the stream`s life
Generator
this is a keyword that allows you to create a sequence of values using yield
- sync* is a synchronous generator. Returns
Iterable
- async* is an asynchronous generator. Returns
Stream
Dart
is a single—threaded programming language. It executes one instruction at a time. But at the same time, we can run the code in a separate thread using Isolate
Isolate
is a lightweight process (execution thread) that runs in parallel with other threads and processes in the application. Each Isolate
in Dart has its own instance of the Dart virtual machine, its own memory and is controlled using its own Event Loop
.
Compute
is a function that creates an isolate and runs the passed code.
Deadlock
— each of the threads is waiting for events that other threads can provideRace conditions
— a manifestation of the nondeterminism of the program executor with different relative order of execution of commands in different threadsLock Contention
— the main time of the stream is spent not performing useful work, but waiting for a resource blocked by another streamLive Lock
— the thread captures the resource, but after it is convinced that it cannot complete the work, it releases the resource, canceling the results
StatelessWidget
is a widget that has no state and does not change its properties during the operation of the application. They can only be changed through external events that occur in the parent widgets.StatefulWidget
is a widget that stores the state, during the operation of the application it can change it dynamically usingsetState()
.
createState()
is called once and creates a changeable state for this widget at a specified location in the treemounted is true
initState()
is called once during initializationdidChangeDependencies()
is called once after initialization and further upon notifications from the Inherited widgets at the top of the tree on which the widget dependsbuild()
is called every time you redrawdidUpdateWidget(Widget oldWidget)
is called every time the widget configuration is updatedsetState()
is called imperatively for redrawingdeactivate()
is called when a previously active element is moved to the list of inactive elements, while being removed from the treedispose()
is called when this object is removed from the tree forevermounted is false
The BuildContext
is the interface that implements the Element
.
BuildContext
can be useful when needed:
- Get a reference to the
RenderObject
object corresponding to the widget (or, if the widget is not a Renderer, then a descendant widget) - Get the size of the
RenderObject
- Access the tree and get the nearest parent
InheritedWidget
. This is used by virtually all widgets that usually implement the of method (for example,MediaQuery.of(context)
,Theme.of(context)
etc.)
An InheritedWidget
is a widget that provides its descendants with the ability to interact with the data stored in it. Solves the problem with passing data through constructors. It can notify widgets at the bottom of the tree about changes in its own data, thereby provoking their redrawing.
To get an Inherited widget, call context.dependOnInheritedWidgetOfExactType<T extends InheritedWidget>()
in didChangeDependencies()
The complexity of the operation of obtaining InheritedWidget - O(1). This speed is achieved due to the fact that Inherited widgets are stored as a hash table in the Element
.
- The
Widget Tree
consists ofWidgets
that are used to describe the user interface - The
Element Tree
consists ofElements
that manage the widget lifecycle and link widgets and rendering objects. - The
Render Tree
consists ofRenderObject
, which are used to determine the size, position, geometry, and areas of the screen that can be affected by gestures
A Widget
is an immutable description of a part of the user interface. The widget is associated with the element that controls the rendering. Widgets form a structure, not a tree
An Element
is a mutable representation of a widget at a specific location in the tree. They manage the lifecycle, link widgets and rendering objects.
RenderObject
is a mutable object of the visualization tree. It has a parent object, as well as a data field that the parent object uses to store specific information about the object itself, for example, its position. This object is responsible for rendering, accounting for sizes and constraints, listening and processing clicks. If necessary, it is marked as dirty
. It is redrawn using its own layer
method
Proxies
are widgets that store some information and make it available to posterity. These widgets are not directly involved in shaping the user interface, but are used to get the information they can provide.
InheritedWidget
ParentDataWidget
(LayoutId
,Flexible
,KeepAlive
, etc.)NotificationListener
Renderer
are widgets that are directly related to the layout of the screen, as they determine the size, position, and rendering
Row
Column
Stack
Padding
Align
Opacity
Component
are widgets that do not directly provide final information related to dimensions, positions, appearance, but rather data (or hints) that will be used to obtain that final information.
RaisedButton
Scaffold
Text
GestureDetector
Container
A ComponentElement
is a layout element that does not explicitly contain drawing/display logic. There is a build()
method that returns the widget. It is formed only when creating widgets StatelessWidget
, StatefulWidget
, InheritedWidget
.
ProxyElement
StatelessElement
StatefulElement
RenderObjectElement
is a display element that is explicitly involved in drawing components on the screen. Contains a RenderObject
and inherits from the Element
class. It is formed when creating widgets Padding
, Column
, Row
, Center
, etc.
LeafRenderObjectElement
ListWheelElement
MultiChildRenderObjectElement
RootRenderObjectElement
SingleChildRenderObjectElement
SliverMultiBoxAdaptorElement
SlottedRenderObjectElement
- The element is created by calling the
Widget.createElement
method and is configured by the widget instance from which the method was called. - Using the
mount
method, the created element is added to the specified position of the parent element. When calling this method, child widgets are also associated and objects of the rendering tree are mapped to the elements. - The widget becomes active and should appear on the screen.
- If the widget associated with an element changes (for example, if the parent element has changed), there are several scenarios. If the new widget has the same
runtimeType
andkey
, then the element is associated with it. Otherwise, the current element is removed from the tree, and a new element is created and associated with the new widget. - If the parent element decides to delete a child element, or an intermediate one between them, this will delete the rendering object and move this element to the inactive list, which will deactivate the element.
- When an item is considered inactive, it is not on the screen. An element can be inactive only until the end of the current frame, if it remains inactive during this time, it is dismantled, after that it is considered non-existent and will no longer be included in the tree.
- When you re-include elements in the tree, for example, if an element or its ancestors have a global key, it will be removed from the list of inactive elements, the
activate
method will be called, and the render object associated with this element will again be embedded in the rendering tree. This means that the item should appear on the screen again.
GlobalKeys
are keys that provide access to widgets. For stateful widgets, global keys also provide access to the state. Allow widgets to change parents anywhere in the application without losing state. They must be unique to the entire application.
LocalKeys
are the keys that are needed to identify widgets in a collection with the same values and must be unique among widgets with the same parent widget. They can be used for tests:
ValueKey
is a key that uses a value of a certain type to identify itself. Overrides the comparison operator. If the value is the same, then the keys are the sameuniqueKey
is a key that is equal only to itselfObjectKey
is the key that is used to bind the widget identifier to the identifier of the object used to create this widget
The framework level
is everything we are working with at the time of writing the application, and all the service classes that allow us to interact with the engine level. Everything related to this level is written on Dart. The Flutter Framework
interacts with the Flutter Engine
through an abstraction layer called Window
The engine level
is a lower level than the framework level, it contains classes and libraries that allow the framework level to work. Including the Dart virtual machine
, Skia
and so on.
Platform level
— specific mechanisms related to a specific launch platform.
The Flutter Engine
notifies the Flutter Framework
when:
- The event of interest occurs at the device level (orientation change, settings change, memory problem, application operation status...)
- Some kind of event occurs at the glass level (gesture)
- The platform channel sends some data
- But also mostly when the Flutter Engine is ready to render a new frame
- A new process is created and launched —
Thread (Isolate)
. This is the only process in which your application will run. - Two queues with
MicroTask
andEvent
are initialized, the queue type isFIFO
(note: first in first out, i.e. messages that arrived earlier will be processed earlier) - The
main()
function is executed - The
Event Loop
is started. It controls the order of execution of your code, depending on the contents of two queues:MicroTask
andEvent
. It is an "endless" cycle. - The
Event Loop
checks theMicroTask
andEvent
with a certain frequency. If there is something in theMicroTask
, then it is executed before theEvent
queue.
CustomPaint
is a class that creates a "canvas" for drawing. The paint
method uses canvas
as arguments, which allows you to draw various shapes
WidgetsFlutterBinding
is a specific implementation of application binding based on the widget infrastructure. In essence, it is the glue connecting the framework and the Flutter engine. The WidgetsFlutterBinding
consists of many links: GestureBinding
, ServicesBinding
, SchedulerBinding
, PaintingBinding
, SemanticsBinding
, RendererBinding
, WidgetsBinding
.
The scheduleAttachRootWidget
method is a deferred implementation of attachRootWidget
. This method belongs to WidgetsBinding
. The description says that it attaches the passed widget to the renderViewElement
— the root element of the element tree.
The scheduleWarmUpFrame
method belongs to SchedulerBinding
and is used to schedule the frame to start as soon as possible without waiting for the Vsync
system signal.
Bindings
are classes for exchanging data between the Flutter Framework
and the Flutter Engine
. Each binding is responsible for processing a set of specific tasks, actions, and events grouped by activity area.
BaseBinding
is a basic abstract class, then let`s look at specific implementations of bindings. Among them we will see:
ServicesBinding
is responsible for redirecting messages from the current platform to the message data handler (BinaryMessenger
);
PaintingBinding
is responsible for linking to the rendering library.
RenderBinding
is responsible for the connection between the rendering tree and the Flutter engine.
WidgetBinding
is responsible for the connection between the widget tree and the Flutter engine.
SchedulerBinding
— scheduler for the next tasks, such as:
- calls of incoming callbacks that the system initiates in
Window.onBeginFrame
— for example, ticker events and animation controllers; - calls of continuous callbacks initiated by the
Window.onDrawFrame
system, for example, events for updating the display system after incoming callbacks have worked out; - post-frame callbacks that are called after continuous callbacks, before returning from
Window.onDrawFrame
; - non-rendering tasks that must be completed between frames.
SemanticsBinding
is responsible for linking the semantics layer and the Flutter engine.
GestureBinding
is responsible for working with the gesture subsystem.
(!) Platform interactions are possible only in the main isolate. This is the isolate that is created when your application is launched.
The platform channel
is a two—way communication channel between the dart code and the native, which combines the channel name and codec to encode messages into binary form and back. The calls are asynchronous. Each channel must have a unique identifier.
Message channels
are platform channels designed for messaging between native code and a flutter application.
Message codecs
:
BinaryCodec
By implementing identifier mapping in byte buffers, this codec allows you to enjoy the convenience of channel objects in cases where you do not need encoding/decoding. Dart message channels with this codec are of the BasicMessageChannel type.JSONMessageCodec
Works with "JSON-like" values (strings, numbers, boolean values, null, lists of these values and key string maps with this data). Lists and maps are heterogeneous and can be nested inside each other. During encoding, the values are converted to JSON strings and then to bytes using UTF-8. Dart message channels are of the BasicMessageChannel type with this codec.StandardMessageCodec
Works with slightly more generalized values than the JSON codec, also supporting homogeneous data buffers (Uint8List, Int32List, Int64List, Float64List) and maps with non-string keys. Number processing differs from JSON in that Dart integers arrive on the platform as 32- or 64-bit signed integers, depending on the value never as floating-point numbers. The values are encoded in a special, fairly compact and extensible binary format. The standard codec is intended to be the default choice for the communication channel in Flutter. As for JSON, Dart message channels created using the standard codec are of the BasicMessageChannel type.
Method channels
are platform channels designed to call native code from a flutter application.
Method codecs
:
StandardMethodCodec
delegates encoding of payload values toStandardMessageCodec
. Since the latter is extensible, the same can be said about the former.JSONMethodCodec
delegates encoding of payload values toJSONMessageCodec
.
Event channels
are specialized platform channels designed to be used in the case of representing Flutter platform events as a Dart stream. It works like a regular Stream
Debug
(JIT
) for developmentRelease
(AOT
) to publish the applicationProfile
(AOT
) for performance analysis
Package
is written only in dartPlugin
uses dart and platform-specific code
FFI Plugin
is a plugin that uses [Dart FFI] to write platform-specific parts (https://dart.dev/guides/libraries/c-interop ). Allows you to run code in C/C++
Ticker
asksSchedulerBinding
to register a callback and tellFlutter Engine
to wake it up when a new callback appears.- When the
Flutter Engine
is ready, it callsSchedulerBinding
via theonBeginFrame
request. SchedulerBinding
accesses the list ofticker
callbacks and executes each of them.- Each
tick
is intercepted by the "interested" controller to process it. - If the animation is completed, then
ticker
is "disabled", otherwiseticker
requestsSchedulerBinding
to schedule a new callback. - ...
Tween animation
. The beginning, the end, the time, the speed are determined in advancePhysics-based animation
. Imitate real behavior
A Tween
is an object that describes between which values the widget is animated and is responsible for calculating the current animation value
Implicit Animations
is a set ofImplicitly Animated Widgets
that animate themselves when they are rebuilt with new arguments. (AnimatedAlign
,AnimatedContainer
,AnimatedPadding
, etc.)Explicit Animations
is a set of animation effects controls. They provide much more control over animation thanImplicit Animations
. To use it, you need to mixSingleTickerProviderStateMixin
/TickerProviderStateMixin
to the state of your widget, create anAnimationController
andAnimation
depending on it, pass the animation to theTransition Widget
(AlignTransition
,DecoratedBoxTransition
,SizeTransition
, etc.)SingleTickerProviderStateMixin
/TickerProviderStateMixin
createsTicker
Ticker
calls a callback for each animation frame
AnimationController
calculates all animation frames - controls animation (forward, reverse, repeat, stop, reset, etc.)Animation
gives the current animation value, and also allows you to subscribe to animation value/status updates
- Some external events lead to the need to update the display.
- The
Schedule Frame
is sent to theFlutter Engine
- When the
Flutter Engine
is ready to start updating the rendering, it creates aBegin Frame
request - This
Begin Frame
request is intercepted by theFlutter Framework
, which performs tasks related mainly toTickers
(for example, animation) - These tasks may re-create the request for later rendering (example: the animation has not finished its execution, and it will need to get another
Begin Frame
at a later stage to complete it) - Next, the
Flutter Engine
sends aDraw Frame
, which is intercepted by theFlutter Framework
, which will look for any tasks related to updating the layout in terms of structure and size - After all these tasks are completed, he moves on to tasks related to updating the layout in terms of rendering
- If there is something on the screen that needs to be drawn, then a new scene for visualization is sent to the
Flutter Engine
, which will update the screen - Then the
Flutter Framework
performs all the tasks that will be performed after the rendering is completed (PostFrame callbacks
), and any other subsequent tasks that are not related to rendering - …
- Restrictions go down the tree, from parents to children.
- Sizes go up the tree from children to parents.
- Parents determine the position of the children.
BuildOwner
is the manager for building and updating the element tree. He is actively involved in two phases — assembly and completion of assembly. Since the BuildOwner
manages the tree assembly process, it stores lists of inactive items and lists of items that need updating.
Methods:
scheduleBuildFor
makes it possible to mark an item as in need of updating.LockState
protects the element from misuse, memory leaks and marking for updates during the destruction process.buildScope
performs the reassembly of the tree. It works with items that are marked as in need of updating.finalizeTree
completes the construction of the tree. Removes unused elements and performs additional checks in debugging mode, including for duplicating global keys.reassemble
ensures the operation of theHotReload
mechanism. This mechanism allows you not to rebuild the project with changes, but to send a new version of the code toDartVM
and initiate a tree update.
PipelineOwner
is an assembly manager that works with the display tree.
The Garbage Collector
is an algorithm that monitors links and cleans up memory in order to prevent it from overflowing.
(!) During the garbage collection process, the Dart Framework layer creates a channel of interaction with the Flutter Engine layer, through which it learns about the moments of application downtime and lack of user interaction. At these moments, the Dart Framework
starts the memory optimization process, which reduces the impact on the user experience and stability of the application.
The amount of memory used can be divided into two spaces: active and inactive. New objects are located in the active part, where, as it fills up, live objects are transferred from the active memory area to the inactive one, ignoring dead objects. Then the inactive half becomes active. This process is cyclical.
Old garbage Collector (Parallel Marking and Concurrent Sweeping)
- The object tree is traversed, the objects used are marked with a special label.
- During the second stage, a repeated pass through the object tree occurs, during which the objects that were not marked in the first stage are processed
- All labels are erased
Platform Task Runner
: The main flow of the platform. The plugin code is executed here. For more information, see the UIKit documentation for iOS or the MainThread documentation for Android. This thread is not displayed in the performance overlay.UI Task Runner
: The UI thread executes the Dart code in the Dart VM. This stream includes code written by you and code executed by the Flutter framework on behalf of your application. When your application creates and displays a scene, the UI thread creates a layer tree, a lightweight object containing device-independent drawing commands, and sends the layer tree to the raster stream for display on the device. Do not block this stream! Shown in the bottom row of the performance overlay.Raster Task Runner
: The raster stream receives the layer tree and displays it by accessing the GPU (GPU). You cannot directly access the raster stream or its data, but if this stream is slow, then this is the result of what you did in the Dart code. The graphics libraries Skia and Impeller work in this stream. They are shown in the top row of the performance overlay. Previously, this stream was known as the "GPU stream" because it performs rasterization for the GPU. However, it is executed on the central processor. We renamed it the "raster stream" because many developers mistakenly (but quite reasonably) believed that this stream runs on the GPU.IO Task Runner
: Performs expensive tasks (mainly I/O) that would otherwise block the operation of UI threads or raster threads. This stream is not displayed in the performance overlay.
[More details](https://www.programmersought.com/article/81813680395 /), More details 2
Architecture is a set of solutions for the organization of a program. Such as dividing the program into layers, building links between them, managing the state, and communicating with the UI. A good architecture makes the layers in the application loosely connected, which makes it easier to make changes, increases the testability of the code, and simplifies the system
Pure architecture is an architecture that follows SOLID
and is divided into three independent layers:
Data (datasources, models, repositories)
getting data from the outsideDomain (entities, repositories interfaces, usecases)
business rulesPresentation (bloc, pages, widgets)
display
Vanilla
Advantages
- Low entry threshold.
- No third-party libraries are required.
Cons
- When the widget state changes, the widget tree is completely recreated each time.
- Violates the principle of sole responsibility. The widget is responsible not only for creating the UI, but also for loading data, business logic, and state management.
- Decisions on how to display the current state are made directly in the UI code. If the condition becomes more complex, then the readability of the code will decrease significantly.
Usage:
- Widget State
BLoC
Advantages
- Clear division of responsibility
- Predictable Event to State transformations
- Reactivity. There is no need to call additional methods
Cons
- Mandatory status in the Block
- Frequent library changes
- Dependence on a third-party library
Usage:
- Widget State
- App State
Redux
Advantages
- Unified state
- All actions are available to everyone
Cons
- Unified state
- Dependence on a third-party library
- A large amount of boilerplate code
- All actions are available to everyone
Usage:
- Widget State
- App State
Provider
Advantages
- Scopes for subtrees
- Flutter is oriented
- There is no static
- Ready-made providers
Cons
- Link to the framework
- Only 1 provider of the same type
- Dependence on a third-party library
Usage:
- App State
- Partially DI
Riverpod
Advantages:
- Scopes for subtrees
- Flutter is oriented
- There is no static
- Ready-made providers
Cons:
- Dependence on a third-party library
- Cyclic dependencies fall in runtime
Usage:
- Widget State
- App State
- DI
Dependency injection (DI)
is a mechanism that allows you to make objects interacting in an application loosely coupled using interfaces. This makes the entire system more flexible, adaptable and extensible.
Parts:
Model
contains all the logic of the application, it stores and processes data, while not interacting with the user directlyView
displays the data that was passed to itViewModel
binds the model and the view (transfers data between them)
Usage:
- Used in a situation where data binding is possible without the need to enter special presentation interfaces (i.e. there is no need to implement IView);
- WPF technology is a common example.
MVC
Parts:
Model
contains all the logic of the application, it stores and processes data, while not interacting with the user directlyView
displays the data that was passed to it- The
Controller
intercepts the event from the outside and, in accordance with the logic embedded in it, reacts to this event by changing the Model by calling the appropriate method. After the change, the Model uses the event that it has changed, and all subscribed to this View events, upon receiving it, access the Model for updated data, after which they are displayed
Usage:
- Used in a situation where data binding is not possible (Binding cannot be used);
MVP
Parts:
Model
contains all the logic of the application, it stores and processes data, while not interacting with the user directlyView
displays the data that was passed to itPresenter
subscribes to presentation events, modifies the model on request, updates the view
Usage:
- Used in a situation where communication between the view and other parts of the application is not possible (and you cannot use MVVM or MVP);
Navigator
- It comes out of the box
Go Router
- Parsing of the path and query parameters (for example, "user/:id")
- Deep-links support
- Redirection support - you can redirect the user to another URL depending on the state of the application
- Named routes
Auto Route
- Parsing of the path and query parameters (for example, "user/:id")
- Deep-links support
- Protected routes
- Named routes
- Different animation options
Non-relational (NoSQL):
Hive
Advantages:
- Implementation on pure Dart, without platform code, due to which it works equally on different platforms
- Easy to use
- Fast write/read
- Little boilerplate code
- Availability of code generation
- Supported on all platforms
Cons:
- Links between objects must be maintained manually
- The limit on the number of adapters for objects is 224
- The limit on the number of fields in the adapter is 255
- Poorly suited for working with a large amount of data
- Uploads the entire database to memory
- You cannot access a box created in another isolate
- Availability of code generation
- There are no migrations
Usage:
- Small amount of data
- The need to save your data models
Shared Preferences
Advantages:
- Easy to use
- Synchronous reading from the in-memory cache
- Supported on all platforms
Cons:
- Different implementations for Android/iOS and other platforms
- Accessing the platform code
- It is not guaranteed to write to disk after successful execution of the method
- It is impossible to save complex objects out of the box
Usage:
- Small amount of data
- The need for rapid implementation of the solution
- Saving application settings as primitive data
Relational (SQL):
SQFLite
Advantages
- There are migrations
- Supports connections between entities
- Does not unload the database into RAM
- The ability to write complex SQL queries
Cons:
- Difficult to use
- Not supported on Web, Linux, Windows
- The speed of operation is lower than that of NoSQL
- There may be different versions of SQLite on different OS and versions
Usage:
- Large amount of data
- Storage of complex structured data
Drift
- There are migrations
- Supports connections between entities
- Does not unload the database into RAM
- The ability to write complex SQL queries
Advantages
- There are migrations
- Fast
- Supported on all platforms
Cons:
- Difficult to use
- The speed of operation is lower than that of NoSQL
Usage:
- Large amount of data
- Storage of complex structured data
- A
unit test
tests a single function, method, or class. Its purpose is to check the correctness of a certain function, method, or class. External dependencies for the module under test are usually passed as a parameter. Widget test
tests one widget. The purpose of such a test is to make sure that the widgets user interface looks and interacts as planned. Widget testing takes place in a test environment that provides the context of the widget
s lifecycle. Also, the widget under test should be able to receive user actions and events and respond to them.- The
integration test
tests the entire application or most of it. The purpose of the integration test is to make sure that all tested widgets and services work together as expected. In addition, you can use integration tests to check the performance of your application. As a rule, the integration test is performed on a real device or emulator.
TDD
is an application development technique in which first a test is written covering the desired change, and then the code that will allow the test to pass.
Generative. They are responsible for convenient and safe creation of new objects or even entire families of objects.
- Factory Method. A generative design pattern that defines a common interface for creating objects in a superclass, allowing subclasses to change the type of objects being created.
- Abstract Factory (Abstract Factory). A generative design pattern that allows you to create families of related objects without being tied to specific classes of objects being created.
- Builder (Builder). A generative design pattern that allows you to create complex objects step by step. The builder makes it possible to use the same construction code to get different representations of objects.
- Prototype. A generative design pattern that allows you to copy objects without going into the details of their implementation.
- Singleton (Single). A generative design pattern that ensures that a class has only one instance and provides a global access point to it.
Structural. They are responsible for building user-friendly class hierarchies.
- Adapter (Adapter). A structural design pattern that allows objects with incompatible interfaces to work together.
- Bridge. A structural design pattern that divides one or more classes into two separate hierarchies — abstraction and implementation, allowing them to be changed independently of each other.
- Composite (Linker). A structural design pattern that allows you to group many objects into a tree structure, and then work with it as if it were a single object.
- Decorator (Decorator). A structural design pattern that allows you to dynamically add new functionality to objects by wrapping them in useful "wrappers".
- Facade. A structural design pattern that provides a simple interface to a complex class system, library, or framework.
- Flyweight (Lightweight). A design pattern that allows you to fit more objects into the allocated RAM. Lightweight saves memory by sharing the general state of objects among themselves, instead of storing the same data in each object.
- Proxy (Deputy). A structural design pattern that allows you to substitute special substitute objects instead of real objects. These objects intercept calls to the original object, allowing you to do something before or after transferring the call to the original.
- Behavioral*. They solve the tasks of effective and safe interaction between the objects of the program.
- Chain of Responsibility. A behavioral design pattern that allows requests to be passed sequentially through a chain of handlers. Each subsequent handler decides whether it can process the request itself and whether it is worth passing the request further down the chain.
- Command (Command). A behavioral design pattern that turns requests into objects, allowing you to pass them as arguments when calling methods, queue requests, log them, and support the cancellation of operations.
- Iterator (Iterator). A behavioral design pattern that makes it possible to consistently bypass the elements of composite objects without revealing their internal representation.
- Mediator. A behavioral design pattern that allows you to reduce the connectivity of many classes to each other by moving these relationships into one intermediary class.
- Memento (Snapshot). A behavioral design pattern that allows you to save and restore the past states of objects without revealing the details of their implementation.
- Observer (Observer). A behavioral design pattern that creates a subscription mechanism that allows one object to monitor and respond to events occurring in other objects.
- State. A behavioral design pattern that allows objects to change behavior depending on their state. From the outside, it seems that the class of the object has changed.
- Strategy. A behavioral design pattern that defines a family of similar algorithms and places each of them in its own class, after which the algorithms can be interchanged right during program execution.
- Template Method (Template Method). A behavioral design pattern that defines the skeleton of an algorithm, shifting responsibility for some of its steps to subclasses. The pattern allows subclasses to redefine the steps of the algorithm without changing its overall structure.
- Visitor. A behavioral design pattern that allows you to add new operations to the program without changing the classes of objects on which these operations can be performed.