preface

How is scrolling in the Flutter project implemented on the previous blog? This gives you an overview of several ways that the Flutter scroll Widget can be easily used in a project. This blog will continue to cover two other important aspects of Flutter – Dart asynchronous operations and network requests.

  • Dart’s asynchronous model
  • Dart’s asynchronous operation
  • Asynchronous complement to Dart
  • Network request method [encapsulation]

Hopefully, you can learn about Flutter in one or two months, and you will surely have the development level of Flutter! I will continue to provide quality blog content to you, currently covering objective-C, Swift, Flutter, small program development. Welcome to praise blog and pay attention to myself, common progress is the purpose of sa ~~~

Dart’s asynchronous model

First, how does Dart handle asynchronous operations

2.1 Dart is single-threaded

2.1.1 Time-consuming operations in the program

Time-consuming operations in development:

  • In the development, there are often some time-consuming operations to complete, such as network requests, file reads and so on;

  • If the main thread waits for these time-consuming operations to complete, it blocks and cannot respond to other events, such as a user click;

  • Obviously, we can’t do that!!

How to handle time-consuming operations:

Different languages have different ways of handling time-consuming operations.

  • Processing method 1: multithreading, such as Java and C++, generally starts a new Thread (Thread), completes these asynchronous operations in the new Thread, and then passes the data to the main Thread through inter-thread communication.

  • Method 2: Single thread + event loop. For example, JavaScript and Dart are all based on single thread + event loop to complete time-consuming operations. But how can a single thread perform time-consuming operations?

2.1.2 Single-thread asynchronous operations

Is it full of greetings to single-threaded asynchronous operations??

None of them actually conflict:

  • Because an application is idle most of the time, it does not interact with the user indefinitely.

  • For example, waiting for user click, network request data return, file read and write IO operations, these waiting behavior does not block the thread;

  • This is because IO, such as network requests and file reads and writes, can be called on a non-blocking basis;

Blocking and non-blocking calls

If you want to understand this point, you need to know the concept of blocking and non-blocking invocation in the operating system. Blocking and non-blocking are concerned with the state of the program while it waits for the result of the call (message, return value).

  • Blocking call: The current thread is suspended until the result of the call is returned, and the calling thread will not resume execution until it gets the result of the call.

  • Non-blocking calls: After the call is executed, the current thread does not stop executing. It just needs to wait for a while to check if the result is returned.

Let’s do it with an example

You are hungry at noon and need to order a takeaway. The action of ordering a takeaway is our call, and getting the last takeaway is the result we have to wait for. Blocking calls: ordering takeout and not doing anything is just waiting stupidly for your thread to stop doing anything else. Non-blocking calls: You order a takeaway and continue to do something else: you continue to work, play a game, and your thread is not doing anything else, just checking occasionally to see if there's a knock on the door or if the takeaway has arrived.Copy the code

Many time-consuming operations in development can be invoked on a non-blocking basis

  • For example, the network request itself uses Socket communication, and Socket itself provides a SELECT model, which can work in a non-blocking manner.

  • For example, for file read and write I/O operations, you can use the event-based callback mechanism provided by the operating system.

How does a single thread handle the results of network traffic, IO operations, and so on? The answer is the Event Loop.

2.2 Dart Event Loop

2.2.1 What is an event loop

The single-threaded model basically maintains an Event Loop.

  • In fact, the Event loop is not complicated, it is a series of events (including click events, IO events, network events) to be processed in an Event Queue.

  • Continually fetching events from the Event Queue and executing their corresponding blocks of code until the Event Queue is empty.

Pseudocode for event loops:

List eventQueue = []; List eventQueue = []; var event; While (true) {if (eventQueue.length > 0) {eventQueue.removeat (0); // Execute the event event(); }}Copy the code

When there are events, such as click events, IO events, and network events, they are added to the eventLoop, and when it is found that the event queue is not empty, the event is fetched and executed.

2.2.2 Event loop code simulation

Take a look at pseudocode to understand how click events and network request events are executed:

  • This is a Flutter code, many things you may not understand, but with patience you will understand what is going on. A button, RaisedButton, executes the onPressed function when clicked.

  • The onPressed function sends a network request and executes the callback function in THEN when the request succeeds.

The simulation code is as follows:

RaisedButton( child: Text('Click me'), onPressed: () { final myFuture = http.get('https://example.com'); myFuture.then((response) { if (response.statusCode == 200) { print('Success! '); }}); },)Copy the code

How is this code executed in an event loop?

  1. When the user clicks, the onPressed callback is placed into an event loop and a network request is sent during execution.

  2. Instead of blocking when the network request is sent, the event loop is discarded after the onPressed function is finished.

  3. After the network request is successful, the callback function passed in THEN is executed, which is also an event. The event is put into the event loop for execution, and the event loop discards it after execution.

While onPressed is a little different than the callback in THEN, they both tell the event loop that I have a piece of code that needs to be executed, help me get it done.

Dart’s asynchronous operation

Asynchronous operations in Dart use futures and async and await. If you have experience with front-end ES6 and ES7 programming, Future can be interpreted as Promise, async and await are basically the same as in ES7. But what about Future and async and await without front-end development experience?

3.1 to know the Future

Let’s slowly introduce Future.

3.1.1 Synchronous Network Requests

Let’s start with an example:

  • In this example, getNetworkData is used to simulate a network request;

  • The network request takes three seconds, after which the data is returned;

The code is as follows:

import "dart:io";

main(List<String> args) {
  print("main function start");
  print(getNetworkData());
  print("main function end");
}

String getNetworkData() {
  sleep(Duration(seconds: 3));
  return "network data";
}
Copy the code

What does this code run like?

GetNetworkData blocks execution of main

Main function start // Wait 3 seconds Network data main function endCopy the code

Obviously, the above code is not the desired execution because the network request blocks the main function, which means that all subsequent code cannot continue to execute properly.

3.1.2 Asynchronous Network Requests

Modify the above code as follows:

import "dart:io";

main(List<String> args) {
  print("main function start");
  print(getNetworkData());
  print("main function end");
}

Future<String> getNetworkData() {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    return "network data";
  });
}
Copy the code

Take a look at the result of this code:

main function start
Instance of 'Future<String>'
main function end
Copy the code

Let’s see what the code looks like:

  • 1. The code was executed sequentially without any blocking phenomenon;

  • 2. Instead of printing the result directly, print an instance of the Future.

  • Conclusion: A time-consuming operation is isolated so that it no longer affects the main thread.

  • Question: How to get the final result?

Get the result of the Future

main(List<String> args) { print("main function start"); Var Future = getNetworkData(); Future.then ((value) {print(value); future.then(value) {print(value); }); print(future); print("main function end"); }Copy the code

The result of executing the above code:

Main function start Instance of 'Future<String>' main function end // 3sCopy the code

Throwing an exception

If an exception occurs during the call and the result cannot be obtained, how can I obtain the information about the exception?

import "dart:io"; main(List<String> args) { print("main function start"); var future = getNetworkData(); future.then((value) { print(value); }). CatchError ((error) {print(error); }); print(future); print("main function end"); } Future<String> getNetworkData() { return Future<String>(() { sleep(Duration(seconds: 3)); // return "network data"; Throw Exception(" Network request error "); }); }Copy the code

The result of executing the above code:

Main function start Instance of 'Future<String>' main function endCopy the code

3.1.3 Future use supplement

Supplement 1: summary of the case above

Through a case to learn some Future use process:

  • Create a Future (either by us, or by calling an internal API or a third-party API, you need to get an instance of a Future, which usually encapsulates asynchronous operations);

  • 2. Use.then(success callback) to listen for the result of the Future’s internal execution.

  • CatchError messages from Future catchError via.catcherror

Supplement two: The two states of the Future

Throughout its implementation, a Future is typically divided into two states:

  • State 1: Uncompleted State When an operation inside a Future is performed (in the case above, the specific network request process, modeled with latency), the process is called incomplete

  • When an operation inside a Future completes, it usually returns a value or throws an exception. In both cases, we call the Future a completed state.

Supplement 2: Chain calls to Future

The above code can be improved ** : **

You can continue to return values in THEN, and the result will be returned in the next chained THEN call callback

import "dart:io"; main(List<String> args) { print("main function start"); getNetworkData().then((value1) { print(value1); return "content data2"; }).then((value2) { print(value2); return "message data3"; }).then((value3) { print(value3); }); print("main function end"); } Future<String> getNetworkData() { return Future<String>(() { sleep(Duration(seconds: 3)); Return "network data1"; }); }Copy the code

The print result is as follows:

Main function start main function end network data1 Content data2 message data3Copy the code

Add 4: Future other apis

  • Future.value(value) : Directly retrieves a completed Future that calls the then callback function

The code is as follows:

main(List<String> args) { print("main function start"); Future.value(" hahaha "). Then (value) {print(value); }); print("main function end"); }Copy the code

The print result is as follows:

Main function start main function end hahahaCopy the code
  • Future.error(object) : Gets a completed Future directly, but an exception Future, and calls the catchError callback directly

The code is as follows:

main(List<String> args) { print("main function start"); Future.error(Exception(" error ")).catcherror ((error) {print(error); }); print("main function end"); }Copy the code

The print result is as follows:

Main function start Main function end Exception: error messageCopy the code
  • Future.delayed(time, callback function) : the callback function is executed when a certain amount of time is delayed, and the callback for then is executed after the callback function is executed;

The code is as follows:

main(List<String> args) { print("main function start"); Future.delayed(Duration(seconds: 3), () {return "info 3 seconds later "; }).then((value) { print(value); }); print("main function end"); }Copy the code

3.2 await, async

3.2.1 Understanding of theoretical concepts

What is await, async? —– they allow us to use synchronous code format, to implement asynchronous call process.

We already know that a Future can do this without blocking our thread, allowing it to continue executing, change its state when it completes an operation, and call back then or errorCatch callbacks.

How do you generate a Future?

  • 1. Use the Future constructor you learned earlier, or any other Future API you learned later.

  • 2. Another way is through async functions.

3.2.2 Case code drill

To modify the Future asynchronous processing code to await, async form.

The code will not execute if written directly like this:

  • Because future. delayed returns a Future object, it cannot be used as synchronous returned data: “network data”

  • You can’t use asynchronous code as if it were synchronous!

The code is as follows:

import "dart:io"; main(List<String> args) { print("main function start"); print(getNetworkData()); print("main function end"); } String getNetworkData() { var result = Future.delayed(Duration(seconds: 3), () { return "network data"; }); Return "request data:" + result; }Copy the code

Now modify the following line of code with await:

String getNetworkData() async { var result = await Future.delayed(Duration(seconds: 3), () { return "network data"; }); Return "request data:" + result; }Copy the code

You will find:

  • Future.delayed is preceded by an await. The await keyword must exist in the async function.

  • Running the code still returns an error, which is obvious: functions that use the async flag must return a Future object.

Continue to modify the code as follows:

Future<String> getNetworkData() async { var result = await Future.delayed(Duration(seconds: 3), () { return "network data"; }); Return "request data:" + result; }Copy the code

This code should be ideal for execution

  • You can now use the result returned asynchronously by the Future as if it were synchronous code;

  • Wait for the results to be joined with other data, and then return together;

  • You don’t need to wrap a Future when you return it, you just return it, but the return value is wrapped in a Future by default

Asynchronous complement to Dart

4.1 Task Execution Sequence

4.1.1 Microtask queue

Know that Dart has an Event Loop to execute the code, and there is an Event Queue in it, and the Event Loop constantly pulls the Event execution from the Event Queue.

But there is another Queue that exists in Dart, if strictly divided: the Microtask Queue.

  • The priority of microtask queue is higher than that of event queue.

  • In other words, the event loop executes the tasks in the microtask queue first and then the tasks in the event queue.

So in Flutter development, which should be placed in the event queue and which should be placed in the microtask queue?

  • All external event tasks are in the event queue, such as IO, timer, click, and draw events.

  • Microtasks are often derived from within Dart and are few and far between. This is because if there are too many micro-tasks, the event queue will not be queued up and the execution of the task queue will be blocked (for example, when the user clicks and does not respond).

At this point, it might be a little messy, but how does code actually execute in a single-threaded Dart?

  • Dart’s entry is the main function, so the code in the main function takes precedence.

  • 2. After the main function is executed, an Event Loop will be started and the tasks in the queue will be executed.

  • 3. First, all tasks in the Microtask Queue are executed in a first-in, first-out order.

  • 4. Second, all tasks in the Event Queue are executed in a first-in, first-out order.

4.1.2 How do I Create a Microtask

In development, scheduleMicrotask under Async in DART can be used to create a microtask:

import "dart:async";

main(List<String> args) {
  scheduleMicrotask(() {
    print("Hello Microtask");
  });
}
Copy the code

During development, if you have a task that you don’t want queued in an Event Queue, you can create a microtask.

Will the Future’s code join the event queue or the microtask queue?

There are usually two function bodies in a Future:

  • The body of the function passed in by the Future constructor

  • The body of the function then (catchError)

So what queue are they joining?

  • The body of the function passed in by the Future constructor is placed in the event queue

  • The function body of then is divided into three cases:

  • If the Future is not executed, then is added directly to the function body of the Future.

  • If the Future is then executed, the body of the then function is put into the microtask queue, and the microtask queue is executed after the current Future is executed.

  • Case 3: If Future is a chained call, it means that the next THEN will not be executed.

The code is as follows:

Future(() => print("future_1")). Then ((_) => print(" THEN_1 ")); Future(() => null). Then ((_) => print("then_2")); / / future_3, then_3_a, then_3_b, in turn, join the eventqueue Future (() = > print (" future_3 ")), then ((_) = > print (" then_3_a ")), then ((_) = >  print("then_3_b"));Copy the code

4.1.3 Code execution sequence

Learn a final code execution sequence example from the previous rule:

import "dart:async";

main(List<String> args) {
  print("main start");

  Future(() => print("task1"));
 
  final future = Future(() => null);

  Future(() => print("task2")).then((_) {
    print("task3");
    scheduleMicrotask(() => print('task4'));
  }).then((_) => print("task5"));

  future.then((_) => print("task6"));
  scheduleMicrotask(() => print('task7'));

  Future(() => print('task8'))
    .then((_) => Future(() => print('task9')))
    .then((_) => print('task10'));

  print("main end");
}
Copy the code

The code execution result is as follows:

main start
main end
task7
task1
task6
task2
task3
task5
task4
task8
task9
task10
Copy the code

Code analysis:

  • 1. The main function is executed first, so main start and main end are executed first without any problems;

  • 2. During the execution of main, some tasks will be added to EventQueue and MicrotaskQueue respectively;

  • Task7 is called by scheduleMicrotask, so it is added to the MicrotaskQueue first and is executed first.

  • Task1 is added to the EventQueue and executed.

  • Final Future = Future(() => null); The then of the created future is added to the micro-task. The micro-task is executed first, so task6 is executed.

  • Add task2, task3, task5 to EventQueue

  • Task4 () {scheduleMicrotask () {scheduleMicrotask ();}} ScheduleMicrotask is called as part of task3, so task4 is executed after Task5.)

  • 8, task8, task9, task10 are added to EventQueue once;

In fact, the order in which the code is executed above is likely to show up in an interview. This kind of complex nesting usually doesn’t occur in development, and it needs to be fully understood

4.2 Utilization of multi-core CPU

4.2.1 Understanding of Isolate

In Dart, there is a concept of Isolate. What is it?

  • You already know that Dart is single-threaded, and that this thread has its own memory space that it can access and an event loop that it needs to run;

  • You can call this spatial system an Isolate;

  • For example, there is a Root Isolate in Flutter, which is responsible for the code that runs Flutter, such as UI rendering, user interaction, and so on.

Within the Isolate, resource isolation is very good. Each Isolate has its own Event Loop and Queue.

  • The Isolate does not share any resources and communicates with each other only through the message mechanism. Therefore, resource preemption is not a problem.

However, having only one Isolate means that only one thread can be used forever, which is a waste of resources for multi-core cpus.

If you have a lot of time consuming computation during development, you can create your own Isolate and perform the computation in an independent Isolate.

How do you create an Isolate?

Creating isolate. spawn is relatively easy, we can create it using isolate. spawn:

import "dart:isolate"; main(List<String> args) { Isolate.spawn(foo, "Hello Isolate"); } void foo(info) {print(" new ISOLATE: $info"); }Copy the code

4.2.2 Isolate Communication mechanism

However, in real development, you wouldn’t just start a new Isolate and not care about the results:

  • The new Isolate is required for calculation and the results are reported to the Main Isolate (which is the Isolate enabled by default).

  • The Isolate uses SendPort to communicate messages.

  • The send channel of Main Isolate can be passed to it as a parameter when the Isolate is started and concurrency occurs;

  • Concurrency when execution is complete, this pipe can be used to send messages to the Main Isolate;

The code is as follows:

import "dart:isolate"; main(List<String> args) async { // 1. ReceivePort = ReceivePort(); Create new Isolate Isolate Isolate = await isolate. spawn<SendPort>(foo, receiveport.sendport); ReceivePort. Listen ((data) {print(' data: $data'); Receiveport.close (); // Need to kill isolate? .kill(priority: Isolate.immediate); }); } void foo(SendPort sendPort) { sendPort.send("Hello World"); }Copy the code

But our communication above becomes one-way communication, what if we need two-way communication?

  • In fact the code for two-way communication is a bit more cumbersome;

  • Flutter provides a compute function that supports concurrent computations and internally encapsulates the creation and two-way communication of Isolate.

  • It takes full advantage of multi-core cpus and is very simple to use.

Network request mode

There are three common web requests in Flutter: HttpClient, HTTP library, and DIO library

5.1 HttpClient

Dart HttpClient is a request class that implements basic network request operations in the I/O package.

Network calls generally follow the following steps:

  1. Create a client.

  2. Construct the Uri.

  3. Initiate a request and wait for the request. You can also configure headers and body requests.

  4. Close the request and wait for the response.

  5. Decode the contents of the response.

Network request instance:

void requestNetwork() async { // 1. Final HttpClient = HttpClient(); / / 2. The build request uri = final uri uri. Parse (" http://123.207.32.32:8000/api/v1/recommend "); Final Request = await httpClient.geturl (uri); // 4. Final response = await request.close(); if (response.statusCode == HttpStatus.ok) { print(await response.transform(utf8.decoder).join()); } else { print(response.statusCode); }}Copy the code

HttpClient can also send post-related requests.

HttpClient can send normal network requests, but exposes too much detail:

  • For example, you need to actively close the request and manually decode the string after getting the data

In development, most web requests are made directly to HttpClient, using libraries instead.

5.2 Http library

HTTP is another network request class that Dart officially provides and is much easier to use than HttpClient.

However, there is no default integration into Dart’s SDK, so you need to rely on it in PubSpec first:

Import and use

import 'package:http/http.dart' as http; void httpNetwork() async { // 1. Create Client final Client = http.client (); / / 2. Build a uri final url = uri. Parse (" http://123.207.32.32:8000/api/v1/recommend "); // 3. Final response = await client.get(url); If (response.statusCode == httpStatus.ok) {print(response.body); } else { print(response.statusCode); }}Copy the code

5.3 Dio Tripartite Library

The official HttpClient and HTTP can send network requests normally, but for modern application development, there are usually more requirements: interceptors, cancel requests, file upload/download, timeout Settings, etc.

At this point, we can use a tripartite library that is very popular in Flutter: Dio

The official website explains dio:

Dio is a powerful Dart Http request library with support for Restful apis, FormData, interceptors, request cancellation, Cookie management, file upload/download, timeouts, custom adapters, and more…

Using the Dio tripartite library also necessarily requires relying on it in PubSpec first

Code walkthroughs:

import 'package:dio/dio.dart'; void dioNetwork() async { // 1. Final Dio = Dio(); / / 2. Send the final network request response = await dio. Get (" http://123.207.32.32:8000/api/v1/recommend "); If (response.statusCode == httpstatus.ok) {print(response.data); Else {print(" Request failed: ${response.statuscode}"); }}Copy the code

Dio library package

http_config.dart

class HTTPConfig {
  static const baseURL = "https://httpbin.org";
  static const timeout = 5000;
}
Copy the code

http_request.dart

import 'package:dio/dio.dart'; import 'package:testflutter001/service/config.dart'; class HttpRequest { static final BaseOptions options = BaseOptions( baseUrl: HTTPConfig.baseURL, connectTimeout: HTTPConfig.timeout); static final Dio dio = Dio(options); static Future<T> request<T>(String url, {String method = 'get', Map<String, dynamic> params, Interceptor inter}) async { // 1. Final options = options (method: method); Interceptor dInter = InterceptorsWrapper(onRequest: (RequestOptions options) {// 1. You can add a loading display // 2 to any network request. Many pages must be accessed with a Token, so you can determine that there is a Token // 3. Print (" intercepted request "); return options; }, onResponse: (Response Response) {print(" intercepted Response "); return response; }, onError: (DioError error) {print(" error "); return error; }); List<Interceptor> inters = [dInter]; if (inter ! = null) { inters.add(inter); } dio.interceptors.addAll(inters); Response Response = await dio.request<T>(url, queryParameters: params, options: options); return response.data; } on DioError catch(e) { return Future.error(e); }}}Copy the code

Code use:

HttpRequest.request("https://httpbin.org/get", params: {"name": "why", 'age': 18}).then((res) {
  print(res);
});

HttpRequest.request("https://httpbin.org/post",
                    method: "post", params: {"name": "why", 'age': 18}).then((res) {
  print(res);
});
Copy the code

Conclusion:

This article introduces Flutter asynchronism and network requests in detail to familiarize you with the use of Flutter and requests in your project. From the above, you can write a simple web request and list display page.

All the basic knowledge of Flutter should be covered so far, and we will go into the Flutter project later!!

You can write previous Demo examples manually. We believe that one or two related blog posts per week will deepen your understanding of the Flutter project.

Thank you for your praise and attention to me, common progress, mutual encouragement!!