0% found this document useful (0 votes)
19 views92 pages

Slide 08 - Classes, Futures and Local Storage - Tagged

Uploaded by

gogetassj4ul4
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)
19 views92 pages

Slide 08 - Classes, Futures and Local Storage - Tagged

Uploaded by

gogetassj4ul4
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/ 92

CLASSES

AND
FUTURES
Classes , Constructs, Asynchronous Programming
Slides 08
OOP
• Dart supports object oriented programming
(OOP).
• OOP languages are based on the concept of objects
that hold both data (called fields) and code (called
methods). These objects are created from blueprints
called classes that define the fields and methods an
object will have.
• In Dart, everything is an object—that is, every value
stored in a variable is an instance of a class.
Additionally, all objects also extend the Object class,
directly or indirectly.
• The following also applies:
• Dart classes can have both instance members
(methods and fields) and class members (static
methods and fields).
Dart Class Example
class Person {
String? firstName;
String? lastName;
String getFullName() => "$firstName $lastName";
}

main() {
Person somePerson = Person();
somePerson.firstName = "Clark";
somePerson.lastName = "Kent";
print(somePerson.getFullName()); // prints Clark Kent
}
Class Example: Explanation
• In
the previous example, we have not defined a constructor for the class, but,
as you may have guessed, there's a default empty constructor (no
arguments) already provided for us.
• Toinstantiate a class, we call the constructor invocation. Unlike in many OOP
languages, there is no need to use the new keyword (although it is a reserved
word and will appear in older code).
• The class does not have an ancestor class explicitly declared, but it does
implicitly inherit from Object, as do all classes in Dart.
• Theclass has two fields, firstName and lastName, and a getFullName() method
that concatenates both by using string interpolation and returning the result.
• The class does not have any get or set accessor declared, so how did we
access firstName and lastName to mutate it? A default get/set accessor is
defined for every field in a class.
• Thedot class.member notation is used to access a class member, whatever it
is—a method or a field (get/set).
Field getters and setters
Getters and setters allow us to access a field on a class, and
every field has these accessors, even when we do not define
them.

class Person {
String? firstName;
String? lastName;
String get fullName => "$firstName $lastName";
String get initials => "${firstName[0]}. ${lastName[0]}.";
}
main() {
Person somePerson = new Person();
somePerson.firstName = "Clark";
somePerson.lastName = "Kent";
print(somePerson.fullName); // prints Clark Kent
print(somePerson.initials); // prints C. K.
}
Static fields
In some cases, you may want to share a value or method between all of the object instances
of a class. The static fields are associated with the class, rather than any object instance.

class Person {
static String personLabel = "Person name:";
String get fullName => "$personLabel $firstName $lastName";
// modified to print the new static field "personLabel"
}

main() {
Person somePerson = Person();
somePerson.firstName = "Clark";
somePerson.lastName = "Kent";
Person anotherPerson = Person();
anotherPerson.firstName = "Peter";
anotherPerson.lastName = "Parker";
print(somePerson.fullName); // prints Person name: Clark kent
print(anotherPerson.fullName); // prints Person name: Peter Parker
Person.personLabel = "name:";
print(somePerson.fullName); // prints name: Clark Kent
print(anotherPerson.fullName); // prints name: Peter Parker
}
Encapsulation
• Dart does not have explicit access restrictions on
fields and methods, unlike the famous keywords used
in Java—protected, private, and public.
• InDart, encapsulation occurs at the library level
instead of the class level.
• Dartcreates implicit getters and setters for all fields in
a class, so you can define how data is accessible to
consumers and the way it changes.
• InDart, if an identifier (ID) (that is, a class, class
member, top-level function, or variable) starts with an
underscore (_), it's private to its library where a
library is normally the contents of a single file.
Inheritance
• Inheritance allows us to extend a class definition
to a more specialized type.
• Dart permits single direct inheritance. A class
can only inherit from one other class, leading to
a strict tree structure.
• Dart does not contain a final class directive,
unlike other languages. This means that a class
can always be extended.
Inheritance
Dart allows us to extend defined classes using the extends keyword,
where all of the members of the parent class are inherited, except the
constructors

class Student extends Person {


String nickName;
Student(String firstName,String lastName,this.nickName,) :
super(firstName, lastName);
@override String toString() => "$fullName, aka $nickName";
}
main() {
Student = Student("Clark", "Kent", "Kal-El");
print(student);
}
Composition
•Composition is a more modular approach in which
a class contains instances of other classes that
bring their own abilities with them.

•Composition is used extensively in Flutter's UI


framework. If you were to examine the code for
Flutter's Container widget, you'd find it's
composed of many other widgets, the exact
nature of which depend on the arguments passed
to the Container during instantiation. Examples:
•Padding
•Margin
Composition versus
inheritance
Inheritance derives one class from another. For example,
you may have a class such as Vehicle and subclasses of
Car and Motorbike. The Car and Motorbike classes would
inherit the abilities of the Vehicle class and then add their
own specializations. In this instance, Car is a Vehicle and
Motorbike is a Vehicle.

Composition defines a class as the sum of its parts. For


example, you may have an Engine class and a Wheel
class. In this model, a Car is composed of an Engine, four
Wheels, and other specializations; a Car has an Engine
and a Car has Wheels. Composability is less rigid than
inheritance and allows for things such as dependency
injection and modifications at runtime.
Abstraction
•Abstraction is a process whereby we define a
type and its essential characteristics, moving to
specialized types from parent ones.
•Dartcontains abstract classes that allow a
definition of what something does/provides,
without caring about how this is implemented.
•Darthas the powerful implicit interface concept,
which also makes every class an interface,
allowing it to be implemented by others without
extending it.
abstract class Msg {
void Send(String message);
}
Abstract classes
• InOOP, abstract classes are classes that cannot be instantiated. An
abstract class may have abstract members without an implementation,
allowing it to be implemented by the child types that extend them

abstract class Person {


String firstName;
String lastName;
Person(this.firstName, this.lastName);
String get fullName;
}
class Student extends Person {
//... other class members
@override String get fullName => "$firstName $lastName";
}
Polymorphism
• Polymorphism is achieved by inheritance and can be
regarded as the ability of an object to behave like
another; for example, the int type is also a num type.

• Dartallows overriding parent methods to change their


original behavior.
• Polymorphism is exemplified in Dart by the @override
metatag. With it, a subclass's implementation of an
inherited behavior can be specialized to be appropriate to
its more specific subtype.

• Dartdoes not allow overloading in the way you may be


familiar with.
Functions as objects
• Dartis called a true object-oriented language. In Dart,
even functions are objects, which means that you can
do the following:

• Assign a function as a value of a variable and pass it


as an argument to another function

• Return it as a result of a function as you would do


with any other type, such as String and int

• This
is known as having first-class functions because
they're treated the same way as other types.
Files and imports
• If
you look at the main.dart file in the HelloWorld
project, you will see the following import statement at
the very top:
import 'package:flutter/material.dart';

• In this example, the material.dart file is being


imported, with all the classes and functions within that
file being made available to your class. This file holds
lots of the basic constructs needed to create Flutter
widgets, so most files in a Flutter project will need this
import.
Generics and Dart literals
• In some examples we use the [] and {} literals to initialize
collections.

main() {
var placeNames = <String>["Middlesbrough", "New York"];
var landmarks = <String, String>{
"Middlesbrough": "Transporter bridge",
"New York": "Statue of Liberty",
};
}

• Specifying the type of list seems to be redundant in this case as the Dart
analyzer will infer the string type from the literals we have provided. In
some cases, this is important, such as when we are initializing an empty
collection:
var emptyStringArray = <String>[];
ASYNCHRONOUS
PROGRAMMING
Asynchronous
programming
Dart is a single-threaded programming language,
meaning that all of the application code runs in the
same thread. Put simply, this means that any code
may block thread execution by performing long-
running operations such as input/output (I/O) or
HyperText Transfer Protocol (HTTP) requests.
This can obviously be an issue if your app is stuck
waiting for something slow such as an HTTP
request while the user is trying to interact with it.
The app would effectively freeze and not respond
to the user's input.
Asynchronous
programming
Asynchronous is a non-blocking architecture, so
the execution of one task isn't dependent on
another. Tasks can run simultaneously.
Synchronous is a blocking architecture, so the
execution of each operation depends on
completing the one before it. Each task requires an
answer before moving on to the next iteration.
Futures
•However, although Dart is single-threaded, it can
perform asynchronous operations through the use
of Futures. This allows your code to trigger an
operation, continue doing other work, and then
come back when the operation has been
completed. To represent the result of these
asynchronous operations, Dart uses the Future
object combined with the async and await
keywords.
Explain
• When our code calls a method that is a long-running task but
we don't want to block execution of other parts of the app such
as the user interface (UI), we can mark the method as
asynchronous using the async keyword. This tells all code that
calls that method that it may be long-running, so you shouldn't
block thread execution while waiting for the result. We can
then call the method and continue the execution of other code.
• However, we may want to get a result from the long-running
method, so we need to come back to the method call after the
long-running method has returned. To do this, we specify that
we want to return when there is a response, using the await
keyword.
•A key distinction between async and await is that the method
itself declares its asynchronicity using async, but it is the code
calling the method that specifies it will return when there is a
response through the use of await.
Example
import 'dart:io';
void longRunningOperation() {
for (int i = 0; i < 5; i++) {
sleep(Duration(seconds: 1));
print("index: $i");
}
}
main() {
print("start of long running operation");
longRunningOperation();
print("continuing main body");
for (int i = 10; i < 15; i++) {
sleep(Duration(seconds: 1));
print("index from main: $i");
}
print("end of main");
}
Output
start of long running operation
index: 0
index: 1
index: 2
index: 3
index: 4
continuing main body
index from main: 10
index from main: 11
index from main: 12
index from main: 13
index from main: 14
end of main
Asynchronous Example
import 'dart:io';
import 'dart:async';
Future<void> longRunningOperation() async {
for (int i = 0; i < 5; i++) {
sleep(Duration(seconds: 1));
print("index: $i");
}
}
main() {
print("start of long running operation");
longRunningOperation();
print("continuing main body");
for (int i = 10; i < 15; i++) {
sleep(Duration(seconds: 1));
print("index from main: $i");
}
print("end of main");
}
Output
start of long running operation
index: 0
index: 1
index: 2
index: 3
index: 4
continuing main body
index from main: 10
index from main: 11
index from main: 12
index from main: 13
index from main: 14
end of main
Asynchronous Example
import 'dart:io';
import 'dart:async';
Future<void> longRunningOperation() async {
for (int i = 0; i < 5; i++) {
await Future.delayed(Duration(seconds: 1));
print("index: $i");
}
}
main() {
print("start of long running operation");
longRunningOperation();
print("continuing main body");
for (int i = 10; i < 15; i++) {
sleep(Duration(seconds: 1));
print("index from main: $i");
}
print("end of main");
}
Output
start of long running operation
continuing main body
index from main: 10
index from main: 11
index from main: 12
index from main: 13
index from main: 14
end of main
index: 0
index: 1
index: 2
index: 3
index: 4
Dart Isolates
• We can execute truly parallel code and improve the
performance and responsiveness of our app using Dart
Isolates.
• Every Dart application is composed at least of one
Isolate instance—the main Isolate instance where all of
the application code runs. So, to create parallel
execution code, we must create a new Isolate instance
that can run in parallel with the main Isolate instance,
as illustrated in the following diagram:
Dart Isolates
•Using isolates, your Dart code can perform
multiple independent tasks at once, using
additional processor cores if they're available.
Isolates are like threads or processes, but each
isolate has its own memory and a single thread
running an event loop.

• Most modern devices have multi-core CPUs. To take


advantage of multiple cores, developers sometimes
use shared-memory threads running concurrently.
Instead of threads, all Dart code runs inside isolates.
The main isolate
• Dartprograms run in the main isolate by default. It’s
the thread where a program starts to run and execute,
as shown in the following figure:
Event handling
•In a client app, the main isolate's event queue
might contain repaint requests and notifications
of tap and other UI events. For example, the
following figure shows a repaint event, followed
by a tap event, followed by two repaint events.
The event loop takes events from the queue in
first in, first out order.

•Event handling happens on the main isolate after


main() exits. In the following figure, after main()
exits, the main isolate handles the first repaint
event. After that, the main isolate handles the tap
event, followed by a repaint event.
Background workers
•ifyour app's UI becomes unresponsive due to a
time-consuming computation—
parsing a large JSON file, for example—consider
offloading that computation to a worker isolate,
often called a background worker.

•A worker isolate can perform I/O (reading and


writing files, for example), set timers, and more. It
has its own memory and doesn't share any state
with the main isolate. The worker isolate can
block without affecting other isolates.
Using isolates
• There are two ways to work with isolates in Dart,
depending on the use-case:
• Use Isolate.run() to perform a single computation on
a separate thread.
• Use Isolate.spawn() to create an isolate that will
handle multiple messages over time, or a
background worker.

•The static Isolate.run() method requires one


argument:
a callback that will be run on the newly
spawned isolate.
Example
int slowFib(int n) => n <= 1 ? 1 : slowFib(n - 1) +
slowFib(n - 2);

// Compute without blocking current isolate.


void fib40() async {

var result = await Isolate.run( () => slowFib(40) );


print('Fib(40) = $result');

}
Asynchronous Example
import 'dart:io';
import 'dart:isolate';
Future<void> longRunningOperation(String message) async {
for (int i = 0; i < 5; i++) {
await Future.delayed(Duration(seconds: 1));
print("$message: $i");
}
}
void main() async {
print("start of long running operation");
Isolate.spawn(longRunningOperation, "Hello");
print("continuing main body");
for (int i = 10; i < 15; i++) {
await Future.delayed(Duration(seconds: 1));
print("index from main: $i");
}
print("end of main");
}
Output
start of long running operation
continuing main body
index from main: 10
Hello: 0
index from main: 11
Hello: 1
index from main: 12
Hello: 2
index from main: 13
Hello: 3
index from main: 14
end of main
Hello: 4
ASYNCHRONOUS
PROGRAMMING
Asynchronous
• Asynchronous programming allows your app to
complete time-consuming tasks, such as retrieving an
image from the web, or writing some data to a web
server, while running other tasks in parallel and
responding to the user input.

• Asynchronous operations do not stop the main line


of execution, and therefore they allow the execution of
other tasks before completing.
Synchronous Programming
• When you write your code, you generally expect your instructions to
run sequentially, one line after the other (Synchronous Programming):

int x = 5;
int y = x * 2;

• You expect the value of y to be equal to 10 because the instruction


int x = 5 completes before the next line. In other words, the second
line waits for the first instruction to complete before being executed.

• Thispattern works perfectly, but in some cases, and specifically, when


you need to run instructions that take longer to complete, this is not
the recommended approach, as your app would be unresponsive until
the task is completed.
Asynchronous
• Inthe diagram, you can see how the main execution line, which
deals with the user interface, may call a long-running task
asynchronously without stopping to wait for the results, and when
the long-running task completes, it returns to the main execution
line, which can deal with it.

• Dart
is a single-threaded language, but despite this, you can use
asynchronous programming patterns to create responsive apps.
• In
Dart and Flutter, you can use the Future class to perform
asynchronous operations
HTTP
EXAMPLE
Http Example
• Letscreate an app that
reads data from a web
service using the http
library. Specifically, we will
read JSON data from the
Google Books API.
• Steps:

• 1. Create an App
• 2. Add http dependency
Flutter pub add http
• 3.
Update pubspec.yaml for
dependency
• 4.Method to retrieve data
from web service
2. Add http dependency
• pubspec.yaml:

dependencies:
flutter:
sdk: flutter
Http: ^1.2.2

• Ifusing macOS, you should enable networking: in


macos/Runner/DebugProfile. entitlements and
macos/Runner/Release.entitlements files add the following
lines:

<key>com.apple.security.network.client</key>
<true/>
Method
•Create a method getData that retrieves some data from a web
service:
import 'package:http/http.dart' as http;
Future<Response> getData() async {
const authority = 'www.googleapis.com';
const path = '/books/v1/volumes/junbDwAAQBAJ';
Uri url = Uri.https(authority, path);
return http.get(url);
}

• In general, you use Futures to get a single asynchronous operation result


(like uploading data to a web server).
• Whena method returns a Future, it does not return the actual value but the
promise of returning a value at a later time.
Button
• In order to call the getData() method when the user taps the ElevatedButton:

ElevatedButton(
child: Text('GO!'),
onPressed: (){
setState(() {});
getData()
.then((value) {
result = value.body.toString().substring(0, 450);
setState(() {});
}).catchError((_){
result = 'An error occurred';
setState(() {});
});
},
),
Explanation
1. You call getData.
2. The execution continues.
3. At some time in the future, getData returns a Response.
4. The then function is called, and the Response value is passed to
the function.
5. You update the state of the widget calling setState and showing
the first 450 characters of the result.
• Afterthe then() method, you concatenate the catchError function.
This is called if the Future does not return successfully: in this case,
you catch the error and give some feedback to the user.
•A Future may be in one of three states:
1. Uncompleted: You called a Future, but the response isn’t
available yet.
2. Completed successfully: The then() function is called.
3. Completed with an error: The catchError() function is called.
Using async/await to avoid
callbacks
• Futures, with their then callbacks, allow developers to
deal with asynchronous programming. There is an
alternative pattern to deal with Futures that can help
make your code cleaner and easier to read and
maintain: the async/await pattern.

• async is used to mark a method as asynchronous, and it


should be added before the function body.

• await is used to tell the framework to wait until the


function has finished its execution and returns a value.
While the then callback works in any method, await only
works inside async methods.
.then vs Async/Await
1. ElevatedButton(
child: Text('GO!'),
onPressed: (){
getData()
.then((value) {
result = value.body.toString().substring(0, 450);
setState(() {});
});
},),

2. ElevatedButton(
child: Text('GO!'),
onPressed: () async {
Response value = await getData();
result = value.body.toString().substring(0, 450);
setState(() {});
}),
Another
Example ASYNC/AWAIT
Example 2: on async/await
• In this example will have one button and a text.
• Clicking the button will call 3 functions and place the
total in text.
3 Functions Code
class _HomePageState extends State<HomePage> {
String result= "";

Future<int> returnOneAsync() async {


await Future.delayed(const Duration(seconds: 3));
return 1;
}

Future<int> returnTwoAsync() async {


await Future.delayed(const Duration(seconds: 3));
return 2;
}

Future<int> returnThreeAsync() async {


await Future.delayed(const Duration(seconds: 3));
return 3;
}
Counting Code
Future count() async {
int total = 0;
total = await returnOneAsync();
total += await returnTwoAsync();
total += await returnThreeAsync();

setState(() {
result = total.toString();
});
}
ElevatedButton
ElevatedButton(child: Text("Total"),
onPressed: () async {
count();
setState((){ });

},),
Text(result),

Try out your app: you should see the result 6 after 9
seconds,
How it works
• The great feature of the async/await pattern is that you
can write your code like sequential code, with all the
advantages of asynchronous programming (namely, not
blocking the UI thread). After the execution of the await
statement, the returned value is immediately available to
the following lines, so the total variable in our example
can be updated just after the execution of the await
statement.
• The three methods used in this recipe (returnOneAsync,
returnTwoAsync, and returnThreeAsync) each wait 3
seconds and then return a number using Future.delayed.
• Future.delayed creates a Future that completes after a
specified amount of time. This can be useful when you
want to delay the execution of some code.
COMPLETE Class

R
Completing Futures
•Using a Future with then, catchError, async, and
await is probably enough for most use cases, but
there is another way to deal with asynchronous
programming in Dart and Flutter: the Completer
class.

•Completer creates Future objects that you can


complete later with a value or an error. We will
be using Completer in this example.
Example: Completer
At the top of the main.dart file, import dart:async:
import 'package:async/async.dart’;

2. Add the following code in the _HomePageState class:


late Completer completer;

Future getNumber() async {


completer = Completer<int>();
await Future.delayed(const Duration(seconds : 5));
completer.complete(42);
return completer.future;
}
Elevated Button
• Add the following code in the onPressed() function:

ElevatedButton(child: Text("Go!"),
onPressed: () {
getNumber().then((Value){
setState(() {
result = Value.toString();
});
});
},),
Text(result),

• On
click, you’ll notice that after 5 seconds delay, the
number 42 should show up on the screen
How it works ..
• A Completer creates Future objects that can be completed later. In
other words, it allows you to control when a Future is completed. In our
example, the Completer.future that’s set in the getNumber method is
the Future that will be completed once complete is called. When you
call the getNumber() method, you are returning a Future, by calling the
following:
return completer.future;
• The getNumber() method performs async function, which waits 5
seconds (here, you could place any long-running task), and calls the
completer.complete method. Completer.complete changes the state of
the Completer, so that you can get the returned value in a then()
callback.
• Completers are very useful when you call a service that does not use
Futures, and you want to return a Future. It also de-couples the
execution of your long-running task from the Future itself, and this can
be useful when the completion of the Future depends on the outcome
of some other asynchronous task.
completeError
• You can also call the completeError method of a
Completer when you need to deal with an error:

calculate() async {
try {
await new Future.delayed(const Duration(seconds : 5));
completer.complete(42);
// throw Exception();
}
catch (_) {
completer.completeError({});
}
Firing multiple Futures
at the same time
• When you need to run multiple Futures at the same time, there is a
class that makes the process extremely easy: FutureGroup.
FutureGroup is available in the async package, which must be
imported into your dart file:
import 'package:async/async.dart’;
• FutureGroup is a collection of Futures that can be run in parallel.
As all the tasks run in parallel, the time of execution is generally
faster than calling each asynchronous method one after another.
• When all the Futures of the collection have finished executing, a
FutureGroup returns its values as a List, in the same order they
were added into the group.
• You can add Futures to a FutureGroup using the add() method, and
when all the Futures have been added, you call the close() method
to signal that no more Futures will be added to the group.
Example
• Add the following code in the _FuturePageState class, in the main.dart:

void returnFG() {
FutureGroup<int> futureGroup = FutureGroup<int>();
futureGroup.add(returnOneAsync());
futureGroup.add(returnTwoAsync());
futureGroup.add(returnThreeAsync());
futureGroup.close();
futureGroup.future.then((List<int> Listvalue) {
int total = 0;
for (var element in Listvalue) {
total += element;
}
setState(() {
result = total.toString();
});
});
}
Using Futures with
StatefulWidgets
• As mentioned previously, while Stateless widgets do not
keep any state information, StatefulWidgets can keep
track of variables and properties, and in order to update the
UI, you use the setState() method. State is information that
can change during the life cycle of a widget.
• There are four core lifecycle methods that you can leverage
in order to use StatefulWidgets:

• initState() is only called once when the State is built.


You should place the initial setup and starting values for
your objects here. Whenever possible, you should prefer this
to the build() method.
• build() gets called each time something changes. This
will destroy the UI and rebuild it from scratch.
• deactivate() and dispose() are called when a widget is
removed from the tree: use cases of these methods include
closing a database connection or saving data before
ASYNCRONOU
S IN PAGE
NAVIGATION
Example
•You will learn how to leverage Futures using
Navigator to transform a Route into an async
function: you will push a new screen in the app and
then await the route to return some data and update
the original screen.
•The steps we will follow are these:
1. Adding an ElevatedButton that will launch the
second screen.
2. On the second screen, we will make the user
choose a color.
3. Once the color is chosen, the second screen will
update the background color on the first screen.
Example
• First Page • Second Page
First Page
• Create the following method:

class _HomepageState extends State<Homepage> {

Color color = Colors.blue.shade700;

Future _navigateAndGetColor(BuildContext context) async {


color = await Navigator.push(context,
MaterialPageRoute(
builder:
(context) => const NavigationSecond()
), ) ?? Colors.blue;
setState(() {});
}
Second Page
ElevatedButton( •Create three buttons
child: const Text('Red'),
onPressed: () {
color = Colors.red.shade700;
Navigator.pop(context, color); • Thekey for this code to
}),
work is passing data
ElevatedButton( from the
child: const Text('Green'),
onPressed: () {
Navigator.pop() method
color = Colors.green.shade700; from the second screen.
}),
Navigator.pop(context, color); The first screen is
expecting a Color, so
ElevatedButton(
child: const Text('Blue'),
the pop method returns
onPressed: () { a Color to the
color = Colors.blue.shade700;
Navigator.pop(context, color);
NavigationFirst screen.
}),
RESULT FROM
DIALOG
AlertDialog
•An AlertDialog can be used to show pop-up
screens that typically contain some text and
buttons, but could also contain images or other
widgets. AlertDialogs may contain a title, some
content, and actions. The actions property is
where you ask for the user’s feedback (think of
“save,” “delete,” or “accept”).

•Thereare also design properties such as elevation


or background, shape or color, that help you
make an AlertDialog well integrated into the
design of your app.
Example
• Wewill ask the user to choose a color in the dialog, and then
change the background of the calling screen according to the
user’s answer, leveraging Futures.
_showColorDialog(BuildContext context) async {
await
showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return
AlertDialog(
title: const Text('Very important question'),
content: const Text('Please choose a color'),
actions: <Widget>[
TextButton(
child: const Text('Red'),
onPressed: () {
color = Colors.red.shade700;
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Green'),
onPressed: () {
color = Colors.green.shade700;
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Blue'),
onPressed: () {
color = Colors.blue.shade700;
Navigator.pop(context, color);
}),
],
);
},
);
setState(() {});
}
How it works ..
• In
the preceding code, note that the barrierDismissible
property tells whether the user can tap outside of the dialog
box to close it (true) or not (false). The default value is true,
but as this is a “very important question,” we set it to false.
• Theway we close the alert is by using the Navigator.pop
method, passing the color that was chosen: in this, an Alert
works just like a Route.
• Nowwe only need to call this method from the onPressed
property of the “Change color" button:
onPressed: () {
_showColorDialog(context);
});
• This
recipe showed the pattern of waiting asynchronously for
data coming from an alert dialog.
LOCAL STORAGE
Shared Preferences
Saving data simply with
SharedPreferences
• At
its core, SharedPreferences stores key-value pairs
on disk. More specifically, only primitive data can be
saved: numbers, Booleans, Strings, and stringLists. All
data is saved within the app.

• When using SharedPreferences, the first step is adding


the reference to the pubspec.yaml file, since this is
an external library. Then, you need to import the
library at the top of the file where you need to use it.
Setup
• First,
add a dependency to shared_preferences. From
your Terminal, type the following command:

flutter pub add shared_preferences

• Toupdate the dependencies in your project, run the


flutter pub get command from your Terminal window.

• In the main.dart file, import shared_preferences:


import
'package:shared_preferences/shared_preferences.dart';
Next
• The next step is getting an instance of
SharedPreferences. You can do that with the following
instruction:
SharedPreferences prefs = await
SharedPreferences.getInstance();

• ThegetInstance() method returns a


Future<SharedPreferences>, so you should always
treat this asynchronously; for example, using the
await keyword in an async method.
Save/Write values
• You
can read and write values to your device. To read
and write values, you use a key.

• For example, let’s take a look at the following instruction:


await prefs.setInt('appCounter', 42);

• Thisinstruction writes in a key named appCounter with


an integer value of 42. If the key does not exist, it gets
created; otherwise, you just overwrite its value.

• setInt
writes an int value. Quite predictably, when you
need to write a String, you use the setString method.
Read/Get values
• When you need to read a value, you still use the key,
but you use the getInt method for integers and the
getString method for Strings.
Delete
•The last feature of this recipe was deleting the
value from the device. We achieved this by using
the following instruction:
await prefs.clear();

•This deletes all the keys and values for the app.
Summary
• Using a Future
• Using async/await to remove callbacks
• Completing Futures
• Firing multiple Futures at the same time
• Resolving errors in asynchronous code
• Using Futures with StatefulWidgets
• Using FutureBuilder to let Flutter manage your Futures*
• Turning navigation routes into asynchronous functions
• Getting the results from a dialog
DEPENDENCY
INJECTION
Dependency Injection In
Flutter
• Dependency injection is a crucial concept in Flutter,
just as it is in many other programming frameworks. It
helps you manage the dependencies of your
application and make it more testable, maintainable,
and flexible.

• Dependency injection is a software design pattern


that deals with the management and injection of
dependencies into a class or module from the outside.
It is essential to decouple components, making your
code more maintainable, testable, and flexible.
• InFlutter, dependencies can be any external resource
or service, such as databases, REST APIs, shared
preferences, or even other classes.
Why Use Dependency
Injection in Flutter?

1. Testability: By injecting dependencies, you can


easily substitute real implementations with mock or
test implementations, allowing for unit testing
without touching the actual resources.
2. Decoupling: Dependency injection helps separate
the concerns and responsibilities of different
components, making your code more modular and
easier to maintain.
3. Flexibility: It’s easier to change or switch
dependencies without modifying the dependent
code. For example, you can switch between
different database providers without changing the
business logic.
Disadvantages of
Dependency Injection
• Increased Complexity: Introducing DI, especially
when done manually, can add a layer of complexity to
your code with more classes and interfaces to manage.

• BoilerplateCode: Some DI setups can involve writing


a decent amount of code for object creation and
dependency wiring, though this can be mitigated with
DI frameworks.

• Overuse: It’s possible to overuse DI, even in cases


where it provides minimal benefit. This can lead to
unnecessary complexity.
When to implement Dependency
Injection in Your Project?
• Testability:When you prioritize extensive unit testing, DI is a
godsend, allowing you to isolate components and test them
independently.

• Flexibility: If you anticipate needing to swap out implementations


(like switching databases, logging mechanisms, or external services),
DI provides a seamless way to do so.

• Long-term maintainability: When you want to minimize the ripple


effects of changes, making your code more robust over time, DI
fosters loose coupling and promotes long-term health.

• Code reusability: If you want to write components that are highly


reusable in different contexts, DI keeps them free from directly
managing their dependencies.
Types of Dependency
Injection in Flutter
• There are several ways to implement dependency
injection in Flutter. I’ll cover three common
approaches:

1. Constructor Injection: Dependencies are injected


through a class’s constructor.
2. Setter Injection: Dependencies are injected using
setter methods.
3. Provider Injection: Dependencies are provided
using a global service locator.
Constructor Injection
Example
class MyService {

final MyDependency dependency;

MyService(this.dependency);

void doSomething() {
dependency.performAction();
}
}
In this example, MyService depends on MyDependency,
and the dependency is injected via the constructor. This
allows you to provide different implementations of
MyDependency as needed.
Setter Injection Example
class MyService {
MyDependency dependency;

void setDependency(MyDependency newDependency) {


dependency = newDependency;
}

void doSomething() {
dependency.performAction();
}
}
Here, the MyService class has a setter method to inject
the dependency. You can change the dependency at
runtime by calling setDependency().
Provider Injection
Example
• Provider injection uses a service locator to provide
dependencies globally. A popular package for this in Flutter
is get_it.
• import'package:get_it/get_it.dart';
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerSingleton<MyDependency>(MyDependen
cyImpl());
}
class MyService {
MyDependency dependency = locator<MyDependency>();
void doSomething() {
dependency.performAction();
}
}
Explanation
• Inthis example, get_it is used to manage and provide
dependencies. The setupLocator() function registers the
dependency, and then it can be accessed from the MyService
class using the locator.

• Dependency injection is a powerful technique to make your


Flutter applications more modular, maintainable, and testable.
The choice of which method to use (constructor, setter, or
provider injection) depends on your project’s specific
requirements and design patterns.

• Byapplying dependency injection, your Flutter code becomes


more flexible, making it easier to adapt to changes, switch
implementations, and test different components independently.

You might also like