Slide 08 - Classes, Futures and Local Storage - Tagged
Slide 08 - Classes, Futures and Local Storage - Tagged
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
• 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';
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.
}
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.
int x = 5;
int y = x * 2;
• 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
<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);
}
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.
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= "";
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.
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:
• 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.
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 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.