0% found this document useful (0 votes)
34 views397 pages

Intro (10 Files Merged)

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
34 views397 pages

Intro (10 Files Merged)

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 397

Intro into mobile

development
& Flutter
ABOUT ME

● Software Engineer
● 5+ years at company
● https://www.linkedin.com/in/igor-leshkevych/
● Roles and responsibilities:
○ Developer (backend, frontend, mobile)
○ Lead
○ Currently a member of CTO team (company improvements, R&D)
ABOUT “LEOBIT”

● Software development company


● Based in Lviv, Ukraine
● 150+ FT employees
● https://leobit.com/vacancies/
● https://leobit.com/internship/
COURSE PREREQUISITES

● English (pre-inter+)
● Understanding of Git (commit/push/pull)
● Programming experience, preferably with C-like language (C/C++, Java,
JS, C#, etc.)
● Some understanding of mobile platforms (types of devices, Android,
iOS, etc.)
COURSE CONTENT

● Mobile industry overview


● Specifics of development client application
● Dart programming, and programming in general (OOP vs FP, etc.)
● Flutter development: philosophy, tools
● Code design and code quality
● Working in a team environment (Github, pull requests, code reviews)
PROCESS

● 12 weeks = 12 lectures
● Score 0...100 (55 ex + 45 labs)
● Lectures online (at least for now)
HOMEWORK

● Attending lectures is enough to pass the exam


● But not enough to learn Flutter. If you’re interested in it - read at home
(I will give you extra reading resources on each lecture)
Join Us

Leobit Career leobitllc

Умови:
● Підписатись на сторінку Leobit в Instagram
● Підписатись на телеграм канал Leobit Career
● Позначити друга у коментарях під дописом у
Instagram
Intro Q&A
Lecture 1

● Current state of mobile


○ Mobile vs other (b/e, f/e, system)
○ Concept of “nativeness”
○ Cross-platform development
● Flutter
Mobile vs Other areas

● Is it interesting (technologies-wise and in general)?


● Demand: is it easy to find a job?
● Career growth?
● Why learn mobile in case you don’t plan to be a mobile developer?
● Mobile from business POV (typical project, etc.)
● Mobile from developer perspective (responsibilities, communication
with others, etc.)
DOU.ua survey 2022
Native vs Non-native

● Native (Android & iOS)


● Cross-platform (Xamarin, Flutter, React Native) and the concept of
native-compiled
● Hybrid (HTML/CSS/JS)
● PWA (Progressive Web Apps)
Native

● Java/Kotlin for Android, ObjC/Swift for iOS


● Everything is possible
● Lifetime support (platform lives -> development lives)
● Top performance
● Top community support (lots of libs, existing solutions, documentation)
● Double work/expertise required to support 2 platforms
Cross-platform

● Main players: Flutter, React Native, Xamarin (MAUI now)


● Core idea: “build once” -> “use twice”
● Partially shared codebase
● Can be natively compiled (performance close to native)
● Almost full access to native APIs (camera, location, background, etc.)
● Less developed ecosystem/community
● Still requires you to use native sometimes
Hybrid

● In short: website opened in a webview


● No access to native APIs, etc.
● Disfavoured/banned by stores (hello Apple)
● Main benefit: HTML/JS/CSS ecosystem
● Main niche: native wrapper for function-rich web applications
PWA

● In short: enhanced version of Hybrid


● Access to some native APIs, etc.
● Better performance
● Still a web application with all the consequences.
So what’s best?

● Everything is just a tool


● All of them can be used to solve almost any problem/task
● The real difference is how easy will it be
More reading

● https://medium.com/swlh/native-vs-non-native-mobile-apps-whats-
the-difference-b3a641e06f52
● https://dart.dev/overview#platform
What is Flutter

● UI toolkit (originally - for nice UIs, prototyping, etc.)


● Cross-platform tool, native-compiled (JIT* in development, AOT in
production)
● Opensource*, officially supported and maintained by Google, powered
by community (https://github.com/flutter/flutter)
● “Young”
● Current version: 2.10
● Uses Dart as a programming language
About versions and SemVer

https://semver.org/
Flutter vs others

● Xamarin (same level of “nativeness”)


○ Microsoft vs Google
○ .NET ecosystem vs Dart ecosystem
● React Native (slightly more “native”)
○ Facebook vs Google support
○ React ecosystem vs Dart ecosystem
● Hybrid/PWA (significantly more “native”)
Dart

● C-family (familiar syntax and language constructs)


● Designed for client development:
○ Development comfort (hot reload)
○ Production performance (AOT)
● Type-safe, with sound null safety (https://dart.dev/null-safety)
● Supports native (Flutter) & web (Angular + Dart, etc.)
● OOP, but supports some FP elements
Dart
DOU.ua survey 2022
DOU.ua survey 2022
DOU.ua survey 2022
DOU.ua survey 2022
DOU.ua survey 2022
https://dou.ua/lenta/articles/language-rating-2022
Flutter + Dart

● Dart is “tailored” for Flutter


● Young ecosystem
● Yet another language to learn
Opensource, what does it mean

● Opensource technologies always grow faster


○ Bigger community
○ Free labour (new features are paid with Github stars instead of $$$)
○ Transparency
● You can always inspect/modify the code
So, why Flutter?

● Fast development/prototyping
● Pleasant to work with (hot reload, straightforward)
● Is expected to grow (and potentially outgrow React Native with time)
Lecture 1 Q&A
Join Us

Leobit Career leobitllc

Умови:
● Підписатись на сторінку Leobit в Instagram
● Підписатись на телеграм канал Leobit Career
● Позначити друга у коментарях під дописом у
Instagram
UI and Layouts
Lecture 2

● UI/UX (developer’s perspective)


● The concept of layout
● Evolution of layouts
What is UI

● What you see on screen


● How you interact with it
● Strictly scoped to the application
● Covers a lot of things:
○ How elements are arranged (layout)
○ Colors, themes
○ Text and images
○ Transitions, animations
○ Gestures, controls
What is UX
● User experience with product/brand in general
● Emotions triggered by interacting with app/product/brand
● Aftertaste of using your app
● Can be affected by factors you as a developer directly
control (slow performance, bugs, crashes, skewed layout)
● But also can be affected by factors outside your control (for
example, content of your app, populated by content
manager, etc, bad customer support, etc.)
UI vs UX
● UI is always about particular app, while UX is often more
about your product/brand in general
● UI is how you interact with the app, UX is how you feel about
this interaction
● UI contributes to UX
● Good UI doesn’t mean good UX, bad UI often leads to bad
UX
Example
● Imagine movie catalogue app
● App has nice design, is pleasant and easy to use
● So it has good UI
● Information in the app is misleading (wrong names, actors,
some movies do not really exist, etc.)
● So it has bad UX
● In this course, we will mostly talk about UI, not UX
More reading
● [Optional] https://www.usertesting.com/blog/ui-vs-ux
What is layout
● Layout = how elements (buttons, images, etc.) are arranged
on the screen
● Recently, it’s also about how they rearrange when screen
size changes
Static layout
Responsive layout
Adaptive layout
Evolution of layouts
● Legacy web layouts
● Modern web layouts
● Constraint-based layouts
● Modern mobile layouts
Legacy web layout
● Separate markup language
● Static width/height + margin/padding/border
● Physical pixel as a unit of measurement
● Sometimes using <table> for aligning
● Complicated combinations of tools/hacks to achieve
centering, responsiveness, etc.
● Still used sometimes for emails due to email clients
restrictions
Table layout example
<table style="width:100%; border-collapse:collapse; font:14px Arial,sans-serif;">
<tr>
<td colspan="2" style="padding:10px 20px; background-color:#acb3b9;">
<h1 style="font-size:24px;">Tutorial Republic</h1>
</td>
</tr>
<tr style="height:170px;">
<td style="width:20%; padding:20px; background-color:#d4d7dc; vertical-align:
top;">
<ul style="list-style:none; padding:0px; line-height:24px;">
<li><a href="#" style="color:#333;">Home</a></li>
<li><a href="#" style="color:#333;">About</a></li>
<li><a href="#" style="color:#333;">Contact</a></li>
</ul>
</td>
<td style="padding:20px; background-color:#f2f2f2; vertical-align:top;">
<h2>Welcome to our site</h2>
<p>Here you will learn how to create websites...</p>
</td>
</tr>
<tr>
<td colspan="2" style="padding:5px; background-color:#acb3b9; text-align:center;">
<p>copyright &copy; tutorialrepublic.com</p>
</td>
</tr>
</table>
Table layout
● Almost unused nowadays (mostly for emails), but has
historical importance
● Screen is represented as a table with cells
● To align element somewhere -> put it into the right cell (top
left, top right, etc.)
● Height/Width is controlled by controlling height/width of
table cell
● To add space between elements -> use cells, too
Modern web layout
● Still separate markup (but there are attempts to mix, like
JSX)
● Often use Flexbox/CSS Grid/Bootstrap for aligning things
● Constraint-based layouts (will be described later)
● Aligning things is easy (relatively)
● Designed for responsiveness
Flexbox layout example
Mixing markup with code (JSX)
Constraint-based layouts
● Actual width/height/coordinates are not predefined
● Constraints are rules that define positions/size of elements
relative to each other
● When actual screen (Height x Width) is rendered, all
variables (height, width, coordinates) are calculated based
on constraints
● When screen size/orientation is changed, new values are
calculated and used (based on same constraints)
Fixed vs constraint layouts
More reading
● https://flutter.dev/docs/development/ui/layout/constraints
Modern mobile layouts
● Allow both fixed/constrained styles
● Android uses separate markup language (xml), similar to
html
● iOS previously used separate markup (Storyboards), but
now are shifting towards using code + declarative approach
(SwiftUI)
● Responsiveness is not required in runtime, but need to
support multiple resolutions + sometimes orientation
change (vertical vs horizontal)
Declarative approach (Swift UI)
Layouts in Flutter: core principles
● No markup, Dart for coding UI (similar to Swift UI)
● Core building block - Widget
● To build complex UI you use composition - you compose
multiple widgets in hierarchies (widget tree) instead
inheritance
● Widgets are small and reusable
Example
Widget tree
More reading

● https://flutter.dev/docs/development/ui/layout
● https://flutter.dev/docs/development/ui/widgets-intro
Markup pros/cons:
+ Can be done separately from - More code in total
logic - Hard to make UI and logic
+ Can be done by a separate communicate
person (good for business, as - Another language to learn
HTML is cheaper than JS, for - Developer experience
example)
+ Good tooling (markup preview,
etc.), no need to compile code
to preview layout
Why Flutter doesnt use markup
● Flutter offers hot-reload, so there is no need in markup
preview tooling
● Android/iOS have their own markup (xml/storyboard), so
introducing yet another markup on top of that wouldn’t
work well
● Flutter promotes “rapid development” and using Dart
exclusively is much faster than Dart + markup
● Flutter is very developer-oriented (tries to provide best
developer experience)
Lecture 1 Q&A
Flutter
basics
Lecture 3
● Flutter set up
● Flutter core concepts
○ Widgets
● Declarative vs Imperative approach
Supported platforms
● Windows
● Linux
● MacOS*
● ChromeOS**

* Building apps for iOS is only supported on MacOS.


** ChromeOS is Linux based, too
What you need
● Flutter SDK
● Platform tools (Android/iOS)
○ Android/iOS SDK
○ Emulator/Simulator
● IDE/Editor + Flutter dev tools
How to install
● Official guide: https://docs.flutter.dev/get-started/install
● Choose your OS and follow the steps
● Android setup is available on all platforms
● iOS setup is available only on MacOS
● Web setup we won’t cover, but it is available automatically
on all platforms
What is “Flutter SDK”
● SDK = Software Development Kit
● Set of tools (binaries + command line tools) required to
develop and build Flutter apps, such as:
○ Dart SDK
○ Flutter framework + rendering engine, libraries
○ Dart DevTools
○ `flutter` and `dart` command line tools
● https://docs.flutter.dev/development/tools/sdk/overview
Platform tools
● Tools required to build Flutter app for specific OS
(Android/iOS)
● Includes everything you would need to build a native app
● Is installed by simply getting Android Studio or XCode (by
installing them you get everything you need: platform SDKs,
emulator/simulator, etc.)
IDE/Editor setup
● To simply write code, you only need a text editor (such as
notepad)
● To simply build a Flutter app, you only need Flutter SDK +
platform tools + command line
● IDE/Editor setup is only needed for better developer
experience: debugging, code autocompletion, profiling, etc.
Available options:
● Android Studio/IntelliJ Idea (almost same thing)*
● VS Code**

* When using VS Code/Android Studio, you still need to have


XCode installed to build apps for iOS
** When using VS Code, you still need to have Android Studio
installed to build apps for Android
Dart DevTools
● Dart DevTools is a separate tool (binary) + IDE plugin to work
with it (only VS Code and Android Studio are supported)
● DevTools are needed for:
○ UI layout inspection
○ CPU/Memory/Network profiling
○ Debugging
○ Logging
○ Code/bundle analysis
How to choose/setup
● All functionality you need is available both for Android
Studio and VS Code
● Choose Android Studio if:
○ You already have experience with Android development
and you’re familiar with this IDE
○ You’re used to other JetBrains tools (Rider, RubyMine,
WebStorm, PyCharm, etc.)
● Otherwise, I recommend VS Code
● Follow https://docs.flutter.dev/get-started/editor
Getting started with Flutter
● Test your setup: https://docs.flutter.dev/get-started/test-
drive
● Follow “Hello World” guide: https://docs.flutter.dev/get-
started/codelab
Flutter core principles
● Any frontend* app consists of “presentation” and “behavior”
● Presentation is what you see (screens, controls, etc.)
● Behavior is how you interact with it, and how your
presentation change based on those interactions
● In Flutter, the most basic unit that is responsible for both
presentation and behavior is Widget
● Any Flutter app is basically just a combination of Widgets (or
to be precise: a single “root” widget, that includes other
nested widgets)
Flutter widgets

● Widget - a small building block


● Widgets always form a tree, so there is always a root widget. Each
widget can have 0, 1 or N children
● Most widgets are just combinations of smaller and more basic widgets
under the hood
● Composition over inheritance (“complex widget = multiple widgets
composed together”, instead of “complex widget = inherits and extends
simple widget”)
Flutter widgets

● “Everything is a widget” principle


● Meaning that widgets are not necessarily used only to show something
on the screen, they are also used to implement some behavior (gesture
handling as an example)
● Therefore all widgets are different and have different purpose
Layout widgets

● Some widgets do some work and are useful by themselves (buttons,


images), and some are only needed for laying out other widgets (for
example, centering them, adding paddings, organizing them into stack,
etc.)
● Those “serving” widgets are called layout widgets
● There are 3 types of them:
○ Single-child (wrap single element)
○ Multi-child (arrange multiple elements)
○ Sliver* (handle scrolling)
Layout widgets

● Laying out things in Flutter is different from most other technologies


● Usually (in CSS for example) in order to tell system where to put a
particular element, you set some properties on that element (`vertical-
align`, `left`, `bottom`, etc.)
● In Flutter, element is “agnostic” of how it is positioned on the screen,
and to change its position you use other widgets that “wrap” and
control them
Single-child widgets

Usually used to:


● align widget (center, left, right, etc.)
● Add some paddings/margins
● Constrain widget content from overflowing
● Ensuring that widget always has specific size
● etc.
https://flutter.dev/docs/development/ui/widgets/layout#Single-
child%20layout%20widgets
Examples:
Multi-child widgets

Usually used to:


● Build a complex layout
● Laying out static widgets (static = hardcoded)
● Handling dynamic widgets (dynamic = you don’t know how many of
them you will have, example: Instagram posts, etc.)

https://flutter.dev/docs/development/ui/widgets/layout#Multi-
child%20layout%20widgets
Examples:
Sliver widgets

We skip them for now, but you can read about them here:
https://flutter.dev/docs/development/ui/advanced/slivers

And see the list of built-in sliver widgets here:


https://flutter.dev/docs/development/ui/widgets/layout#Sliver%20widgets
Other widgets
● Layout widgets help you align things, but they do nothing by
themselves, so you use them to position other widgets, such as input
widgets, async widgets, animation widgets, etc.
● There are multiple types of them, and the best way to learn them is to
use them
Other widgets
● When you need to implement something in Flutter, and not exactly sure
what widget it is, there are 3 options:
○ Look at existing widgets and try to find something similar to what you
need
○ Build your custom complex widget by combining other existing basic
widgets
○ Search in the internet - someone probably did it before
● For example, button with text and icon is just a combination of
`InkWell`, `Label`, and `Icon`
Basic widgets
Basic widgets
iOS-style widgets
Other widgets
● Flutter has extensive collection of widgets, that are identical to Material
Design elements, or iOS-style elements
● It’s important to understand that all those elements are just “copies” -
when using those widgets, you are not using native elements in any way
● Flutter team just built their own copies of each of those elements, so if
native element changes in its native OS - it will not be automatically
changed in Flutter, and you have to wait until Flutter team updates the
widget and releases this update (in the next lectures we will talk more
about this)
● You can use iOS-like widgets in Android, and Material widgets in iOS
Inspecting layouts
● During development, you often need to inspect your widget tree to
understand why something is not displayed as you planned
● Dart DevTools offer Widget Inspector tool, that allow you to
○ See the Widget tree structure of what is currently displayed
○ Click elements to show them on the tree
○ Check the attributes of elements
VSCode

Ctrl + Shift + P
Widget inspector
Android Studio
Declarative vs Imperative
● Declarative - focuses on “what I want” (result)
● Imperative - focuses on “what to do exactly” (process)
● Declarative is always an abstraction, backed by some
imperative code
Example 1
Example 1
● `array.filter` will still use loops, etc. inside, but now it’s hidden from the
developer
● It allows browser to change implementation of `array.filter`
(performance optimization, refactoring, etc.) and your code will use the
benefits of those updates, and still work (abstraction)
● Declarative code is easier to read/understand = maintain (change,
support)
Example 2

● Imagine having some HTML markup


● You want to highlight some elements with `selected` class
● You can do it via CSS or using JS
● (Example is “borrowed” from https://www.amazon.com/Designing-
Data-Intensive-Applications-Reliable-Maintainable/dp/1449373321)
HTML
CSS approach
JS approach
CSS vs JS approach

● CSS handled by browser


● JS you need to handle manually
● If `selected` class is removed from element - CSS will update
automatically, JS version requires some additional code
● If browser introduces new, more efficient DOM API, CSS version will use
it automatically, JS version will require modification (with possibility to
break things)
● CSS version still uses some imperative code internally, but you don’t
need to know about that
How it is related to Flutter

● Flutter is declarative (in terms of building UI)


● You define what you want to see on the screen, and Flutter internally
decides how to achieve that
● If (when) Flutter changes something internally - your code is not
affected
● It allows Flutter team to constantly improve their rendering algorithms
without making breaking changes to their API
Lecture Q&A
The structure
of a Flutter app
Lecture 4
● Basic app structure
● Stateful/Stateless widgets
● Multiscreen and navigation
● Flutter internals
Basic app structure
● Follow the official “hello world” tutorial to generate the app
https://docs.flutter.dev/get-started/test-drive?tab=terminal
● This is a default skeleton just to show you basics, in the
future you can remove all the code from there and write your
own
Basic app structure
● The only thing that is required in ANY Flutter app is `main()`
function and a call to “runApp(someWidget)” inside
● Main is our entry point: execution of the app (not just Flutter
but Dart in general) starts from there (similar to other
languages)
● Function “runApp()” is the entry point to your Flutter app: it
accepts Widget as a parameter, and it will be the first thing
to be executed when your actual app starts
Widget
● Practically, any Widget is just a class with a “build” method
(your widget will override base class implementation with its
own to display/do something). Build method is what’s run
by framework to “construct” your widget and run it in the
context of your app
● Build method has BuildContext param (some metadata, info
about parent and a lot of other stuff there)
● Widget can have a constructor to accept some input.
Widgets are constructed by you, so you always control what
to pass there)
Demo
Stateful/Stateless widgets
● As you might have seen, all widgets are of two types:
StatefulWidget and StatelessWidget
● Both are normal widgets (meaning they can display
something on the UI)
● StatelessWidget is used to display something that is not
going to be changed after you’ve displayed it
● StatefulWidget is used to display something that might
change after created based on some events (user input,
timer, external event, etc.)
StatelessWidget
● Simply displays something, and this “something” won’t
change after the widget is created
● Widget can accept some “input” via its constructor and
display that input
● Efficient in terms of performance (Flutter knows that it does
not need to re-draw the widget after it is created + can cache
it)
StatefulWidget
● Consists of a pair of classes: StatefulWidget<State> and
State<T>
● StatefulWidget itself is responsible for creating State<T>
● State<T> is responsible for keeping some internal data, that
might change (for example as a result of user action, like
button click) and displaying something based on this
internal data
● Can receive input from outside (via constructor), but also
can manage its own private data
State<T>
● State<T> is designed to keep some internal state (data).
Practically, imagine some private class fields (not visible
from outside).
● This data can be used to display UI, but it is not necessary
(you don’t have to use this data in your Widgets)
● If data is used to build a widget, when you update the data,
you need to have some way to notify Flutter that Widget
needs to be re-built (it is not happening automatically*). To
do that you use “setState()” method
setState()
● Special method, only available in State<T>
● You use this method to wrap any code, that changes
State<T> variables that have to trigger UI updates (or in
other words, when Widget needs to be re-built)
● If you update some variable without calling setState,
variable will be updated, but your app will still display old
value, because Widget won’t be re-built
● If you need update multiple variables simultaneously - use a
single call to setState()
General guidelines
● Aim to use StatelessWidget where possible
● At the beginning, when writing a widget, always start with
StatelessWidget first, and only convert it to a StatefulWidget
when you see that you need that
● For bigger apps you will need some additional tools in your
arsenal to handle app state, we will cover that in future
lectures
Section summary
● All widgets in Flutter are either StatelessWidget or
StatefulWidget
● StatelessWidget is used to display immutable data
● StatefulWidget is used to display data that might change as
a result of some event (user input, external events)
● Prefer StatelessWidget where possible: they are easier to
maintain and provide better performance
Routing & Navigation basics
● Client apps are usually composed of multiple screens
● Navigation = ability to change current screen (navigate from X to Y)
● Sometimes, each screen assigned a key (examples: “/books/lord-of-the-
ring”, “chats/active/jason-statham”) called route
● Changing current route changes current screen (this is called routing)
● In mobile apps, routes are not visible (like they are in web), but it
doesn’t mean they do not exist.
● It’s not required for mobile app to have routing (navigation is enough)
but it gives some benefits (like deep linking)
Deep linking
● Allows you to give user a link (similar to web link). Mobile OS
(Android/iOS) will handle this link, so when it’s clicked - your app is
opened
● Allows you to not just open your app, but open a particular screen
● Examples:
○ com.myOrg.myApp:// - opens My App developed by My Org
○ com.myOrg.myApp://profile/123 - opens the app and immediately
navigates you to the user profile page (user with ID 123)
Navigation & Routing in Flutter
● Screens in Flutter are organized in stack (data structure)
● Navigate somewhere = push, navigate back = pop
● 2 ways: imperative and declarative. Both are OK, but declarative has
more features/capabilities
● https://medium.com/flutter/learning-flutters-new-navigation-and-
routing-system-7c9068155ade
Navigation stack
Navigation stack
● Always has at least one item (when you pop the last item -> app is
closed)
● Works good for simple scenarios (open new screen, go back, etc.)
● Might cause issues when:
○ You have deep linking
○ You have custom handling of “Back” button
○ When you have multi-stage flows (step 1 -> step 2 -> submit/cancel)
Imperative navigation
● Simple way to organize navigation between screens
● Imperative navigation = simply call push/pop on your navigation stack
● Everything is done manually
● Supports named routes, but does not allow dynamic parsing (example:
com.myapp://profile/123, there is no way to extract “123” from route
and dynamically display user with ID 123)
Example

https://flutter.dev/docs/cookbook/na
vigation/navigation-basics
Name routes
● Allows associating some
“keys” with some screens
(routes)
● You can then navigate by
route instead of pushing a
widget directly
● Provides abstraction*
● Does not support
parameters
Declarative navigation
● Can be used as an alternative if you more complex routing scenarios
● You don’t need it for basic scenarios, but eventually it will most likely
completely replace imperative navigation (even for simple cases)
● Declarative navigation is just a framework to abstract you from
manually calling push and pop
● https://medium.com/flutter/learning-flutters-new-navigation-and-
routing-system-7c9068155ade
Section summary
● Navigation allows you to use multiple screens and navigate
between them
● Routing allows you to assign routes to pages and navigate
using routes
● Navigation is implemented using a stack, you can push/pop
to change current screen
● There are some additional abstractions (named routes,
declarative routing) but under the hood it is just another way
to do push/pop for you
Flutter framework architecture
● Flutter has a complex internal structure: it consists of
multiple internal components doing different things
● All those components are forming three different levels:
platform level, engine level and framework level
● At this stage you don’t need to know what those
components are and how they work. You only need to
understand some of them, and they will be covered in
further lectures.
● Lowest level layer, responsible for “connecting” your app to native OS
● Provides an entrypoint; coordinates with the underlying operating
system for access to services like rendering surfaces, accessibility, and
input; and manages the message event loop*.
● Engine layer runs on top of embedder. The “core” of a Flutter app is
C/C++ engine
● It provides the low-level implementation of Flutter’s core API, including
graphics (through Skia), text layout, file and network I/O, accessibility
support, plugin architecture, and a Dart runtime and compile toolchain
● Top layer, exclusively Flutter
● Contains all the Flutter libs, core packages
● Contains all built-in widgets
How Flutter draws things
● Imagine a canvas - just a blank space with some width and some
height, consisting of “virtual pixels”
● When you want to draw something - you need to set color for some set
of pixels
● Manipulating pixels is hard, so there are some “basic abstractions”:
lines, rectangles, circles, etc.
● Manipulating basic abstractions is also hard, so there are some high-
level abstractions - widgets
● Widget = just some set of instructions “how to draw me on canvas”
Important
● Flutter doesn’t use UI system that native apps use (provided by
Android/iOS), it sends drawing commands directly to “canvas” which
sits under the hood of those UI systems.
● All Flutter widgets are designed and “drawn” by Flutter team - Flutter
doesn’t use native buttons, styles etc., all Flutter control just look the
same. Consequences:
○ You can use iOS-like controls on Android, and vice versa
○ If iOS updates some controls - you need to wait till Flutter team
updates corresponding Widgets
More reading
● !! Achtung !!: complicated text ahead (don’t recommend reading it
now)
https://flutter.dev/docs/resources/architectural-overview
Section summary
● Flutter is a complex framework, it consists of multiple
modules doing different work on different levels (platform
level, engine level, framework level)
● Most of those modules just “do their job” and you don’t
need to know about them/understand how they work
● With some of them you’ll work closely, so you’ll have to learn
more about them (will be covered later on our lectures)
Lecture Q&A
Application state
Lecture 5
● Concept of application state
● How UI connects to state (in general)
● How UI connects to state in Flutter
Math intro
● First let’s recall what is math function
● f(x) = y, where x - argument, y - result, f - some kind of
transformation, to get from x to y
Example of function
● f(x) = x + 5
● [x = 1, f(x) = 6]
● [x = 2, f(x) = 7]
● [x = 3, f(x) = 8]
● etc.
Next example
● f(x) = Text(x.toString())
● [x = 1, f(x) = Text(‘1’)]
● [x = 2, f(x) = Text(‘2’)]
● [x = 3, f(x) = Text(‘3’)]
● etc.
Next example

x=1, x=2, x=3,


f(x)= f(x)= f(x)=
Next example
Next example
What is app state (in theory)
● Imagine that your app is f(x)
● In this case your state is x
● State defines what’s currently displayed (but not “how it’s displayed”)
State rules (in theory)
● For the same state, application always renders the same UI
● State can be empty/constant ({} for example), then the app always
renders the same UI on every run (example - your previous practical
tasks)
What is app state (on practice)
● In simple words, state is data that your app is currently holding
somewhere (usually in memory) and uses to display the UI, when state
changes that usually means that UI needs to be changed, too
● There are sometimes things beside state that affect your UI. They are
side effects (example: notifications, network, etc.)
● While technically some UI variables also affect the UI (example: current
scroll position in scroll view, or current route), they are usually not
considered state (but sometimes they need to, for example when you
need to restore previous scroll position)
Why state is important
● Most applications have some sort of state:
○ Client apps
○ Backend apps (database is also a form of state)
● Managing state is hard (industry still learns how to do it properly)
because of trade-offs:
○ Simplicity (maintainability)
○ Performance
Challenge #1
● How to connect UI to state?
○ How to update state from the UI
○ How to reflect state changes on the UI
Challenge #2
● How to maintain shared state?
○ How to access shared states from different parts of the application
○ How to trigger updates to component X from component Y
Challenge #3
● How to persist state?
○ Normalized/Denormalized (reads/writes, fast/slow, easy/complicated)
○ In memory - fast, but state dies when app dies or in persistent storage
(filesystem, remote) - reliable, but slow read/writes
○ How to serialize & deserialize (JSON, binary)
Challenge #4
● How to handle side effects?
○ How to work with things that bypass state but still update the UI?
○ How to handle state updates triggered outside of the UI?
Summary
● Typical UI application can be represented as y = f(x), where x
is application state
● Application state is some data object that defines the
current “state” of your application
● There needs to be some mechanism to connect UI (what
you see) with the state
Connecting state to UI
● In this lecture we will talk about how to connect UI and the state
(change state from UI and update UI when state changes)
● On the next lecture we will talk about state management in details
● In the future we will also talk about side effects (async, streams,
network, etc.)
● We won’t discuss state persistence a lot, for now we just store our state
in memory and don’t care about its structure
Connecting state to UI in client apps
● Same principles/ideas are used in web/mobile/desktop/any other UI
application (with small adjustments per platform)
● The approaches evolved significantly over the last few years, and they
continue to evolve
Solving first challenge
● In the past, UI was often represented
by some object model with it’s own
API, and you (as a developer) were
responsible for calling this API
whenever your state was changing
Reasons
● Representing UI as an object was heavily inspired by OOP (which was
very popular back then) and imperative code style, and it was very
intuitive for people.
● Having a single object and updating it helped to minimize memory
allocations (creating objects is expensive, re-building UI is even more
expensive)
● CPUs weren’t that strong back then, so any automation was simply too
heavy for any hardware
Architecture
● The problem was that any part of the system is able to update the UI, so
people used different architecture patterns to split system into
components and define responsibilities of the components, so only
some components have direct access to the UI, and other can only use
those mediation components.
● Common patterns: MVC (Model-View-Controller), MVP (Model-View-
Presenter). Very similar, with the only difference in Controller/Presenter.
Disadvantages
● You (as a developer) have to manually update the UI each time your
state changes. As a human, you can easily forget to do that
● Code is often duplicated. If at some point you need to refactor
something, you then have to do it in multiple places. As a human, you
can easily forget to do that
Next step
● The next step was the invention of MVVM (Model-View-ViewModel) and
the concept of data binding
● Data binding = binding the UI to some data
● Binding = when one updates -> another updates as well. Bindings can
be one-way (UI -> data or data -> UI) and two-way (combination of both)
● At this point, bindings are configured manually
Example: .NET WPF
● Label text binds to UserName
property of UserViewModel
● When you need to update the UI,
you no longer address it directly,
you update the viewmodel, and
viewmodel automatically
updates the UI (using method
NotifyProperyChanged).
What’s better now
● You no longer have to manually update the UI - you simply operate with
your data (ViewModel) as usual, and under the hood it also updates the
UI.
● UI is now more declarative: you tell the label that it should bind to
UserName, and it’s now Label’s responsibility to track current value
Disadvantages
● You always need to create a separate ViewModel class (a lot of
boilerplate code)
● In this class, you always have to add NotifyPropertyChanged() (a lot of
boilerplate code)
● Potential performance problems, because UI updates are now not
controlled, and not constrained:
Next step
● The concept of data binding is very powerful, so people focused on
improving on it
● Next iteration: data binding with change detection
● You no longer need to call NotifyPropertyChange, because there is a
separate background job responsible for change tracking, and when
change detected - UI is automatically updated
● You’re not tied to MVVM, you can use whatever you want as long as
you’re using data binding
● Used in AngularJS (not Angular)
Example
● “message” is now just a
normal property, without
NotifyPropertyChange()
magic
● The rest is similar
What’s better now
● Less boilerplate code (just define a property on your
controller/presenter/whatever, and bind to it)
● UI updates are now “scheduled”, change tracker is responsible for
detecting changes and re-rendering the control
Disadvantages
● Change tracking is still performance heavy. In big applications
AngularJS was very slow, everything lagged.
● AngularJS used bidirectional data flow - updates to a parent caused
updates to its children, and update to a child caused an update of its
parent (data flows in both directions, both up->down and down->up).
This caused issues with:
○ Performance (heavy updates, often cascade)
○ Maintainability (good luck finding where the property was updated: in
controller, one of its parents or one of its children)
Modern approaches
● Standard approach in modern platforms today is to use data binding
with unidirectional data flow (React, Angular)
● The idea is the same: some backend code contains some properties, to
which your UI binds. Backend code updates those properties
● Dataflow is mostly unidirectional - meaning that updates only go up-
down, not vice versa
● UI updates are handled differently:
○ Angular uses diff and incremental DOM to manage it
○ React uses diff and virtual DOM to manage it
Summary
● UI can be connected to state in multiple ways
● Overall, multiple approaches exist with their own advantages and
disadvantages.
● New approaches regularly emerge, with attempts to fix disadvantages
of prior approaches (and inevitably introduce new ones)
● Examples: using object model (browser DOM, windows forms, early
iOS/Android), MVVM (some web UI frameworks, .NET WPF, Android),
data binding (Angular)
State and state management in Flutter
● Flutter uses a different approach
● UI in Flutter is rebuild each time state changes:
○ It means that there is no need in data binding, because each time you
draw your UI element “from scratch”
○ Flutter can afford this, because widgets are super lightweight
(opposed to “heavy” DOM in web browser)
○ Changing the state = re-build some parts of the UI
○ Flutter knows about the change, when `setState()` is called
State and state management in Flutter
● int _index is a state
● build() method defines how
current value of the state affects
the UI, build is f(x)
● When state needs to be changed,
setState is used, it notifies
Flutter, that build() method
needs to be called again to re-
evaluate the UI based on new
state
How it all works
● Building blocks:
○ StatelessWidget
○ StatefulWidget
○ State<T>
StatelessWidget
● Never changes
● Rendered UI is always based on input
● Same input = same UI
● Stateless widgets are optimized, so if you don’t need a state - always
use stateless widget over stateful.
StatefulWidget & State<T>
● Always work in pair
● Stateful widget simply tells “what is my state” by using method
`State<T> createState() { return … }`
● `State<T>` contains the actual state + widget’s build method (so
State<T> can tell, how it’s internal state is reflected on the UI)
● StatefulWidget contains method `setState(function () { … })`
● `setState` is similar to `NotifyPropertyChanged` but on global level
How to use them
● State should always be as local as possible - if you need state only for a
small piece of your app - then make this small piece a StatefulWidget,
and the rest of it StatelessWidget
● If state needs to be shared between multiple widgets - lift it up to the
closest shared parent, and use StatefulWidget
● We will learn more about shared state in the next lecture
Lecture Q&A
Local and
global state
Lecture 6
● Local vs global state
● Handling simple global state
● Handling complex global state
○ Using Provider
○ Other patterns
Recap of previous lecture
● UI=f(state)
● There are multiple techniques to reflect state changes on the UI
(imperative approach, mvc/mvp/mvvm, data binding,
declarative/imperative, etc.)
● UI in Flutter is declared as a function (or “reflection”, “projection”) of
state
● Widget state is updated by calling `setState({})`, which changes the
state, and triggers the UI update
Local state
● Is a piece of state, that is only visible/accessible inside a
single widget
● Other widgets don’t know (or need to know) about it, local
state is an implementation details and is only used
internally, so implementation can be changed without
“breaking” other widgets
● Similar to encapsulation concept in OOP
Global state
● Piece of state, that needs to be used by multiple widgets
● Usually it’s either some shared data that needs to be
displayed in multiple places, or data that is changed from
one place and displayed in another
● In this case, state management technique depends on how
those widgets relate to each other (parent-child or sibling-
sibling)
Handling local state
● Local state should be as small as possible. If you widget has a lot
of local state (multiple properties that frequently change, etc.) it
is a strong sign that your widget has to be refactored to multiple
smaller widgets
● When your local state is small enough, it is best to use as simple
measures as possible. In this case, using `StatefulWidget`,
`State` and `setState()` is the optimal solution
● But you might still want to use something “heavier” even for
local state
Handling global state
● The complexity behind global state is the fact, that you need
to organize “shared access” to some piece of data.
● Simple solutions (like making some global variable) are
introducing multiple issues:
○ Hard to track changes (where/why/when state changes
when you don’t expect it to change)
○ Breaks isolation (widgets are no longer reusable, because
they rely on some global things to function)
○ Hard to test widget (because there is no isolation)
Handling global state
● When handling global state we (ideally) still want to follow
those principles:
○ Widgets are small and easy to read/understand
○ Widgets receive some input and return some output,
meaning UI=f(state)
○ State changes are easy to track (when state changes
unexpectedly, you have a possibility to easily identify
where it happens)
Example #1
What happens here
● Text and Button are children of
MyHomePage, and are in different
subtrees
● Text and Button are declared inside
MyHomePage build method
Summary
1. Passing state from parent to child is
easy and can be done using
constructors, for both data and
behavior (or functions, often called
callbacks)

1. passing state from child to parent,


can always be re-modelled as
parent->child using callbacks
Summary
In our example:
● Parent passes information about
current counter (_counter is our
data) to child
● Child wants to notify parent about
something, so parent passes a
callback (_increment counter is our
behavior) to child
Example #2
● Again, root (MyApp) and two
children (= two subtrees)
● Button and Text are again in
different sub-trees (left and right)
● State now resides in left subtree,
not in the root
● Button (right subtree) still needs to
update Text (left subtree)
Code
Problem #1
● We can pass data from parent to
child, but here we need to pass data
from one sibling to another
● This cannot be done directly,
because siblings don’t define each
other in their build method, only
their parent defines them both
Imperative approach:
Why it’s bad in Flutter
● Ruins encapsulation (widget internal state now can be managed from
outside)
● Ruins declarativity (you would normally expect parent’s build method
to be the only place where widget is configured, and widget internals is
the only place where its state can be change)
● Using Flutter not in a way is was designed to be used means that your
code might break in the future (when Flutter team updates the
Framework, they care only about “conventional” use cases)
Declarative solution
● If two siblings need to manage
same state, the state must be lifted
up to the nearest shared parent
● In this example, MyApp is the
nearest shared parent for subtrees
containing Text and Button
Declarative solution
● So we need to move our state to
MyApp
● MyApp will pass it down to
MyStatefulWidget and
MyStatelessWidget
● They will in turn pass them down to
Text and Button
● MyStatefulWidget can now be made
stateless, but MyApp is now stateful
Code
What happens here
● State is now in MyApp
● State is passed down to both
subtrees first to MyApp direct
children (MyTextWrapper and
MyButtonWrapper)
● They pass it further down to Text
and Button
Recap
● State can only be passed from parent to child
● If sibling-sibling communication is needed, state must be lifted up to
the nearest shared parent and then passed down to both subtrees
● This approach is suitable for handling local state - state that makes
sense only for particular widget (or/and its children)
● Theoretically, it can also be used for managing global state (state that
makes sense in scope of entire application), because all widgets are
children of a root widget, so defining the state in your root widget
makes it “local” for all descendants of the root widget (or just ALL
widgets)
Problem #2
● MyTextWrapper and
MyButtonWrapper don’t use
_counter or _callback, they just
pass it further down
● All the intermediate nodes will
include params of all their nested
nodes of any depth (not just direct
children)
How to solve problem #2?
● We want to be able to access the state inside a node of subtree
● We don’t want to pass the state all the way down to this node
● We want to be able to change that state
● We don’t want to call setState directly for parent widget, but we still
need some way for notify framework that state changed and we need to
re-draw
What is “provider”
● “Provider” is an open-source package
● It gives you:
○ The ability to register some data at the parent node, and access it
inside child nodes without the need to pass it down as a parameter
○ The mechanism for all child nodes to receive notification when that
data updates in the parent node
Observer pattern
● There are some parties that are
interested in some data
● That data updates over time, and
those parties want to receive a
notification when that happens,
otherwise they would have to
periodically check data and
compare it to what they received
before (those checks are called
polling)
Observer pattern
● Instead, each interested party
first “registers” to receive
notification
● Interested party is often called
“Observer” or “Subscriber”
● Data that is observed is called
“Observable” or “Publisher”

* “Observer” and “Publisher-Subscriber” are sometimes mentioned as


synonyms and sometimes - as different patterns, but it doesn’t matter now
Observer pattern
● When data is updated,
observable sends a notification
to each registered observer
Back to provider
Provider package introduces 3 core elements:
● ChangeNotifier (aka Observable aka Publisher)
● Consumer<T> (aka Observer aka Subscriber)
● Provider (something that gives each Observer reference to the
Observable)
○ ChangeNotifierProvider
○ MultiProvider
○ others
ChangeNotifier
ChangeNotifier
● A normal dart class
● Encapsulates some business logic (aka state), and provides some public
API (aka methods) to manipulate that state
● Internally, uses notifyListeners() method each time the state changes
(without calling it - subscribers won’t receive the updates)
● Remember MVVM and NotifyProperyChanged() - very similar
Consumer
● Context - buildContext (same
as in Widget build method)
● Cart - an instance of
Observable (it’s type is defined
by generic param of Consumer)
● Child - used for optimization
and allows to skip re-rendering
for subtree if it doesn’t depend
on data from Observable
Consumer
● A Flutter widget
● Wraps the widget that needs to access the state
● Should wrap only the part of the widget tree that is actually interested in
updates (not the entire app)
● Instead of overriding build method, you define builder function, that
does the same, but has access to additional params
● Allows to define a child and reuse that child in builder function to avoid
excessive re-renders
ChangeNotifierProvider
Consumer alternative: Provider.of()

● Can be used instead of wrapping a widget with Consumer<T>


● Usually used when you need to write to state (change it) but don’t need
to read the state
● The reason for that: if you don’t read state, then widgets UI doesn’t
depend on it, and then it means that it doesn’t need to be re-rendered if
state changes (optimization)
ChangeNotifierProvider
● A Flutter widget
● Wraps the widget, where descendants need to have the access to
Observable (can wrap the root widget)
● Provides an instance of a ChangeNotifier to its descendants
● Manages the lifetime of ChangeProvider (Observable): controls how to
create it, how to dispose it, etc.
● ChangeNotifierProvider and MultiProvider are your best friends for now,
but there are also other types of providers (just so you know)
How to use it together
1. Define your ChangeNotifier (or multiple) - class that holds some state,
allows you to update it via public methods (and notifies subscribers
internally)
2. Wrap your app (or part of your app) with ChangeNotifierProvider (or
some other type of provider, like MultiProvider), and define how the
instances of your ChangeNotifier are created (and optionally disposed)
3. For widgets where you need to read state, wrap them with
Consumer<T>, and use provided instance as you need
4. For widgets where you only need to write state, do the same OR use
Provider.of() to get ChangeNotifier instance
Recommendations
● Keep your ChangeNotifier instances small and clean, let them manage a
small but well-defined part of the state (single responsibility)
● Create multiple ChangeNotifier if you need to manage multiple parts of
state (and those parts are not related to each other)
● When defining consumer, only wrap the widgets that actually need the
state, avoid wrapping extra widgets
● Consider using consumer.child for optimizations (but don’t use it if you
don’t understand how, it will be worse)
● Where the state is local, use setState() instead
Recommended reading
● https://www.youtube.com/watch?v=d_m5csmrf7I (30 minutes video)
● https://flutter.dev/docs/development/data-and-backend/state-
mgmt/intro (this is where I copypasted provider snippets from :))
More reading
● At this point you can just consider provider as some “magic box” that
just works, you don’t need to know how it does what it does
● It’s opensource, and has some documentation here:
https://github.com/rrousselGit/provider
● Similar results can be achieved by using InheritedWidget (it is used by
provider internally), but we don’t have enough time to cover it
(https://flutter.dev/docs/development/data-and-backend/state-
mgmt/options#inheritedwidget--inheritedmodel)
More complex scenarios
● In bigger apps, it often becomes hard to maintain this
approach (it’s hard to “scale”, meaning that app complexity
grows exponentially when new features are added)
● In this case it is better to have some “centralized” solution
for state management. “Centralized” means that there is a
special “pipeline” for handling state changes and it is
separated from the rest of the app
● Those approaches will be covered in the next lectures
Lecture Q&A
Asynchronous
programming
in Flutter, Part 1
Lecture 7
● How code executes
○ Linear execution model
○ Parallel/Asynchronous execution
● Threading model in Flutter
○ Isolates
○ Event loop
● Dart tools for asynchronous programming
○ Futures
○ Steams/Observables (next lecture)
Linear execution model
● Code always starts somewhere
(main in this case)
● Instructions go one-by-one (in a
linear fashion)
● When all instructions are
executed - app ends
Limitations
● List of instructions is finite, so
eventually it always ends (except
for hacks like “while (true) {}”)
● Cannot be interrupted/changed
once started (no interactivity)
● “Long” instructions temporarily
block all the instructions after
them
External events
● Sometimes, we don’t know when
something happens, because it is
not deterministic (user input,
network response, etc.)
● When we need to wait for such
event, we block when we start
waiting, and unblock when we’re
done waiting
● While being blocked, we don’t do
anything, we just wait
How limitations affect client apps
● Client apps (and not only them) require interactivity (handling clicks,
timers for animations, etc.). Interactivity = reacting to events, and events
are not known in advance.
● Client apps cannot just “end” because instructions are over, it has to
keep running, until user (or OS) decides to close it
● Client apps must always be intractable, they cannot afford to “freeze”
because of
○ Some long computations
○ Waiting for something to happen (I/O, network, user input)
Ways to overcome limitations
● Parallelism
● Asynchronous execution
Refresher: Threads
● Representation of “code execution path”
● Operating-system level concept (all programming languages provide
their own API, but threads usually* belong to OS)
● Is what CPU physical cores run
● You can have more than one tread, you can have more threads than
physical cores, thread number is not limited*
● OS performs multitasking by switching context between threads
● Creating threads is costly, switching threads is costly, too
Parallelism
Parallelism
● Parallelism is running multiple sets of instructions in multiple threads
and (ideally) on multiple cores simultaneously
● While having multiple simultaneous threads, same rules of “linear
execution model” are applied inside each of them
Practical benefits
● Same work, if run in parallel, can be done X times faster (but, running
something in “N threads/cores” doesn’t mean running it “N times
faster” because of context switching, synchronization, etc.)
● Long-running tasks can now use their own thread, so they no longer
block other instructions from executing
Drawbacks
● Synchronization (when multiple threads need to access some shared
resources)
○ Mutex, semaphore, monitor, barrier, etc.
● High complexity, very easy to make mistakes (human brain is also
linear, so thinking about parallelism is unnatural)
● High cost of errors
○ Deadlocks -> app dies -> expensive for business
○ Hard to spot/understand bugs -> expensive to fix
How parallelism is used in client apps
● OS can run multiple apps (by running them in multiple threads and
doing context switching)
● Single app can run UI thread + other threads (not true for Dart*)
○ UI can only be updated from main thread (to avoid problem of shared
resources)
○ Other work is usually done in supplementary thread (to avoid problem
of long-running tasks blocking UI thread)
○ App is alive while UI thread is alive
Asynchronous execution
Asynchronous execution
● Means running something else instead of blocking, while waiting for
some external event to happen
● When thread encounters a point where we need to wait for something
to happen, it switches to some other set of instructions (or just
becomes available for being occupied). When external event is received,
execution is continued from that point (as long as that “another set of
instructions” is finished OR it is also waiting for some other event)
Practical benefits
● Interactivity (in single-core scenario) - ability to run other tasks while
waiting for external events
● Better resource utilization (in multi-core scenario) - your threads (and
physical cores) are always doing some useful work, instead of waiting
for something and wasting resources
Drawbacks
● Asynchronous execution is not a part of OS (even though it uses
threads), so every programming language/platform has to implement
that manually
How async execution is used in client apps
● App remains responsive while waiting for user input
● App is able to still receive user input (or literally any other external
event) while already handling previous input
Recap
● CPU executes instructions in a linear fashion
● Linear execution has limitations, such as blocking and inability to run
multiple tasks
● Operating system executes instructions using threads and achieves
multitasking by using context switching
● Asynchronous execution helps to overcome blocking issue
● Parallel execution helps to run code in parallel, but introduces new
issues, such as additional complexity and the need for synchronization
How code executes in Dart/Flutter
● Dart is a single-threaded language (meaning that Dart code is always
executed in a single thread*)
● Dart supports asynchronous execution (and it works just as you would
expect)
● Flutter app uses multiple threads internally, but your code is still using
only one of them
Why single-threaded?
● It’s a deliberate decision. Dart team wanted to completely get rid of all
the typical problems related to multithreading and parallelism, such as
○ Access to shared resources and synchronization
○ Deadlocks
○ Complexity
What about parallelism then?
● Instead of threads, Dart operates with something called isolate
● Isolate is similar to thread in a way that it represents one code
execution path, BUT:
○ Isolate always operates with its own private resources (memory)
○ Isolate cannot access other isolate resources
○ Threads communicate via shared resources, isolates use messaging
pattern
● On high level, isolate is simply another Dart app, running in parallel,
and you can send/receive messages to it
How to use isolates
● Isolate.spawn is used to create an isolate
● Each isolate has send and receive ports
● When isolates communicate, first isolates uses his send port to send
data to second isolates receive port
● Second isolate is “listening” for messages on his receive port
How to use isolates
Asynchronous execution
● Asynchronous execution is enabled by using a special mechanism
called event loop
● Each isolate has its own event loop (completely independent of other
isolates event loops)
Event loop
Event loop
How it all works
● CPU always needs to do something (= execute some instructions)
● We always start with running the “normal”, synchronous code (your
main function)
● At some point that code finishes (remember linear execution model)
● At this point we enter the event loop
How it all works
● Event loop is basically “a loop” and 2 queues:
○ Microtask queue (high-priority code, usually used for internal
purposes)
○ Events queue (normal priority, used for external events)
● How the queues are consumed:
○ Check microtask queue, execute everything there
○ If it’s empty, go to event queue and execute one item from it
○ Do that while event queue is not empty, if empty - exit application
How to “use” event loop
● Microtask queue is usually used for some internal work, but you can
manually queue an item there by using `scheduleMicrotask()` function
● Event queue is not accessible directly, but Dart provides some
abstractions to put items there. Those abstractions are:
○ Futures
○ Steams
○ async/await*
Future<T>
● Dart abstraction to represent the
result that “is not there yet”
● 3 possible states: uncompleted,
completed successfully,
completed with error
● Future queues item in event loop
● Future<void> for result-less
execution
● Future<T> for something that
returns result (in the future)
How to “use” event loop
● Imagine two worlds: our “current” world, and parallel “future” world
● Things from “future” cannot be used in “current” world
● Things from “current” world can be used in “future” world
● To shift from future world to current world, you use “.then()”
● To shift from current world to future world, you use Future
constructors:
○ Future(() => …)
○ Future.delayed(() => ...)
○ Future.value(5)
Drawbacks
● Working with “.then” quickly becomes uncomfortable, especially when
they are
○ Nested (Future is created inside a Future)
○ Chained (then().then().then())
Async/await
● Await is just another way to shift
from “future” to “current”
● Async is a way to tell that inside
you will use a shift from “current”
to “future”
● Under the hood, it’s still same old
“then()”
● For you it looks more like a
normal synchronous code
How to use
● If method is planned to be “awaited” -> declare it as “async”
● If method is planned to “await” -> declare it as “async”
● If method is “async” -> it must return Future<T> (so if return type was
String, now it will be Future<String>)
● Instead of writing `someFuture.then(someFunction)`, you now write
`await someFuture; someFunction;`
Recap
● Dart - single threaded language (same as JS)
● Flutter uses multiple threads internally, but only one for all of the Dart
code (both framework code and your code)
● Flutter/Dart achieves multi-tasking by using event loop
● Flutter/Dart introduce multiple abstractions (Future, Stream) but they
all are just a way to communicate with event loop
Lecture Q&A
Asynchronous
programming
in Flutter, Part 2
Lecture 8
● Dart Streams
○ Using Streams
○ Creating Streams
● Reactive programming
○ ReactiveX
○ rxdart
○ Rxdart vs Streams
● State management with BLOC
Recap
● Most of the time, the code is synchronous - instructions are executed
one by one, from start to finish, without interruption.
● Sometimes we need to react to external events (response from the
network, file I/O, user interaction), and the timings of those events are
non deterministic.
● To avoid blocking the thread while waiting for the operation to finish,
asynchronous code is used: when external event is encountered, thread
switches to something else and resumes from where it had switched
when signal is received.
Recap
● Dart is single threaded, so asynchrony is implemented using the event
loop: async continuations are queued and executed synchronously
when previous synchronous piece is finished.
● Future<T> in Dart represents the external event plus continuation (aka
callback) that must happen when this event is fired. Using a Future<T>
is just a way to communicate with event loop.
● You can make your code look similar to synchronous code, by using
async/await
Recap
Future<T>
● Future<T> represents a single event that “emits” a single value, like:
○ Network response received
○ User clicked a button once
● Consider Future<T> as a point on the timeline: you don’t know when (or
if) it completes exactly, but when/if it happens - it happens only once.
● What if we need to represent a series of points?
○ Notifications from server that user sent us a chat message
○ User clicking a button periodically
Dart Streams
● Implementation of
publisher/subscriber (observer)
pattern
● Stream<T> (publisher) emits
value and allows to subscribe
● StreamSubscription (subscriber)
is created when subscribed to a
Stream, and handles
values/errors emitted by a stream
Stream<T>
● Emits values asynchronously
● 1 sub by default
● Can be converted to
BroadcastStream to support
multisub
● Can throw errors (then either
stops or continues to emit)
● Can complete (finish emitting)
● Supports stream operations
StreamSubscription<T>
● Represent subscriber
● Allows you to define what to do
when:
○ Value is produced
○ Error is thrown
○ Stream is completed
● You can pause(), resume() or
cancel() subscription
● By default only 1 sub is allowed
Multiple subs
● To support multisub - use
broadcast streams
● Each subscription can be
individually managed
(paused/resumed/cancelled)
Sequence of actions:
● Same as Future<T>,
Stream<T> works with
event loop
● First we execute entire
main(), and only after
that Dart executes code
inside subscriptions
“Await for” consumption
● Instead of listen() you can use
await for, and work with a stream
as if it was Iterable (list)
● Each iteration = onData(), loop
exists when onDone(), stream
errors are thrown as normal
errors
● Rules are same as for Future
async/await (function must return
Future)
“Await for” and multisub
● Rules are the same as in
listen():
○ 1 sub by default
○ Multiple subs allowed if
converted to broadcast
stream
Recap
● Stream<T> and StreamSubscription are implementing pub/sub pattern
● Stream asynchronously emits new values (or throws errors) and can
complete (but not mandatory)
● Stream can accept 1 subscription (by default) or multiple subscriptions
(if converted to broadcast stream)
● Stream can be consumed using listen() and providing callbacks for
onData, onError and onDone, or alternatively by using “await for” syntax
How to actually create a stream?
● Async generator
● StreamController
● Transforming an existing stream
● Combining multiple streams
Async generators
● Async generator is a function that
asynchronously produced values
using “yield”
● Generator is defined using
“async*”
● Because it is async, it can use
“await” inside
● You can also use “yield*” to sub-
yield another stream
Using await
Using yield*
Async generators
● Easy to use and intuitive (like synchronous code)
● Good option when you have a single source of values for your stream
(all values can be emitted from a single method)
● Not suitable when you need to emit values from different places at
different times (imagine that there are multiple buttons on different
screens, and when user clicks any of them - the value must be emitted)
● Async generators are imperative
StreamController
● Allows you to combine “input” and “output” in a single object. You can
use input to push data to a stream, and you can use output to
subscribe to this data
● Input is implemented using a Sink<T> - class for simply “pushing”
values in it
● Output is implemented using a Stream<T>.
StreamController
● Allows to “manually” emit values
on demand and listen to them at
the same time
● 2 in 1:
○ Allows to add() values to the
stream (by using Sink<T>)
○ Allows listeners to subscribe to
its stream property (by using
Stream<T>)
StreamController
StreamController
● Easy to use and intuitive
● Allow to aggregate events from multiple sources into a single stream
● StreamControllers are imperative
● Very quickly become hard to maintain so avoid implementing complex
scenarios with them
● Use only if it helps to keep code simpler
Stream transformations
● The reason #1 why people use
streams :)
● Allow you to transform one
stream into another by somehow
projecting each element/multiple
elements to some other
element/elements
Stream transformations: map
Map allows you to project
one sequence (steam) to
another, by applying
transforming function to
each element of Stream
Stream transformations: expand
Expand transforms each
element to some
sequence, and then
merges all those
sequences to a single
sequence.
x=[1,2,3];
1=>[1], 2=>[2,2], etc.
[[1],[2,2],[3,3,3],...]
y=[1,2,2,3,3,3,...]
Stream transformations: reduce
Gradually aggregates (aka
reduces) all the elements
of the sequence into an
aggregated value, and
returns Future containing
this value
Stream transformations: skip/take, etc.
Allow you to skip first N
values, and take N values.
Useful when working with
infinite streams:
● with skip/take: emits
only 3 elements
● without take: infinite
steam, need to exit
app manually
Stream transformations: where
Allows you to filter
elements by some
condition
Stream combinations
● The reason #2 why
people use streams :)
● Allow you to combine
multiple streams into
one in different ways
● Require “async” package
Stream combinations: merge
Simply merge 2 sequences
into one, that contains
elements of each sequence
Stream combinations: zip
Take element #i from each
stream, combine them all
into array, and produce a
new stream, where each
element #i is that array
Stream combinations: split
Splits a single stream into
multiple independent
streams, identical to the
original one
Stream transformations/combinations
● Powerful mechanism to produce new streams based on existing
streams
● Allow you to define your app as a set of streams and handlers
● Those transformations/combinations are declarative
● Sometimes require you to spend more time thinking than coding
● Complex transformations/combinations require a particular way of
thinking and also some experience
Recap
● There are multiple ways to create a stream: async generators,
StreamControllers or transforming/combining other streams
● Async generators are easy to use/read, allow you to generate stream
values in one place by using some generation algorithm
● StreamControllers allows you to generate values on demand
(imperatively) where you need them, and use multiple sources
● Stream transformations/combinations allow you to produce streams
based on existing streams (which happens more often than you might
think)
More reading
● Official videos from Flutter (I recommend viewing the entire series):
https://www.youtube.com/watch?v=vl_AaCgudcY
● Text version https://dart.dev/tutorials/language/streams
● Creating streams (Flutter docs):
https://dart.dev/articles/libraries/creating-streams
● (in russian) https://habr.com/ru/post/450950/
● Dart:async package (basic combinators):
https://api.flutter.dev/flutter/dart-async/dart-async-library.html
Dart Streams and Reactive Programming
● Dart is relatively young language, and obviously the concept of “data
streams” existed long before Dart and is the core of Reactive
Programming paradigm
● The most famous implementation Reactive Programming and
streaming in general is ReactiveX (http://reactivex.io/) - multiplatform
API for working with observable data streams
● Dart streams are similar (and probably inspired by) to RX streams, but
simpler, has less features and sometimes inconsistent with it
● rxdart is an implementation of RX for Dart, uses Stream API as a basis
and add some operations on top of it
rxdart package
● https://pub.dev/packages/rxdart
● Built on top of Stream API (so inherits all the Stream<T> default
behavior, even if it’s not consistent with ReactiveX libs for other
platforms)
● Allows alternative access to a lot of Stream<T> built in operations and
adds some operations on top of that
● Introduces Observable<T> (Stream wrapper) and Subject<T>
(alternative to StreamController)
● The main point: added utility (in addition to Stream core features)
Pure Streams API vs rxdart
● Rxdart has more features and adds a lot of quality of life, but requires
you to install an additional dependency
● Rxdart is useful when you already have experience with it on other
platforms (JS for example), OR when you have multiple client apps and
want code reuse or just consistency between them
● If you don’t need any of that: use Streams API, its fine.
● Rx can be hard to understand, use https://rxmarbles.com/ for help
Rx combineLatest
Rx withLatestFrom
Rx throttleTime
Rx debounceTime
Rx distinctUntilChanged
Why use Streams/RX
● Streams/RX is more like a philosophy: if you “think” in terms of Streams,
you will be able to express things with your code that you couldn’t
express with standard approach
● Streams are purely declarative, so using them = supercharging your app
with declarativity
● Streams are useful when working with asynchronous code, and also
when representing the state in your app (and the fact that state
changes with time, reacting on some external events)
More reading
● Intro into ReactiveX http://reactivex.io/intro.html
● Rxdart home page https://github.com/ReactiveX/rxdart
● Interactive RX diagrams: https://rxmarbles.com/
Using Streams for state management
● The common approach is to use Streams to represent state
● Your events (network, user input, etc.) push some data to streams, and
your UI acts as a stream listener, and updates when new values are
emitted
● Different parts of the app sometimes depend on same state but
expressed in different forms (transformed/combined), and using
Streams allows you to express that with code, rather than performing
some imperative transformations
BLOC pattern
● BLOC = Business LOgic Component
● Pattern that promotes using Streams as a “source of truth” for your
state
● Multiple related streams are organized into small encapsulated units
(blocs), each unit having an inputs (Sinks) and outputs (Streams)
● Originally, blocs were just plain Dart classes with Sink/Stream
properties, later on the https://pub.dev/packages/flutter_bloc package
was added that introduces a few extra abstractions, but the principles
are the same
● We’re going to cover “basic” (and original) version of BLOC
Using BLOC in Flutter
● Using the BLOC consists of two parts:
○ Defining a BLOC (plain Dart class) which encapsulates your logic,
exposes some ins/outs
○ Using that BLOC in your Widgets with StreamBuilder<T>
● To avoid passing BLOC down the widget tree to all descendants, the
same approach as in provider can be used:
○ InheritedWidget
○ Provider package (remember that provider can be used to “provide”
things to child widgets without using constructor)
BLOC
● Has 2 outs and 1 in
● Input updates 2 streams
simultaneously
● Everything is encapsulated
(cannot push to counter
stream manually)
Displaying
● Text is wrapped with
StreamBuilder
● Inside builder we now have
access to “snapshot” -
current value
● When stream emits new
value -> widget is redrawn
with new snapshot
Modifying
● We can simply use Sink to
push new values. Under the
hood, output streams are
reading from Sink
Recap
● BLOC can be used for state management
● BLOC is just a class with in and outs, and both are streams
● You can use multiple BLOCs
● In reality you will have to add some additional code to
○ Provide your BLOCs to descendants
○ Close your streams when they’re no longer needed
○ Error handling, etc.
● More reading will be provided later
More reading
● Google.io talk https://www.youtube.com/watch?v=RS36gBEp8OI
● Flutter_bloc package (you can try it when you understand what the
original BLOC does):
○ https://bloclibrary.dev/#/
○ https://pub.dev/packages/flutter_bloc
● (in russian): https://inostudio.com/blog/articles-develop/razdelenie-
biznes-logiki-i-ui-vo-flutter-s-pomoshchyu-bloc-arkhitektury/
Lecture Q&A
Packages
Lecture 9
● Concept of package
● Package management and package managers
● pub (package manager for Dart)
● Sound null safety in Dart
History of packages
● Developer’s work consists of 2 parts:
○ Solving unique problems
○ Solving typical problems
● Unique problem is usually a combination of multiple typical problems
● Solutions for typical problems already exist, so to solve typical problem
= choose one of multiple available solutions
● What we need then is some way to
○ Find a proper existing solution
○ Integrate it into our codebase
The concept of package
● Package (or library) - encapsulated, ready to use solution for (usually)
typical problem
● Package usually has some public API, used to “communicate” with
package, and some internal part (encapsulation in action)
● Distributed as a single “atomic” unit (or as a set of smaller packages)
● Usually there is a special “mechanism” to plug in the package into your
application (will be discussed later)
Package vs copy-pasting code
● Can be added/removed in a single operation (instead of
inserting/removing a bunch of files)
● Not checked-in in the source control (easier to review and maintain
codebase)
● Can be updated automatically* (or semi-automatically) when a new
edition is released
Package versions
● Package codebase can evolve over time (new features, bug fixes, etc.)
● When we use a package, we are actually using some “snapshot” of it
(state of package in this specific moment of time). This snapshot is
called version. Version can be anything that allows to distinguish
between different editions of the same package:
○ my_lib:1, my_lib:2
○ my_lib:10.10.2010, my_lib:2019.2
○ my_lib:1.0.4, my_lib:7.1.0.13004b (SemVer)
● At any moment of time, we need to have an understanding of what
exact version we are using right now
What version can do
● Tells us which edition of package is newer/older
● (Sometimes) Tells us is it backward-compatible with previous version
● (Sometimes) Tells us what kind of changes were introduced
(refactoring, bugfix, new feature, breaking change)
● Some versioning schemes do not necessarily support all that:
○ Visual Studio 2022
○ IntelliJ Idea 2021.3
● SemVer does, see https://semver.org/ and lecture #1
Package registry
● When we have a typical problem, we want to find existing solutions for
it.
● Information can be scattered in the Internet, so ideally we want some
centralized place where packages are stored and can be
browsed/searched. This place is sometimes called repository or registry
● Examples:
○ npm (JS)
○ Maven (Java), NuGet (.NET)
○ pub (Dart)
Package manager
● Then, we want a tool that can
○ Find and download package (usually by some name or id) in a registry
○ Integrate it into our codebase
○ Keep track of the exact version of package we use
○ Allow us to upgrade package version (if new version is available)
● Such tool is called a package manager. Package manager and package
registry often use the same name (npm, pub, Maven, NuGet).
Packages descriptor file
To operate, package
managers often use descriptor
file, describing what packages
(and what version) your app
depends on. Possible formats
are json, yaml, xml or others
Application as a package
● Sometimes, your app is always considered a package and
requires you to provide some metadata (package version,
repository URL, license, etc.). This package can then be
published to a registry with no extra effort. Examples: JS,
Dart
● Sometimes, you app need to be explicitly declared as a
package, so you only need to provide that metadata if you
plan to publish (.NET)
Dependency tree
● Packages can use other
packages. Example:
other_guy_lib:1.0.4 uses
yet_another_lib:2.5.6.
● That means that dependencies
can be transitive: my_app-
>lib_1, lib_1->lib_2, so my_app-
>lib_2
● We’ve just introduced the
concept of dependency tree
Dependency graph
In reality:
● Same dependency can be both
direct and transitive at the same
time
● Some dependencies can depend
on the same package

That means, that instead of


dependency tree we have a
dependency graph
Dependency graph and versioning problem
● Same dependency can be used
multiple times (either directly or
transitively), and each reference
can use a different version
● It is not clear then which version
of package to use
Version constraints (pub, bundler)
● Instead of defining specific version of dependency, we can define
version ranges using some constraints
● Example:
○ my_lib:1.2.3 now depends on some_package
○ Because of SemVer, I’m sure that if I use 2.7.1, I can use any version up
to 3.0.0, and my code will be OK (no breaking changes)
○ So I define my dependency as
■ >= some_package:2.7.1 (inclusive)
■ < some_package:3.0.0 (exclusive)
Version constraints (pub, bundler)
● This “range” is only for “declaring” dependency, to compile our code
(and in runtime) we still need to use one specific version
● But ranges give us a “space for maneuver”:
○ Package x depends on my_lib [1.2.4;3.0.0)
○ Package y depends on my_lib [1.2.7; 1.4.8]
○ Package z depends on my_lib [1.3.9; 3.0.0)
○ Then we can use 1.3.9 for all of them
● How to “remember” that 1.3.9 should be used, and how to ensure that
1.3.9 will be used always and for everyone?
Lock file
● Package managers that allow you to operate ranges usually have a
concept of .lock file
● Lock file simply holds ALL direct/transitive dependencies, and for each
dependency it holds exact version that should be used
● Lock file is updated only when package descriptor file is updated (or
when you ask your package manager to re-calculate your lock file)
● Usually looks similar to package descriptor, but:
○ Bigger in size (because includes ALL transitive dependencies)
○ Always contains specific versions, without ranges
Lock file
Version constraints problems
● Ranges allow us to avoid versioning problem (sometimes), but do not
eliminate the problem completely:
○ Package x depends on my_lib [1.2.8; 2.0.0)
○ Package y depends on my_lib [1.1.17; 1.2.0)
○ Ranges do not have an overlap, so we have a version conflict
● Constraint resolution is dynamic: you are starting with some assumed
versions, trying to traverse the graph, and regularly going back and
change the versions you’ve already set. This can process can loop,
causing unresolvable (“unstable”) graph
● Range does not necessarily has an existing version in it
Multiple copies (npm style)
● Instead of treating each package as unique, we can simply hold
multiple versions of the same library. So my_lib:1.2.3 and my_lib:3.2.1
are two different and entirely separate packages.
● Now our dependency graph becomes dependency tree, because each
dependency (direct or transitive) has its own dependencies, and they
are not shared with anyone (two packages can depend on their own
copies of my_lib:1.2.3)
Problems of multiple copies
● For statically typed languages (dart, Java, C#) type MyClass from 1.2.3
and MyClass from 3.2.1 will be two completely different and unrelated
types. If you try to pass the instance of MyClass from one package to
another - you will receive compile type error.
● This is not a problem for dynamically typed languages (JS, python), as
long as MyClass<T> 1.2.3 and MyClass<T> 3.2.1 use the same public API
(= have same methods with same signatures)
Version lock (NuGet)
● We can simply enforce user to manage his direct dependency so that
the versions of transitive dependencies match:
○ My app uses my_lib:1.2.3 that depends on some_package:3.2.1, and
also uses another_lib:2.5.6 that depends on some_package 3.3.2
○ I must manually either upgrade my_lib to some version that uses
some_package 3.3.2 or downgrade another_lib, so it uses 3.2.1
○ Until I do that - my app won’t build
Version lock problems
● It is not always possible to find “the right” version of specific package,
and you have to do a lot of work to keep packages consistent
● Because of that, major package vendors usually sync their new releases
with the releases of their dependencies:
○ My_lib depends on some_package, and I’m waiting for some_package
to release a new version to be able to release new version of my_lib
● To allow more people to use their package, vendors often resist
upgrading their dependencies (if you update before other vendors do -
noone will be able to use your package), which “locks” you and other
vendors to “sit” on specific version and delay upgrades
Dev dependencies
● Some dependencies are only needed for “development” and not in
production.
● Example: test frameworks (you want to be able to run tests locally, but
you don’t need that in your production app), linters/analyzers
● Having extra packages increase bundle size which is critical for client
apps (mobile and JS)
● To resolve that, some package managers (pub, npm and others) allow
you to have two sets of dependencies: normal and development (dev)
● Same is true for transitive dependencies
Dev dependencies vs normal
● Dev dependencies usually not included when you build your app with
production flag (to reduce build time + bundle size)
● Dev dependencies of your dependencies (direct or transitive) are
usually not installed at all
○ They are usually used to develop/test those packages, and you are
only interested in “production” version of the package, because you
don’t develop/test your dependency, you do that only for your own
code
Recap
● To reuse existing solutions, we use packages - encapsulated, ready-to-
use units, that can be easily plugged into our app
● Package management is more complicated than just install/delete:
there are transitive dependencies, versioning problems, dev
dependencies and other challenges
● All modern platforms have some advanced package manager,
sometimes even more than one
● Modern package managers operate using mostly same principles
Package management in Dart/Flutter
● pub is an official (meaning supported by Google) package manager for
Dart
● https://pub.dev is a package repository
● pub supports everything you’d expect from a modern PM solution:
○ install/remove package
○ package versioning and version constraints
○ transitive dependencies
○ dev dependencies
Core concepts
● Package in pub is any folder with pubspec.yaml file inside. Meaning
your Flutter app is also a package (with its own version, etc.)
● You communicate with pub using CLI (command line interface), your
IDE usually provides some UI for managing packages, but under the
hood this UI just uses the CLI
● When the package is installed, to use it in your Dart code you must
import it
● Pubspec operates with constrained declarations, actual versions reside
in pubspec.lock file
● Physically, packages stored outside of project folder (local cache)
Pubspec file
● Describes your own app as a
package
● Declares dependencies and
version constraints
Version constraints
● You can specify exact version
● You can specify upper/lower
boundary
● You can use “caret” which means
“any version starting from X with
the same major”
Packages physical location
● Storing package files inside your project has some problems:
○ Project folder would have a big size
○ Every project would have its own copy of same library (wasted storage
space)
● Packages are stored in a central place (for example on Windows it’s
C:/Users/username/flutter/.pub-cache)
● Inside your project, there is a file .dart-tool/package_config.json, that
contains references to real physical locations of packages in cache
.dart_tool/packages_config.json
Getting packages
● Your pubspec file is just a declaration of what you want to have. To
actually install packages (physically add files) you have to run
> flutter pub get
or
> dart pub get
● This command:
○ Builds dependency graph (to calculate actual versions to be used
based on constraints)
○ Generates your lock file
dart pub get
Upgrading packages
● You should regularly check for updates and upgrade your packages:
vendors often fix bugs/vulnerabilities, add new features, etc.
● There are 2 types of upgrades:
○ “minor” automatic upgrade: just asking pub to try to use higher
versions if possible based on your constraints
○ “major” manual upgrade: changing your pubspec file (updating
constraints, incrementing major versions, fixing your code after
breaking changes, etc.)
“Minor” upgrade
● Should be done regularly
● Use
> flutter pub upgrade
or
> dart pub upgrade
dart pub upgrade
Adding new package
There are two ways:
● Manually add entry inside “dependencies” section of your pubspec +
run “pub get”
● Run
> flutter pub add <package_name>
or
> dart pub add <package_name>
(it will add entry to pubspec and run “get” for you)
dart pub add <package_name>
Other commands
Using a package
● After you’ve added a package to
your app, you can use `import`
statement
● Works similar to `import` in JS,
or `using` in .NET
Sound null safety
Sound null safety is a Dart’s feature, that requires you to explicitly tell
whether the variable can be null or not. It wasn’t there from the beginning,
so some packages don’t support it.

If you run the app in SNS mode (enabled by default), all your packages must
support it. If one or more packages doesnt support it, you have to run the
app with SNS disabled:
> flutter run --no-sound-null-safety

You can check package SNS compatibility at pub.dev website


pub.dev
Recap
● For managing packages in Dart/Flutter you use pub
● Files related to pub:
○ pubspec.yaml - package definition + deps/constraints, can edit
○ pubspec.lock - all deps (including transitive) and versions,
autogenerated
○ .dart_tool/package_config.json - physical location of packages,
autogenerated
● Pub is managed using CLI
● New packages can be found/installed from https://pub.dev
Resources
● https://dart.dev/tools/pub/versioning - about different
approaches for solving versioning conflicts and how pub
does that
● https://semver.org - about semantic versioning
● https://pub.dev - Pub main website (list of packages)
Lecture Q&A
Working with
network
Lecture 10
● Basics of networking and HTTP
● Working with HTTP in Dart
● Serialization
Network
● Modern apps are often communicate with some backend service
● There are generally 2 types of communication:
○ Pull based: synchronous request-response model
○ Push based (reactive): backend “pushes” updates to device, device
“reacts”
● Pull based communication is usually done via HTTP/UDP
● Push based - lot of options, usually websockets or emulation (server
sent events, long polling)
● In this course we’ll cover synchronous communication via HTTP
HTTP basics
● HTTP - stateless protocol (no state is saved “in between” requests, all
information is passed with every request)
● In HTTP parties exchange information using HTTP request and HTTP
response
● HTTP request & HTTP response = plain text + sometimes some binary
data (image, media)
HTTP request
● HTTP request consists of:
○ Protocol + method (GET/POST/PUT/PATCH) + path + query
○ Headers (request “metadata”)
○ (optionally) Body
● HTTP method (GET/POST/PUT/PATCH/Other) defines:
○ Semantics
○ Presence of body (GET for example doesn’t have one)
HTTP request example
HTTP response
● HTTP response consists of:
○ Protocol + status code (200/404/500/etc.)
○ Headers (response “metadata”)
○ (optionally) Body
● Status codes:
○ 2XX - success
○ 3XX - redirects (more rare)
○ 4XX - client error (client has to fix his request to make it successful)
○ 5XX - server error (usually unexpected/unhandled)
HTTP response example
Working with HTTP in development
● When developing apps you often wants to call API directly (not from
code) to test how it works
● To do that, you need some API client. Popular API clients are:
○ curl - command line utility (Linux only*)
○ Postman
○ VS Code extension “Thunder Client”
○ Browser console + `fetch` function*
Working with HTTP in Flutter
● Implemented using package
● There are a lot of “advanced” packages, but what we’re interested in is
a simple and minimalistic http client
● The package we need is called http
● For android, we also need to “request” a permission to use network
(https://flutter.dev/docs/cookbook/networking/fetch-data)
● Http requests are asynchronous, so we use `Future<T>` to represent it
Serialization
● Http exchanges text/bytes, but in our code we operate with Dart data
structures (objects, ints, strings, etc.), so we need a way to convert one
to another
● “Converting” Dart data structures (or any other language) into plain
text/bytes is called serialization, reverse process - deserialization
● By default, http provides us with basic serialization to Map<string,
dynamic>: key-value pairs
Serialization
● Some languages allow you to serialize/deserialize statically typed
objects instead of dynamic maps.
● This is possible because those languages support reflection
● (code below is not Dart)
Reflection
● Reflection - language feature that allows to operate with type metadata
in runtime.
● Examples of what can be done using reflection:
○ Create a new type definition in runtime (class with
fields/methods/properties)
○ Dynamically construct an instance of a type in runtime based on
text/byte data
○ Dynamically traverse properties of the object and serialize those
properties into text/bytes
Reflection in Dart
● Reflection API exists in Dart, but it is not usable in Flutter (you will
receive compile-time error if you try to use reflection package)
● This is done to enable tree-shaking
● Tree-shaking - removing unused code from final bundle, to reduce
bundle size. This is possible, compiler can track all code usages
(class/function references, etc.) at compile time, and if something is not
used -> it can be removed
● With reflection, types can be constructed dynamically, so you never
know whether type will be used or not, and you can’t remove anything
Serialization/Deserialization in Dart
● Because of absence of reflection,
Dart can only use Map<string,
dynamic> for
serialization/deserialization
● Main drawback: no compile time
checks (we are shifting from
statically typed environment to
dynamically typed, like
javascript)
How to bypass that limitation
● Define statically typed class and
encapsulate working with Map
there
● You won’t eliminate the problem
but you will “localize” it, so
external code will still be
“statically typed”
Further improvements
● To automate this process, you can use code generation
(https://flutter.dev/docs/development/data-and-backend/json#code-
generation)
● Same thing under the hood, but all those classes are generated for you
(you just need to decorate your code with attributes)
Further improvements
Working with http
Things to consider
● Always check status codes, if not 2xx - something went wrong, and you
won’t have response to deserialize
● Consider adding timeouts to your request: if it can’t complete in a few
seconds, most likely it won’t complete at all
● Always use futures and async code to process your http code
● Consider wrapping http code into some service classes (to avoid tight
coupling between your UI and http code)
● Don’t call http inside build method
Lecture Q&A

You might also like