# learn-flutter
**Repository Path**: mirrors_dwyl/learn-flutter
## Basic Information
- **Project Name**: learn-flutter
- **Description**: π¦ Learn how to use Flutter to Build Cross-platform Native Mobile Apps
- **Primary Language**: Unknown
- **License**: GPL-2.0
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-08-08
- **Last Updated**: 2026-01-17
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README

Learn the **`Flutter`** basics to get up-and-running **fast**
and build **awesome cross-platform applications**!
- [What? π‘](#what-)
- [Why? π€·](#why-)
- [Who? π€](#who-)
- [Mac Focussed? π](#mac-focussed-)
- ["_`iOS` users `spend` more than `double` on `subscriptions` compared to `Android` users_"](#ios-users-spend-more-than-double-on-subscriptions-compared-to-android-users)
- [Install β¬οΈ](#install-οΈ)
- [Mac: Homebrew πΊ](#mac-homebrew-)
- [_Manual_ Install](#manual-install)
- [Installing Flutter SDK](#installing-flutter-sdk)
- [Install `XCode`](#install-xcode)
- [Install Android Studio](#install-android-studio)
- [Install Virtual `Android` Device](#install-virtual-android-device)
- [Installing `Cocoapods`](#installing-cocoapods)
- [Adding plugins to Android Studio](#adding-plugins-to-android-studio)
- [Checking everything](#checking-everything)
- [Windows?](#windows)
- [_Before_ You Start: Run The `Demo App`!](#before-you-start-run-the-demo-app)
- [Running on an emulator](#running-on-an-emulator)
- [Running on a real device](#running-on-a-real-device)
- [Android](#android)
- [iOS](#ios)
- [Troubleshooting possible errors](#troubleshooting-possible-errors)
- [`"No valid code signing certificates were found"`](#no-valid-code-signing-certificates-were-found)
- [`"Unable to verify app. An internet connection is required to verify the trust of the developer ... This app will not be available until verified."`](#unable-to-verify-app-an-internet-connection-is-required-to-verify-the-trust-of-the-developer--this-app-will-not-be-available-until-verified)
- [`"βiproxyβ cannot be opened because the developer cannot be verified"`](#iproxy-cannot-be-opened-because-the-developer-cannot-be-verified)
- [Core Principles π£](#core-principles-)
- [Widgets](#widgets)
- [Stateless widgets](#stateless-widgets)
- [Stateful widgets](#stateful-widgets)
- [Layout](#layout)
- [Assets](#assets)
- [Navigation and routing](#navigation-and-routing)
- [Networking](#networking)
- [Local databases](#local-databases)
- [SQLite](#sqlite)
- [1. Add the dependencies](#1-add-the-dependencies)
- [2. Define a Model](#2-define-a-model)
- [3. Open connection to the database](#3-open-connection-to-the-database)
- [4. Creating table](#4-creating-table)
- [5. CRUD operations](#5-crud-operations)
- [`ObjectBox`](#objectbox)
- [State management](#state-management)
- [Dependency injection](#dependency-injection)
- [Testing π§ͺ](#testing-)
- [Unit testing](#unit-testing)
- [Mock testing](#mock-testing)
- [Integration testing](#integration-testing)
- [A few remarks on `Flutter Web`](#a-few-remarks-on-flutter-web)
- [Adding a splash screen](#adding-a-splash-screen)
- [Decreasing loading time](#decreasing-loading-time)
- [App demo π±](#app-demo-)
- [0. Setting up a new project](#0-setting-up-a-new-project)
- [1. Project structure](#1-project-structure)
- [2. Creating a list of todos](#2-creating-a-list-of-todos)
- [3. Adding interactivity](#3-adding-interactivity)
- [4. Adding navigation](#4-adding-navigation)
- [5. Finishing touches](#5-finishing-touches)
- [6. Testing!](#6-testing)
- [6.1 Unit testing](#61-unit-testing)
- [6.2 Widget testing](#62-widget-testing)
- [6.3 Test coverage](#63-test-coverage)
- [Deployment π¦](#deployment-)
- [i18n π](#i18n-)
- [Final remarks π](#final-remarks-)
# What? π‘
**`Flutter`** is an open-source framework created by Google
for creating multi-platform, high-performance applications
from a single codebase. It makes it easier for you to build
user interfaces that works both on web and mobile devices.
**`Flutter`** uses
[`Dart`](https://github.com/dwyl/learn-dart),
a general-purpose programming language created by **Google**.
If you come from an object-oriented programming language
like `Java`, `C#`,
`Go` or `Javascript/Typescript`,
you will feel right at home.
# Why? π€·
1. **`Flutter`** can be used to build
**cross platform _native_ applications**
(Android, iOS, Desktop and Web)
using the **_same_ codebase**.
This significantly simplifies
maintenance costs and dev headache
when deploying for Android or iOS devices, Desktop, Web and even
[Cars!](https://pub.dev/packages/flutter_carplay)
2. The `Dart` programming language used in `Flutter`
is object oriented and familiar to most developers.
`Flutter` benefits immensely by leveraging `Dart`.
Being a language optimized for UI and compiling to ARM
& x64 machine code for mobile, desktop and backend,
it offers amazing performance benchmarks.
3. **Development times** are **_significantly_ faster**
than other cross-platform frameworks
thanks to stateful hot-reloading
and excellent virtual device support.
If we close the application,
when we open it again
we can continue from where we stopped.
4. `Flutter` has a **_complete_ design system**
with a library of **Material UI widgets**
included which speeds up the
development process.
5. `Flutter` is the fastest-growing mobile development platform
and is wildly used in production worldwide.

`Flutter` overtook React Native 2020 in Google searches,
further showcasing the growing trend of `Flutter`:
https://trends.google.com/trends/explore?date=today%205-y&q=flutter,react%20native

# Who? π€
This repo is useful for anyone
that is interested in mobile and web app development.
For anyone that hasn't yet touched `Flutter`,
it's a *great* place to start to get your computer
ready for `Flutter` development, understand the
**main concepts** and *guide* you to then create
your very first `Flutter` app.
## Mac Focussed? π
While the _installation_ steps below
include Mac-specific steps like `Homebrew` and `XCode`,
this guide can still _easily_ be followed by people
using Linux or Windows as their OS.
The _reason_ we use **`Mac`** is simple:
it's the _only_ way to ship apps for **`iOS`**.
Like it or not, **`iPhone`** now has a
**`50%` Market Share in the US**:
[visualcapitalist.com/iphone-majority-us-smartphones](https://www.visualcapitalist.com/iphone-majority-us-smartphones/)

In Europe, **`iPhone`** ownership/use correlates strongly to wealth of the nation;
[Monaco](https://en.wikipedia.org/wiki/Monaco#Economy)
and
[Norway](https://en.wikipedia.org/wiki/Norway#Economy)
the two countries with the highest GDP/Capita top the table
with
[**`69.91%`**](https://www.reddit.com/r/MapPorn/comments/xx4gp6/percentage_of_iphone_users_in_europe/)
and
[**`68.89%`**](https://gs.statcounter.com/os-market-share/mobile/norway)
respectively.
[mezha.media/en/2022/10/10/percentage-of-iphone-users-in-different-european-countries](https://mezha.media/en/2022/10/10/percentage-of-iphone-users-in-different-european-countries/)

Worldwide **`iPhone`** has a **`~30%` Market Share**:
[gs.statcounter.com/os-market-share/mobile/worldwide](https://gs.statcounter.com/os-market-share/mobile/worldwide)
Mostly because there are _many_ cheap Android devices
that have flooded the market.
But by _far_ the most important fact/stat to pay attention
from an Native Mobile App development perspective is:
### "_`iOS` users `spend` more than `double` on `subscriptions` compared to `Android` users_"
[phonearena.com/news/app-store-users-spend-more-than-double-google-play-users-subscriptions_id138692](https://www.phonearena.com/news/app-store-users-spend-more-than-double-google-play-users-subscriptions_id138692)
So ... if you're building a
[**`SaaS` product**](https://github.com/dwyl/product-roadmap#why-are-we-building-an-app),
you should focus _most_ of your effort
on perfecting the UI/UX on **`iPhone`**.
This is _why_ we use **`Mac`** computers
for our **`Flutter`** dev work.
So we can run **`XCode`**
and test on **`iOS`** devices
and pay our bills.
We would _much_ rather use
a fully Open Source Hardware/Software platform.
e.g:
[Framework](https://github.com/dwyl/hq/issues/565);
We _love_ their
[Mission](https://frame.work/about)!
> Note: We also _love_ that Apple focusses on Privacy.
So while we don't like the vendor lock-in
and often absurd over-pricing
of the Apple ecosystem,
we _definitely_ prefer it
to sharing _all_ our data
with a Google
the way you are forced to with Android. π’
Yes, there are
Google-free privacy focussed versions
of Android, e.g:
[makeuseof.com/tag/using-android-without-google](https://www.makeuseof.com/tag/using-android-without-google/)
but have you tried _using_ them in _practice_?
If you have, please share your experience! π
# Install β¬οΈ
## Mac: Homebrew πΊ
The easiest way to install **`Flutter`**
on a Mac is using **`Homebrew`**:
[brew.sh](https://brew.sh)
After you've installed `brew`,
you can install **`Flutter`**
with the command:
```sh
brew install --cask flutter
```
You should see something similar to:
```sh
==> Downloading https://storage.googleapis.com/releases/stable/macos/flutter
#################################################################### 100.0%
==> Installing Cask flutter
==> Linking Binary 'dart' to '/opt/homebrew/bin/dart'
==> Linking Binary 'flutter' to '/opt/homebrew/bin/flutter'
πΊ flutter was successfully installed!
```
## _Manual_ Install
Installing Flutter might seem like a daunting task.
But do not worry, we'll help you get your local environment
running in no time! Since we are targeting web and mobile,
there are a few tools and SDKs we ought to install first.
These steps will be oriented to Mac/Unix devices but you should
be able to follow if you have a Windows device. If you're ever stuck,
don't be shy!
Please reach out to us and
[open an issue](https://github.com/dwyl/learn-flutter/issues),
we'll get back to you as fast as we can!
## Installing Flutter SDK
Head over to https://docs.flutter.dev/get-started/install,
select your operating system and follow the instructions.
In our case, we're going to download the SDK for
our Mac. After downloading the SDK, you should extract
the `.zip` contents to a wanted location
(in our case, we extracted the folder to our `Home` - `cd ~`).
Now, we ought to update our `PATH` variable so we can access
the binary we just downloaded to our command line. Open your terminal and:
```sh
cd $HOME
nano .zshrc
```
And add `export PATH="$PATH:`pwd`/flutter/bin"` pointing
to the location where you extracted the folder.
Now, if you restart the terminal and type `flutter doctor`,
you should be able to run the command with no problems.
`flutter doctor` checks your environment and displays a report to the
terminal window. It checks it all the necessary tools for development
for all devices are correctly installed. Let's do just that.
## Install `XCode`
If you don't already have **`XCode`** installed,
open your **`AppStore`**, search for `"XCode"`
and press `Install`. It's that easy.
### Install Android Studio
Now targeting for Android devices, we need to install Android SDK and toolkits.
For this, we are going to install Android Studio and work from there.
Head over to https://developer.android.com/studio and download.
After downloading, run the installer and select `Default settings` and let
the installer do its magic. After this, you should be prompted with the following window.
Click on the `More actions` dropdown and click on `SDK Manager`.
You should be prompted with this window:
After installing with default settings, you probably already have
an Android SDK installed. If that's the case, follow through
to `SDK Tools` and check on `Android SDK Command-line Tools`.
And then click `Finish`. This will install the command line tools.
After installing, copy the `Android SDK Location` in the window.
Open a terminal window and type the following to add the SDK path
to the `Path` env variable.
```sh
cd $HOME
nano .zshrc
```
and then add the SDK path you just copied, and save the file
`export ANDROID_HOME=PATH_YOU_JUST_COPIED`
Restart your terminal again and type `flutter doctor --android-licenses`.
This will prompt you to accept the Android licenses. Just type `y` as you read
through them to accept.
#### Install Virtual `Android` Device
The `Android` simulator works on _any/all_ platforms.
With `Android Studio` App open,
find the "Device Manager" icon in the top right:

In the **Device Manager** side-panel,
click on the **`Create device`**:

Select a recent model of `Android` device,
e.g: `Pixel 6` and click **`Next`**:

Accept the default/suggested `API` version and click **`Next`**:

Accept all the defaults and click **`Finish`** to complete your virtual device setup:

Your Device Manager should now list the `Pixel 6` virtual device:

With that in place you can run the demo app below!
> **Note**: if you get stuck on this step,
> a good + quick _video_ tutorial
> for adding simulator devices
in `Android Studio`
is:
[youtu.be/QjgmTiD8prA](https://www.youtube.com/watch?v=QjgmTiD8prA "Add Simulator Device to Android Studio 4 minute tutorial")
[4 mins]
## Installing `Cocoapods`
If you run `flutter doctor` again, you should see we are almost done.
You might see a text saying `CocoaPods not installed`. Let's fix that.
Install [Homebrew](https://brew.sh/) and run `brew install cocoapods`.
And you should be all sorted!
## Adding plugins to Android Studio
If you happen to use Android Studio when developing,
adding the Flutter plugin will help you tremendously.
Just open Android Studio, click on `Plugins`,
search for "Flutter" and click `Install`.
You are asked to "Restart the IDE". Do so and ta-da :tada:, you are done!
## Checking everything
If you run `flutter doctor`, you should have everything in the green.
Congratulations, give yourself a pat on the back, you are **all ready**!
## Windows?
We don't use `Flutter` on `Windows` but plenty of people do.
See:
https://docs.flutter.dev/get-started/install/windows

Extract the file and place the folder in directory `C:`.
It's probably best to create a folder in the directory like this.

This is the console that comes with the Flutter folder
you just downloaded. You can see the devices connected or
even create a project through here.

In order to access Flutter commands through the terminal,
instead of having to open this console, we need to update
our environment variables.
You need to go to the bin folder of the extracted
`.zip` you downloaded and pasted on the `C:` drive
and copy the path.
Then, go to the computer properties, then go to advanced system settings.

Click on environment variables,
go to edit path and paste the path to the extracted
Flutter folder.


As you can see, if you open a new Windows terminal
(also known as `windows prompt`) and
run the `flutter` command, this should prop up.

The rest of the steps should be straight forward.
Just follow the ones on the `Mac` device.
Installing `Android Studio` is the exact same procedure.
If you get stuck `Google` is your friend.
π
# _Before_ You Start: Run The `Demo App`!
_Before_ you start learning,
yes _before_,
try and run the `demo_app`.
Ensure you have everything installed
from the previous steps.
Then follow these instructions:
In your terminal window,
clone this repo to your computer:
```sh
git clone git@github.com:dwyl/learn-flutter.git
```
Change into the `demo_app` directory:
```sh
cd learn-flutter/demo_app
```
Install the necessary dependencies:
```sh
flutter pub get
```
## Running on an emulator
If you are interested in running the app
on an emulator through VSCode,
you can find more details in the
[0. Setting up a new project](#0-setting-up-a-new-project)
section.
## Running on a real device
If you are interested in running the app
on your Android or iOS device,
you should follow these instructions.
### Android
Running the app on an Android device is quite easy.
You first need to [enable developer options](https://developer.android.com/studio/debug/dev-options)
and USB debugging on your device.
You can tap your `device build number` several times
and the "Developer Options" option will come up.
Now it's just a matter of enabling `USB debugging` as well,
and you should be sorted.
After this, you just plug your phone
to your computer with a USB cable.
You can check if the device is properly connected
by running:
```sh
flutter devices
```
And you should be able to see the connected phone.

If you are using Visual Studio,
you can choose the device
in the bottom bar
and pick your phone.
To run,
simply press `F5` or `Run > Start debugging`
and the build process will commence,
and the app will be running on your phone!
> If this is your first time running on an Android device/emulator,
> it might take some time so Gradle downloads all the needed dependencies,
> binaries and respective SDKs to build the app to be run on the app.
> Just make sure you have a solid internet connection.
> **Do not** interrupt the the building process on the first setup.
> This will result in a corrupted `.gradle` file
> and you need to clean up to get the app working again.
> If this happens to you,
> check the [`learn-flutter`](https://github.com/dwyl/flutter-counter-example/tree/update-info#running-on-a-real-device-) repo
> in the `Running on a real device` section to fix this issue.
### iOS
The process is a wee more complicated
because you need an **Apple ID**
to sign up for a [`Developer Account`](https://developer.apple.com/programs/enroll/)
(you also can only build and run the app if you have a Mac computer).
After this having your Developer Account,
open `XCode` and sign in with your ID
(inside `Preferences > Accounts`).

Inside `Manager Certificates`,
click on the "+" sign and
select `iOS Development`.

After this,
plug the device to your computer.
Find the device in the dropdown (`Window > Organizer`).
Below the team pop-up menu,
click on `Fix Issue`
and then on `XCode` click the `Run` button.
In subsequent runs,
you can deploy with VSCode
or any other IDE.
This certificate setup is only needed on the first time with XCode.
#### Troubleshooting possible errors
If you try to run the app through VSCode
with the real iOS device connected,
you might run into errors before you get it working.
Even if you run `flutter clean` and run `flutter build ios`,
you may *still* get some errors.
Let's go through possible scenarios and how to solve them π.
##### `"No valid code signing certificates were found"`
You may get the following output on your terminal:
```
No valid code signing certificates were found
You can connect to your Apple Developer account by signing in with your Apple ID
in Xcode and create an iOS Development Certificate as well as a Provisioning
Profile for your project by:
1- Open the Flutter project's Xcode target with
open ios/Runner.xcworkspace
2- Select the 'Runner' project in the navigator then the 'Runner' target
in the project settings
3- Make sure a 'Development Team' is selected.
- For Xcode 10, look under General > Signing > Team.
- For Xcode 11 and newer, look under Signing & Capabilities > Team.
You may need to:
- Log in with your Apple ID in Xcode first
- Ensure you have a valid unique Bundle ID
- Register your device with your Apple Developer Account
- Let Xcode automatically provision a profile for your app
4- Build or run your project again
5- Trust your newly created Development Certificate on your iOS device
via Settings > General > Device Management > [your new certificate] > Trust
For more information, please visit:
https://developer.apple.com/library/content/documentation/IDEs/Conceptual/
AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html
Or run on an iOS simulator without code signing
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
No development certificates available to code sign app for device deployment
```
To resolve this, we just need to follow the instructions!
- open `/{ProjectName}/ios/Runner.xcworkspace` with XCode.
- click on the `Runner` project on the left side pane.
- go to `Signing & Capabilities`.
- set the `Team` to your personal Apple ID account.
- change the `Bundle Identifier` to something valid.
- press `Try Again` button so XCode creates a valid signing.
> [!NOTE]
>
> `Flutter` apps usually have an ID of `com.example.app`,
> which is not valid by default when running on iOS devices.
> Simply change it to something else and it should work!
> (`com.iamanawesometurtle.app` works, for example)
##### `"Unable to verify app. An internet connection is required to verify the trust of the developer ... This app will not be available until verified."`
Even if XCode properly signs your app,
you might not be able to get it to run on your iPhone.
This is because **the device doesn't trust you as a developer**.
To debug iOS `Flutter` apps,
you need to turn on `Developer Mode` on your iPhone.
You can do that under `Settings > Privacy & Security > Developer Mode`.
Now you need your iPhone to *trust you*.
To do this,
go to `Settings > General > Device Management`
and click on your developer profile.
After that, click on `Trust YOUR_NAME`.
All of your apps will be trusted by your iPhone π₯³.
##### `"βiproxyβ cannot be opened because the developer cannot be verified"`
Even after all of the above steps,
you may *still* get the following error pop up on your computer.
Luckily, this has a rather simple solution!
As per https://stackoverflow.com/questions/71359062/iproxy-cannot-be-opened-because-the-developer-cannot-be-verified,
you really only need to open your terminal,
navigate to the location of the `Flutter SDK`
(which is usually in `~/flutter`),
and run a command!
```sh
cd FLUTTER SDK DIRECTORY/flutter/bin/cache/artifacts/usbmuxd
sudo xattr -d com.apple.quarantine iproxy
```
And you're done!
You can optionally go to your Mac's
`Settings > Privacy & Security`,
scroll down to the `Secutiry` section
and click on the `Open Anyway` button
that appears by a text stating
`"iproxy" was blocked from opening because it is not from an identified developer`.
And you're done!
You should be able to run your application on a real iOS device now!
π₯³
# Core Principles π£
If you have had experience in mobile development prior to `Flutter`,
you will find the learning curve quite manageable,
as `Flutter` foundations are built upon a few principles
that are present in both.
Let's take a look at these.
If you want an in-depth guide and learn every aspect of Flutter,
check the official documentation -> https://flutter.dev/learn
## Widgets
In `Flutter` _everything_ is a **`Widget`**.
A `Widget` is a **UI building block**
you can use to assemble your app.
You will build your UI out of `Widgets`.
They essentially describe
what *the view should look like*
given their current `state`.
If the `state` changes,
the `Widget` rebuilds
and checks the diff
to determine the minimal changes
to transition from `state` `t0` to `t1`.
In the following `GIF`
the sample counter app
contains a total of **6 `Widgets`**:

Image attribution: https://uxplanet.org/why-you-should-use-google-flutter-42f2c6ba036c
1. The **container** widget `Scaffold` starting
on line 38 groups all other widgets in the layout.
2. The ***`appBar`*** widget displays the text "Flutter Demo Home Page"
3. The ***`body`*** contains a **child** widget which in turn has **Text** and a **$_counter** placeholder.
4. The ***`floatingActionButton`*** is the button that gets clicked,
it contains a **child** which is the icon.
Examples of Widgets include
dialog windows, buttons, icons, menus, scroll bars and cards.
You can use one of the many built-in Material UI widgets
or create your own from scratch.
A widget can be defined as:
- Physical elements of an application (buttons, menus or bars)
- Visual elements such as colors
- Layout and positioning of elements on the screen using a grid system
Widgets are assembled in declarative hierarchy
which allows us to easily organize
the layout of our App as a series of nested widgets.

Screens are composed of several small widgets that have only one job.
Groups of widgets are assembled together to build a functional application.
For example, a Container widget contains other widgets
that have functions like layout, placement and size.
A basic screen layout is controlled by combining
a container and other smaller widgets as their children.
This was seen in the gif above. The `Scaffhold` widget
warps three widgets.
Remember, Widgets aren't necessarily visual elements within the application.
In the gif above, the second child `body` widget also uses
a widget named `Center` that, as the name implies, centers
its children within the screen. It's *controlling* the
aspects of their child and displaying them centered.
There are several other widgets that have a similar behaviour,
such as padding, alignment, row, columns, and grids.
### Stateless widgets
Widgets are not all stateless. Stateless widgets never change.
They receive arguments from their parent, store them in `final` member variables
(`final` is analogous to a `const`ant variable). When a widget is asked
to `build()`. it uses these stored values and renders.
Here's what a stateless widget looks like:
```dart
class MyAppBar extends StatelessWidget {
const MyAppBar({required this.title, super.key});
// Fields in a Widget subclass are always marked "final".
final Widget title;
@override
Widget build(BuildContext context) {
return Container(
height: 56.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(color: Colors.blue[500]),
// Row is a horizontal, linear layout.
child: Row(
children: [
const IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null disables the button
),
// Expanded expands its child
// to fill the available space.
Expanded(
child: title,
),
const IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
```
We notice straight away the widget is a subclass of
[`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html).
All widgets have a `Key key` (`super.key`)
as an optional parameter in their constructor.
The `key` is used by the **`Flutter` engine**
at the step of recognizing which widget
in a list has changed.
It's more useful when you have a list of widgets
*of the same type*
that can potentially be removed or inserted.
This `MyAppBar` widget takes as argument a `title`.
This effectively becomes the `field` of the widget,
and is used in the `Expanded` children widget.
Additionally, since this is a widget (more specifically,
a subclass of `Stateless Widget`), we have to
implement the `build()` function.
This is what is rendered.
This widget could be used in a container
and be one of its children
like so:
```dart
MyAppBar(
title: Text(
'Example title',
),
),
```
Simple enough, right?
Here the `MyAppBar` is the parent widget,
`title` is a property
and `Text` is the child widget.
### Stateful widgets
While stateless widgets are static (never change),
**stateful widgets** are dynamic.
For example: they change appearance or behavior
according to events triggered by user interaction
or when it receives data.
For example `Checkbox`, `Slider`, `Textfield`
are examples of
[`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html).
A widget's state is stored in a `State` object.
Therefore, we _separate_ the widget's state from its appearance.
Whenever the state changes,
the `State` object calls `setState()`,
thus rerendering the widget.
Let's see some code!
```dart
import 'package:flutter/material.dart';
class Counter extends StatefulWidget {
// Counter is the Stateful Widget, different from the appearance.
// It holds the state configuration and values
// provided by the parent and used by the build method
// of the State (no values are provided in this instance)
// Fields in a Widget subclass are always marked
const Counter({super.key});
@override
State createState() => _CounterState();
}
class _CounterState extends State {
int _counter = 0;
void _increment() {
setState(() {
// This call to setState tells the Flutter framework
// that something has changed in this State, which
// causes it to rerun the build method below so that
// the display can reflect the updated values. If you
// change _counter without calling setState(), then
// the build method won't be called again, and so
// nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called,
// for instance, as done by the _increment method above.
// The Flutter framework has been optimized to make
// rerunning build methods fast, so that you can just
// rebuild anything that needs updating rather than
// having to individually changes instances of widgets.
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
const SizedBox(width: 16),
Text('Count: $_counter'),
],
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Counter(),
),
),
),
);
}
```
Let's unpack the code above.
The `StatefulWidget` and `State` are separate objects.
The former (being the first one)
declares its state by using the `State` object.
The `State` object is declared right after, initializing an `int _counter` at `0`.
It declares an `_increment()` function that calls `setState()`
(indicating the state is going to be changed) and increments the `_counter` variable.
As with any widget, the `build()` method
makes use of the `_counter` variable
to display the number of times the button is pressed.
Everytime it is pressed,
the `_increment()` function is called,
effectively changing the state and incrementing it.
## Layout
As we've already stated,
the core of `Flutter` are widgets.
In fact, almost everything is a widget - even layout models.
The things you see are widgets.

But things that you *don't see* are also widgets.
We mentioned this before but we'll understand it better now.
For any web or mobile app development,
we need to create layouts to organize our components in and
make it look _shiny_ β¨ and _good-looking_ π¨.
This example is taken from the official docs:
https://docs.flutter.dev/development/ui/layout#lay-out-a-widget
Layout | Layout with padding and delimited borders
:-------------------------:|:-------------------------:
 | 
So, you may ask,
**how many widgets are there in this menu**?
Great question!
There are visible widgets but also widgets that
*help us* lay out the items correctly, center them and space
them evenly to make it look good.
Here's how the widget tree looks like for this menu:

The **pink nodes** are **containers**.
They are **_not_ visible**
but help us **customize** its **child widget**
by adding
`padding`, `margin`, `border`, `background color`, etc...
Let's see a code example of an invisible widget that will
center a block of `text` in the middle of the screen:
```dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(color: Colors.white),
child: const Center(
child: Text(
'Hello World',
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 32,
color: Colors.black87,
),
),
),
);
}
}
```
The `Center` widget centers all its children inside of it.
`Center` is *invisible* but is a widget nonetheless.
This yields the following result:

See? Isn't it so simple? π
You can create _any_ Layout you wish just
by encapsulating widgets
and ordering them accordingly.
## Assets
Sometimes we need images and assets to be displayed in our App.
Common resources are:
image files,
static data (`JSON` files),
videos, buttons and icons.
In Flutter, we use a `pubspec.yaml` file
(often located at the root of the project) to
require assets in the app.
```yaml
flutter:
assets:
- directory/
- assets/my_icon.png
```
> There's a nuanced behavior when loading assets.
> If you have two files ` .../graphics/background.png` and
> `.../graphics/dark/background.png` and the `pubspec.yaml` file
> contains the following:
> ```yaml
> flutter:
> assets:
> - graphics/background.png
> ```
> Both are imported and included in the asset bundle.
> One is considered the **main asset** and the other
> a **variant**.
> This behavior is useful for images of different resolutions.
There are two ways of accessing the loaded access.
Each `Flutter` app has a `RootBundle`
for easy access to the main asset bundle.
You can import directly
using the `rootBundle` global static.
However, inside a widget context,
it's recommended to obtain the asset bundle
for the widget `BuildContext` using the
[`DefaultAssetBundle`](https://api.flutter.dev/flutter/widgets/DefaultAssetBundle-class.html).
This approach allows the parent widget to substitute a different
asset bundle at runtime, which is useful for localization
or testing purposes.
Here's a code example for the `rootBundle` approach:
```dart
import 'package:flutter/services.dart' show rootBundle;
Future loadAsset() async {
return await rootBundle.loadString('assets/config.json');
}
```
Here's a code example
for the recommended approach
inside a widget:
```dart
String data = await DefaultAssetBundle.of(context).loadString("assets/data.json");
final jsonResult = jsonDecode(data); //latest Dart
```
## Navigation and routing
Most web and mobile apps aren't just a single page/screen.
The person using the App
needs to navigate between screens to
perform several distinct actions,
be it checking the details of a product
or shopping cart.
`Flutter` provides a `Navigator` widget
to display screens as a stack,
using the native transition animations of the target device.
Navigating between screens necessitates the route's
`BuildContext` (which can be accessed through the widget)
and is made by calling methods like
`push()`
and
`pop()`.
Here's code showcasing navigating between two routes:
```dart
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
const FirstRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
const SecondRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Route'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go back!'),
),
),
);
}
}
```
This basic code example showcases two routes,
each one containing only a single button.
Tapping the one on the first route
will navigate to the second route.
Clicking on the button of the second route
will return the user to the first route.
We are using the `Navigator.push()` and `Navigator.pop()`
functions to achieve this, by passing the context of
the widget.
Additionally, we are leveraging `MaterialPageRoute` to
transition between routes using a platform-specific animation
according to the [Material Design guidelines](https://m3.material.io/).
Here's how it should look!

If your application has
advanced navigation and routing requirements
(which is often the case with web apps
that use direct links to each screen,
or an app with multiple `Navigator` widgets),
consider using a routing package like
[`go_router`](https://pub.dev/packages/go_router).
This package allows parsing the route path
and configure the `Navigator`
whenever an app receives, a deep link.
## Networking
For most apps, fetching data from the Internet is a _must_.
Luckily, fetching data from the Internet is a breeze.
Let's do it!
Firstly, we need to add the
[`http`](https://pub.dev/packages/http)
package to the dependencies section
in the `pubspec.yaml` file.
This file can be found at the root of your project.
Add the package to the dependency list and import it:
```yaml
dependencies:
http: 0.13.5
```
Then in your project file
add the following `import` statement
to _use_ the package:
```dart
import 'package:http/http.dart' as http;
```
We also need to change the
`AndroidManifest.xml` file
to add Internet permission on Android devices.
This file can be found in the
`/android/app/src/main`
on newly created projects.
Add the following line:
```xml
```
Now, to make a network request,
use code similar to the following:
```dart
Future fetchAlbum() {
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1'));
}
```
Calling `http.get()` returns a
[`Future`](https://github.com/dwyl/learn-dart#asynchronous-events)
that contains a `Response`.
`Future` is a class to work with asynchronous operations.
It represents a potential value that will occur in the future.
While `http.Response` has our data,
it's much more useful to translate it
to a logical class
that has all the **fields** of the data
we expect to receive from the **REST API**.
We can convert `http.Response` to a `Todo` class,
representing a "todo item".
Let's create that class!
```dart
class Todo {
final int id;
final String title;
final bool completed
const Todo({
required this.id,
required this.title,
required this.completed
});
factory Todo.fromJson(Map json) {
return Todo(
id: json['id'],
title: json['title'],
completed: json['completed'],
);
}
}
```
We can create a function
that makes the `http request`
and, if it is ***successful***,
tries to parse the data
and create a `Todo` object
or raise an an error
if the `http request` is
***unsuccessful***.
```dart
Future fetchTodos() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Todo.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load todos');
}
}
```
How would we call this inside a widget?
We could do this inside `initState()`!
It is called exactly one time and never again!
Do **not** put an API call in the `build()` method
(unless you know what you are doing).
This method is called every time a render occurs,
which is quite often!
```dart
class _MyAppState extends State {
late Future futureTodo;
@override
void initState() {
super.initState();
futureTodo = fetchTodo();
}
// Β·Β·Β·
}
```
Finally, to display the data,
we use the `FutureBuilder` widget.
As the name implies,
it's a widget that handles async data operations.
```dart
FutureBuilder(
future: futureTodo,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
)
```
The `future` parameter
relates to object we want to work with.
In this case, it is a parsed `Todo` object.
The `builder` function tells `Flutter`
what needs to be rendered,
depending on the current state of `Future`,
which can be *loading*, *success* or *error*.
Depending on the result of the operation, we
either show the error, the data or a loading animation
while we wait for the http request to fulfill.
Isn't it easy? π€
## Local databases
Sometimes, we need to persist and query
large amounts of data on the local device.
In these cases we use database
instead of a local file or key-value store.
In this walkthrough,
we present two alternatives:
`SQLite` and `ObjectBox`.
### SQLite
SQLite is one of the most popular methods for storing data locally.
For this demo, we will use the package
[`sqflite`](https://pub.dev/packages/sqflite).
Sqflite is one of the most used and updated packages
to connect to SQLite databases in Flutter.
#### 1. Add the dependencies
To work with SQLite databases, we need
to import two dependencies.
We'll use `sqflite` to interact with the `SQLite` database,
and
[`path`](https://pub.dev/packages/path)
to define the location
for storing the database on disk.
```dart
dependencies:
flutter:
sdk: flutter
sqflite:
path:
```
And import the packages in the file you are working in.
```dart
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
```
#### 2. Define a Model
Let's take a look at the data we are going to store.
Let's define a class for the table we are going to create
in SQLite.
```dart
class Item {
final int id;
final String text;
final bool completed;
const Item({
required this.id,
required this.text,
required this.completed,
});
// Convert an Item into a Map. The keys must correspond to the names of the
// columns in the database.
Map toMap() {
return {
'id': id,
'text': text,
'completed': completed,
};
}
// Implement toString to make it easier to see information about
// each item when using the print statement.
@override
String toString() {
return 'Item{id: $id, text: $text, completed: $completed}';
}
}
```
#### 3. Open connection to the database
To open a connection to the SQLite database,
we are going to define the path to the database file
using `path`
**and**
open the database with `sqflite`.
```dart
WidgetsFlutterBinding.ensureInitialized();
// Open the database and store the reference.
final database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'item_database.db'),
);
```
#### 4. Creating table
To create the table to store our items, we must first
verify the number of columns and type refer
exactly to the ones we defined in the class.
After this, it's just a matter of running the appropriate
`SQL` expression to create the table.
```dart
final database = openDatabase(
join(await getDatabasesPath(), 'item_database.db'),
// When the database is first created, create a table to store items.
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
'CREATE TABLE items(id INTEGER PRIMARY KEY, text TEXT, completed INTEGER)',
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
```
#### 5. CRUD operations
Now that we have a database created, alongside the
table, to create, update, list and insert Items is
quite easy! Check the following piece of code.
```dart
Future crudOperations(Item item) async {
// Get a reference to the database
final db = await database;
// Insert an Item into the table.
await db.insert('items', item.toMap())
// Retrieve list of items
// and convert the List