One, foreword

After learning the layout and interaction examples, I can get started with Flutter. I can basically implement some common page building and interaction effects. However, this is far from enough. Now all apps need network access, and today’s goal is to learn IO and network.

Asynchronous task message loop in Dart

Dart is a single-threaded model. What is a single-threaded model? Single threading is when a program executes, the path of the program is arranged in sequential order, the first must be handled before the next can be executed (that is, only one operation can be performed at a time). Life actually, for example, when going to work in the morning, need fingerprint puncher, was about to punch, suddenly calls, you answer the phone, call to clock in, from the phone to clock in this operation is a single thread, that is to say, when I answer the phone, clock in this operation is blocked, need to call to clock out. What is asynchrony? In the computer world, asynchrony means that a thread does not have to wait forever, but continues to perform the following operation, regardless of the state of other threads, and is notified to do so when a message returns. Just like in ordinary life, I need to cook now, I am not waiting for the rice to cook to do other things, but the rice cooker cook button press, and then you can watch TV, read a book, etc., when the rice is ready, the button of the rice cooker will jump to the heat preservation state, at this time you can eat. I also thought about synchronous and asynchronous requests for OkHttp in Android:

  • Synchronization: Sends a network request to the background, waits for the background to return the data result, and then sends the next network request
  • Asynchronous: Sends a network request to the background without waiting for the background to return data. The next network request can be sent anytime

But there is something different about the asynchronism of Flutter, which is explained below.

1. Event cycle system

1.1. The Event – stars

Dart is an Event loop and an Event Queue. EventLooper executes all events in sequence.

Dart
Event
Event
Event queue
Event loop
EventQueue
Event

1.2. Single-threaded model

When a Dart function starts execution, it is executed until the end of the function, meaning that the function is not interrupted by other code. First, I want to explain what IS THE ISOLATE in Dart. The isolate itself stands for isolated, with its own memory and a single thread controlled entity, because the internal logic between the isolates is isolated and the code of the ISOLates is executed sequentially. Dart allows concurrent use of ISOLates. Isolates are similar to Threads, but there is no shared memory between them. A Dart program starts with the Main function of Main ISOLATE. In our daily development, the default environment is Main ISOLATE. The Main function of App startup is an ISOLATE. The Main ISOLATE thread starts processing each Event in the Event Queue one by one.

1.3.Dart message loop and message queue

A DartMain ISOLATE has only one message loop (Event Looper) and two message queues: the Event queue and the MicroTask queue.

  1. The Event queue contains all foreign events: I/O, Mouse Events, Drawing Events, timers, messages between isolate, etc
  2. MicroTask queue inDartThis is necessary because sometimes event handlers want to do something later but want to do it before the next event message is executed.

The Event queue contains Dart and other events in the system. MicroTask only contains Dart code, so the Event Looper processes the two queues in the following order. When the main method exits, the Event Looper begins its work. Microtasks are first executed in FIFO order (short asynchronous tasks first), and when all microtasks are finished, Event execution is fetched from the Event queue, and so on until both queues are empty.

1.4. Specify the task order by linking

new Future(() => futureTask)  // Function of asynchronous task
        .then((d) => "execute value:$d")  // Subtasks after the task is executed
        .then((d) => d.length)  // where d is the result returned after the execution of the previous task
        .then((d) => printLength(d))
        .whenComplete(() => whenTaskCompelete);  // A callback function when all tasks are completed
}
Copy the code

As you can see, the above code makes clear the dependencies before and after. You can use then()() to indicate that to use a variable, you must wait until the variable is set. You can also use whenComplete(), a callback to asynchronous completion.

1.5. The Event queue

Add events to the Event queue using either new Future or new future.delayed (), which means Future operations are handled by the Event queue, as shown in the following code:

// Add a task to the event queue
new Future(() {
  // Specific tasks
});
Copy the code

You want to add the task to the Event queue after two seconds

// Add the task to the event queue after two seconds
new Future.delayed(const Duration(seconds:2).(a) {
  // Task specific code
});
Copy the code

The Misrotask queue is empty, and the previous task needs to be completed, so the task may be executed for more than 2 seconds.

1.6. MicroTask queue

scheduleMicrotask(() {
  // Specific logic
});
Copy the code

Add a task to the MicroTask queue.

1.7. Case 1

import 'dart:async';
main() {
  print('main #1 of 2');
  scheduleMicrotask(() => print('microtask #1 of 2'));

  new Future.delayed(new Duration(seconds:1),
                     () => print('future #1 (delayed)'));
  new Future(() => print('future #2 of 3'));
  new Future(() => print('future #3 of 3'));

  scheduleMicrotask(() => print('microtask #2 of 2'));

  print('main #2 of 2');
}
Copy the code

Output result:

main #1 of 2
main #2 of 2
microtask #1 of 2
microtask #2 of 2
future #2 of 3
future #3 of 3
future #1 (delayed)
Copy the code

Microtask queue (new Future, new future.delay); Event queue (new future.delay);

1.8. Case 2

import 'dart:async';
main() {
  print('main #1 of 2');
  scheduleMicrotask(() => print('microtask #1 of 3'));

  new Future.delayed(new Duration(seconds:1),
      () => print('future #1 (delayed)'));

  new Future(() => print('future #2 of 4'))
      .then((_) => print('future #2a'))
      .then((_) {
        print('future #2b');
        scheduleMicrotask(() => print('microtask #0 (from future #2b)'));
      })
      .then((_) => print('future #2c'));

  scheduleMicrotask(() => print('microtask #2 of 3'));

  new Future(() => print('future #3 of 4'))
      .then((_) => new Future(
                   () => print('future #3a (a new future)')))
      .then((_) => print('future #3b'));

  new Future(() => print('future #4 of 4'));
  scheduleMicrotask(() => print('microtask #3 of 3'));
  print('main #2 of 2');
}
Copy the code

Output result:

main #1 of 2
main #2 of 2
microtask #1 of 3
microtask #2 of 3
microtask #3 of 3
future #2 of 4
future #2a
future #2b
future #2c
microtask #0 (from future #2b)
future #3 of 4
future #4 of 4
future #3a (a new future)
future #3b
future #1 (delayed)
Copy the code

The two small examples above will deepen your understanding of event messages.

Asynchronous support in Dart

Because the Dart is single-threaded language, when faced with delayed operations (I/O operations), sequential execution of operations in a thread will be blocked, then the app, users will feel caton, so usually use asynchronous processing to solve this problem, when need to the operation, the delay will put delay operation in the queue, does not need to delay the first operation to perform, And finally, we’ll deal with the delay. The Dart library has many functions that return Future or Stream objects. These functions are called asynchronous functions. They return after setting up some operation that takes a certain amount of time, such as an I/O operation, rather than waiting for it to complete.

1.Future

What is a Future, as the name implies, represents an event that will happen in the Future (i.e., not immediately). A value can be taken from the Future. When a method returns a Future event, two things happen:

  1. This method queues something and returns an unfinishedFuture.
  2. After this method is done,FutureThe state becomes completed, at which point the return value of the event can be retrieved.

Future, said an asynchronous operations (or failure) and its final result value, said simply, it is used to deal with asynchronous operations, asynchronous processing is executed successful operation success, asynchronous processing failure will catch errors or to stop further operation, a Future will only corresponds to a result, either succeed or fail.

1.1. The Future. Then

main() {
  create();
}

// The module performs a delay task
void create(a){
   // Delay execution for three seconds
   Future.delayed(new Duration(seconds: 3), () {return "This is data";
   }).then((data){
     print(data);
   });
}
Copy the code

The following output is displayed:

This is data
Copy the code

As you can see above, create a delayed task with future. delayed when thendata receives the value of the string This is data returned three seconds later. Create a file:

main() {
  create();
}
void create(a){
   // Delay execution for three seconds
   var file = File("/ Users/luguian/Downloads/flutter fifth day/flutter. The RTF." ");
   // defines the return value as String
   Future<String> data = file.readAsString();
   // Return the file contents
   data.then((text){
     // Print the contents of the file
     print(text);
   });
   print("I love Android");
}
Copy the code
I love Android --> Print I love Android {\rtf1\ ANSI \ansicpg936\cocoartf1561\cocoasubrtf600 {\fonttbl\f0\ fSwiss \ fCharset0 Helvetica; \f1\fnil\fcharset134 PingFangSC-Regular; } {\colortbl; \red255\green255\blue255; } {\*\expandedcolortbl;; } \paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 \pard\tx566\tx1133\tx1700\tx2083\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partighten Factor0 \f0\fs24 \cf0 I love flutter --> Text contents \f1; }Copy the code

I love Android, I love Android, I love Android, I love Android, I love Android, I love Android, I love Android Note: The Future is not executed in parallel.

1.2. The Future catchError

Catch an error in catchError for an asynchronous task:

Future.delayed(new Duration(seconds: 3), () {throw AssertionError("This is a Error");
}).then((data){
  // This is the logic of success
  print("success");
}).catchError((e){
  // This is where failure comes in
  print(e);
});
Copy the code

The following output is displayed:

Assertion failed
Copy the code

The catchError () function is called instead of the catchError () function. The catchError () function is called instead of the catchError () function.

Future.delayed(new Duration(seconds: 3), () {throw AssertionError("This is a Error");
}).then((data){
  // This is the logic of success
  print("success");
},onError:(e){
  print(e);   
});
Copy the code

Output result:

Assertion failed
Copy the code

1.3. The Future whenComplete

There are many scenarios where asynchronous tasks need to do something whether they succeed or fail. For example, the loading progress box pops up before the network request, and the progress box is closed when the request ends. The following callback is performed with whenComplete:

Future.delayed(new Duration(seconds: 3), () {throw AssertionError("This is a Error");
}).then((data){
  // This is the logic of success
  print("success");
},onError:(e){
  // This is the logic of failure
  print(e);
}).whenComplete((){
  print("Failure or success leads to this.");
});

}
Copy the code

The following output is displayed:

Assertion Failed is where you're going to end up, whether you're going to end upCopy the code

1.4. The Future. Wait

In some cases, you need to wait for multiple asynchronous tasks to complete before performing some operations. For example, if you have an interface that needs to fetch data from two interfaces, the two data will be processed and displayed on the UI. In this case, future. wait is used to receive a Future array parameter. If all futures in the array are successfully executed, then callbacks will be triggered. If only one Future is successfully executed, then callbacks will be triggered.

Future.wait([
  // The result is returned after 3 seconds
  Future.delayed(new Duration(seconds: 3), () {return "Android";
  }),
  // Return the result after 4 seconds
  Future.delayed(new Duration(seconds: 4), () {return " And Future";
  })
]).then((data){
  // Success logic
  print(data[0] + data[1]);
}).catchError((e){
  // Catch errors
  print(e);
});
}
Copy the code

The following output is displayed:

Android And Future
Copy the code

You can see that the then function is called when two asynchronous tasks are complete.

2.Async/await

Async/await can also be used to implement asynchronous operations. Here is a direct example:

main() {
  create();
}
void create(a){
   String data =  getData();
   print(data);
   print("I love Future");
}
getData() async{
  return await "I love Android";
}
Copy the code

An error is reported when the above code is run:

type 'Future<dynamic>' is not a subtype of type 'String'
Copy the code

Type mismatch reported? Why is that? After some searching, it turns out that getData is an asynchronous operation function whose return value is the result of an await delayed execution. In Dart, we have an await operation, and the result is a Future object, which is not a String. So how do you get asynchronous results correctly? Dart states that async functions can only be called with await.

main() {
  create();
}

void create(a) async{
   String data =  await getData(a);
   print(data);
   print("I love Future");

}
getData() async{
  return await "I love Android";
}
Copy the code

GetData = async; getData = async;


String data;

main() {
  create();
}

void create(a){
   getData();
   print("I love Future");

}
getData() async{
  data =  await "I love Android";
  print(data);
}
Copy the code

The output above is:

I love Future
I love Android
Copy the code

It can be found that the first output is “I love Future” followed by “I love Android”. It can be found that when the function is decorated with async, the following operation will be performed first, and then the method decorated with async will be executed. Async is used to indicate that a function is asynchronous. The defined function returns a Future object, and “await” is followed by a Future, meaning that it will wait for the asynchronous task to complete before moving forward. Note the following points:

  1. The await keyword must be used inside an async function, i.e. adding await without async will result in an error.
  2. Calling async functions must use the await keyword. If async is added without await, the code will be executed sequentially.

Here’s another example:

main() {
  _startMethod();
  _method_C();

}

_startMethod() async{
  _method_A();
  await _method_B(a);
  print("Start over");
}
_method_A(){
  print("A starts executing this method ~");

}

_method_B() async {
  print("B starts executing this method ~");
  await  print("Follow this sentence ~");
  print("Continue with this sentence ha 11111~");
}

_method_C(){
  print("C");
}
Copy the code

The results are as follows:

A starts executing the method ~ B starts executing the method ~ C starts executing the method11111To start overCopy the code
  1. When async is used as the suffix, the method returns a Future.
  2. When executing the method code marked with await keyword, the execution of the rest of the method is suspended.
  3. When the Future referenced by the await keyword completes execution, the next line of code executes immediately.

_startMethod is declared as async, so print(“A starts executing this method ~”); , followed by _method_B(), which is declared with await keyword, so it suspends print(“start ends “); _method_B() prints (“B starts executing this method ~”); The next line, encountering the await keyword, suspends the execution of the rest of the code. The _method_C() method is executed immediately when the Future referenced by the await keyword is finished (that is, print(” follow this ~”)), then the execution continues with the hash 11111~, and finally print(“start end “);

3.Stream

Stram receives asynchronous event data. Unlike Future, it can receive the results of multiple asynchronous operations. Stram is often used in asynchronous task scenarios where data is read multiple times.

void create(a){

  Stream.fromFutures([
    // Return the result after 2 seconds
    Future.delayed(new Duration(seconds: 2), () {return "Android";
    }),

    // After 3 seconds an exception is thrown
    Future.delayed(new Duration(seconds: 3), () {return AssertionError("error");
    }),

    // Return the result after 4 seconds
    Future.delayed(new Duration(seconds: 4), () {return "Flutter";
    })

  ]).listen((result){
    // Prints the received result
     print(result);
  },onError: (e){
     // Error callback
     print(e.message);

  },onDone: (){

  });

}
Copy the code

As you can see above, a Stream can pass results or errors by triggering success or failure.

4. File operation

When you need to save files locally, you need to use the file read and write interface to achieve this. The PathProvider plug-in provides a platform transparent way to access common locations on the device file system. The class currently supports two file system locations:

  • Temporary directory: A temporary directory (cache) that the system can erase at any time. On iOS, this corresponds toNSTemporaryDirectory()The value returned. On Android, this isgetCacheDir()The value returned.
  • Document directory: The directory used by an application to store files that only you can access. The system will clear the directory only when the application is uninstalled. On iOS, this corresponds toNSDocumentDirectory. On Android, this isAppDataDirectory.

To read and write files in Flutter, you need to use the PATH_Provider and Dart I/O modules. The two modules have different responsibilities. Path_provider is responsible for finding iOS or Android directory files, while I/O is responsible for reading and writing files.

1. Obtain the local path

Use path_provider to find the local path, first adding the dependency to the pubspec.xml file:

dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS Style ICONS. Path_provider: ^0.4.1 --> Add a dependencyCopy the code

Or temporary directory, document directory, SD card directory as follows:

import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart'; .class _LoadFileState extends State<LoadFile>{
   @override
   void initState(a){
     super.initState();
   }
   
   @override
   Widget build(BuildContext context){
     return new Scaffold(
       appBar: new AppBar(
         title: new Text("LoadFile"),
       ),
       body: new Center(
         child: RaisedButton(
             child: Text("Get file path"),
             // Click call to get the file path method
             onPressed: loadPath,
         ),
       ),
     );
   }
}

loadPath() async{
    try{
      // Temporary directory
      var _tempDir = await getTemporaryDirectory(a);
      // Get the specific path
      String tempDirPath = _tempDir.path;
      // Document directory
      var _document = await getApplicationDocumentsDirectory(a);
      String documentPath = _document.path;
      / / sd card catalog
      var _sdCard = await getExternalStorageDirectory(a);
      String sdCardPath = _sdCard.path;

      // Prints the path
      print("Temporary directory :"+ tempDirPath);
      print("Document Contents:"+ documentPath);
      print("Sd card Directory:"+ sdCardPath);

    }catch(err){ print(err); }}Copy the code

The output (Android) is as follows:

I/flutter (19375): temporary directory :/data/user/0/com.example.loadflie/cache
I/flutter (19375File directory: /data/user/0/com.example.loadflie/app_flutter
I/flutter (19375): SD card directory: /storage/emulated/0
Copy the code

2. Read the local file

The simple_permissions library can be found in Dart Packages to simplify the permissions process. Follow the instructions above:

AndroidManifest
Info.plist
AndroidManifest
Info.plist
iOS
pubspec.yaml

simple_permissions: ^0.19.Copy the code

Remember to click the Packages Get command. Add read/write permission to AndroidManifest file:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Copy the code

Create a TXT file in the sd card internal storage of the mobile phone and try to obtain its contents:

import 'package:simple_permissions/simple_permissions.dart';// Remember to add this sentence.// Read the file method
readData() async {
  try {
    // Apply for permission to read files
    var permission =
    SimplePermissions.requestPermission(Permission.ReadExternalStorage);
    var sdCardPath = getExternalStorageDirectory();
    // When the path is obtained
    sdCardPath.then((filePath) {
      // Get permission to read files
      permission.then((permission_status) async {
        // Get the file contents
        var data = await File(filePath.path + "/flutter.txt").readAsString(a);
        print(data);
      });
    });
  } catch(e) { print(e); }}Copy the code

Button click method changed to readData:

        child: RaisedButton(
          child: Text("Get file path"),
          onPressed: readData
        ),
Copy the code

Click the button results run:

flutter.txt

I/flutter (24038): flutter is very good.
Copy the code

If read/write permission is not granted, an exception will be thrown:

I/flutter (25428): FileSystemException: Cannot open file, path = '/storage/emulated/0/flutter.txt' (OS Error: Permission denied, errno = 13)
Copy the code

3. Write files

// Write the contents to a file
writeData() async{
  try {
    // Apply for permission to read files
    var permission =
    SimplePermissions.requestPermission(Permission.WriteExternalStorage);
    var sdCardPath = getExternalStorageDirectory();
    // When the path is obtained
    sdCardPath.then((filePath) {
      // Get permission to read files
      permission.then((permission_status) async {
        // Write the contents into the file
        var data = await File(filePath.path + "/flutter.txt").writeAsString("Little by little, see the world.");
        print(data);
      });
    });
  } catch(e) { print(e); }}Copy the code

Open the flutter. TXT file of SD card to read the contents:

append
FileMode mode: FileMode.write
FileMode mode: FileMode.append

        // Write content to file now append
        var data = await File(filePath.path + "/flutter.txt").writeAsString("Flutter is very good",
             mode: FileMode.append);
Copy the code

Running results:

5. Sqflite database

SQLite is available on Android and iOS, but does Flutter exist? The answer is yes. The SQLite database in Flutter supports both Android and iOS. It is called SQflite and supports transaction and batch operations, insert/query/update/delete operations, etc. It is a lightweight relational database. The following is a simple implementation of a login interface for simple data operations:

// Display with stateless controls
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      / / theme color
      theme: ThemeData(
          // Set it to blue
          primarySwatch: Colors.red),
      // This is a Widget object that defines the interface to display when the current application is openedhome: DataBaseWidget(), ); }}/ / main frame
class DataBaseWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState(a) {
    return new_DataBaseState(); }}class _DataBaseState extends State<DataBaseWidget> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      //appBar
      appBar: AppBar(
        title: Text("Simple Sqlite operations"),
        // The title is centered
        centerTitle: true,
      ),
      body: new ListView(
        children: <Widget>[
          // User input user information widget
          Padding(
            padding: const EdgeInsets.only(left: 16, right: 16),
            child: InputMessageWidget(),
          ),
          // Add, delete, modify, and query the database table
          Padding(
            padding: const EdgeInsets.all(16), child: SqliteHandleWidget(), ), ], ), ); }}Copy the code

How about the Widget for user input information:

// User name and password
class InputMessageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // This is used to focus the password input field after the user finishes typing
    FocusNode secondTextFieldNode = FocusNode();
    return Column(
      children: <Widget>[
        TextField(
          // Text content changes trigger
          onChanged: (user) {
            // Get the user name
            username = user;
          },
          // Input decorator
          decoration: InputDecoration(
              / / label
              labelText: 'name'.//hint prompts the user for what to type
              hintText: 'Please enter English or number'),
          // The maximum size is one line
          maxLines: 1.// Text submission triggers
          onSubmitted: (result) {
            FocusScope.of(context).reparentIfNeeded(secondTextFieldNode);
          },
        ),
        TextField(
          onChanged: (pwd) {
            // Get the user password
            password = pwd;
          },
          False indicates no hiding, and true indicates hiding
          obscureText: true,
          maxLines: 1,
          decoration: InputDecoration(
            labelText: 'password',
            hintText: 'Please enter your password',),// Keyboard input typekeyboardType: TextInputType.text, onSubmitted: (data) {}, ), ], ); }}Copy the code

The button layout for database table operations is as follows:

// Database component operations
class SqliteHandleWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState(a) {
    return new_SqliteHandleWidgetState(); }}class _SqliteHandleWidgetState extends State<SqliteHandleWidget> {
  // Database name
  String myDataBase = "usermessage.db";

  // Database path
  String myDataBasePath = "";

  // Create a primary key, username, and password
  String sql_createUserTable = "CREATE TABLE user("
      "id INTEGER PRIMARY KEY,"
      "username TEXT,"
      "password TEXT)";

  // Find the number of database tables
  String sql_queryCount = 'SELECT COUNT(*) FROM user';

  // Find all information about the database table
  String sql_queryMessage = 'SELECT * FROM user';

  // This returns data from the database table
  var _data;

  @override
  Widget build(BuildContext context) {
    return Column(
      // Set the cross axis in the middle
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Container(
          height: 40.0,
          child: RaisedButton(
            textColor: Colors.black,
            child: Text("Create database table"),
            onPressed: null,
          ),
        ),
          Row(
            // Align the main axis to the center
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              new RaisedButton(
                  textColor: Colors.black,
                  child: new Text('add'),
                  onPressed: null),
              new RaisedButton(
                  textColor: Colors.black,
                  child: new Text('delete'),
                  onPressed: null),
              new RaisedButton(
                  textColor: Colors.black,
                  child: new Text('instead of'),
                  onPressed: null),
            ],
        ),
        Row(
          // Align the main axis to the center
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new RaisedButton(
                textColor: Colors.black,
                child: new Text('Check number'),
                onPressed: null),
            new RaisedButton(
                textColor: Colors.black,
                child: new Text('Look up information'),
                onPressed: null),
          ],
        ),
        Padding(
          padding: const EdgeInsets.all(16.0),
          child: new Text($_data = $_data),),,); }}Copy the code

SQL > alter table usermessage.db; SQL > alter table usermessage.db; SQL > alter table user;

1. Create databases and tables

Add dependencies first: Go to the Dart package management site to find the latest version of SQLite dependencies.

  sqflite: ^1.1. 0Copy the code

And in the file introduced:

import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
Copy the code

Note: All operations to the database are time-consuming and are handled asynchronously.

  // Create database
  Future<String> createDataBase(String db_name) async {
    // Create in the document directory
    var document = await getApplicationDocumentsDirectory(a);
    // Get path join is the method under the path package, that is, join the two paths
    String path = join(document.path, db_name);
    // The logic is to delete the database if it exists and then create it
    var _directory = new Directory(dirname(path));
    bool exists = await _directory.exists();
    if (exists) {
      // Create a database table before deleting it
      await deleteDatabase(path);
    } else {
      try {
        If [recursive] is false, only the last directory in the path is
        / / create. If recursive is true, then all non-existent paths
        // created. If the directory already exists, no action is performed.
        await new Directory(dirname(path)).create(recursive: true);
      } catch(e) { print(e); }}return path;
  }


  // Create database table method
  cratedb_table() async {
    // Get the database path
    myDataBasePath = await createDataBase(myDataBase);
    // Open the database
    Database my_db = await openDatabase(myDataBasePath);
    // Create a database table
    await my_db.execute(sql_createUserTable);
    // Close the database
    await my_db.close();
    setState(() {
      _data = "Usermessage. db created successfully, user created successfully ~";
    });
}
Copy the code

Add click methods to buttons:

          child: RaisedButton(
            textColor: Colors.black,
            child: Text("Create database table"),
            onPressed: cratedb_table,
          ),
Copy the code

Run it, after installing apK, use Device File Exploder to take a look at the internal storage files:

synchronize

app_flutter
usermessage.db

2. Add data

Insert (rawInsert); insert (rawInsert); insert (rawInsert);

// Add methods
addData() async {
    // Open the database first
    Database my_db = await openDatabase(myDataBasePath);
    // Insert data
    String add_sql = "INSERT INTO user(username,password) VALUES('$username','$password')";
    await my_db.transaction((tran) async{
       await tran.rawInsert(add_sql);
    });
    // Close the database
    await my_db.close();
    setState(() {
      _data = $username = $password = $password;
    });
}
Copy the code

3. Query specific data

In order to cooperate with the increase of data, the function of querying the database table is realized:

// Query the specific value
queryDetail() async{
    // Open the database
    Database  my_db = await openDatabase(myDataBasePath);
    // Display the data in the collection
    List<Map> dataList = await my_db.rawQuery(sql_queryMessage);
    await my_db.close();
    setState(() {
       _data = "$dataList";
    });
}
Copy the code

Querying a table is very simple, actually just using rawQuery, which binds the increment and query methods to a button click:

new RaisedButton(
    textColor: Colors.black, child: new Text('instead of'), onPressed: null),...new RaisedButton(
    textColor: Colors.black,
    child: new Text('Look up information'),
    onPressed: queryDetail),
Copy the code

To verify the results, the process is as follows:

  1. Enter your user name and password first
  2. Click on the add
  3. Click search information: The running result is as follows:

4. Delete data

Delete data as follows:

// Delete a piece of data
delete() async {
    Database my_db = await openDatabase(myDataBasePath);
    // Delete by id or by other information such as name
    String delete_ssql = "DELETE FROM user WHERE id = ?";
    // Returns the number of changes
    int delete_count = await my_db.rawDelete(delete_ssql,['1']);
    // Close the database
    await my_db.close();
    // Status update
    setState(() {
      if(delete_count == 1){
         _data = "Deleted successfully ~";
      } else {
        _data = "Delete failed, see error log ~"; }}); }Copy the code

Remember to bind the delete button to the method, so that the result is not pasted.

5. Modify data

Modify data I believe in the usual development is the most frequently used operation, directly on the implementation example:

// Modify the data method
update() async{
   / / database
   Database my_db = await openDatabase(myDataBasePath);
   String update_sql = "UPDATE user SET username = ? WHERE id = ?";
   await my_db.rawUpdate(update_sql,['paul'.'1']);
   await my_db.close();

   setState(() {
      _data = "Data modified successfully, please refer to ~";
   });

}
Copy the code

The above uses rawUpdate to update the content data of the database table, also can use db.update to update, you can modify the fixed field or the whole data according to the demand change. Above, I modified a piece of data according to the condition id, and changed the name of the data with ID 1 to Paul.

6. Query the number of entries

// There are several queries
query_num() async{
  / / database
  Database my_db = await openDatabase(myDataBasePath);
  // Use the sqflite package method firstInValue
  int data_count = Sqflite.firstIntValue(await my_db.rawQuery(sql_queryCount));
  await my_db.close();
  setState(() {
    _data = $data_count = $data_count;
  });
}
Copy the code

The basic operation of the local database to achieve a again, the following network request operation.

Six, network request operation

Flutter request network can be made in several ways. One can use the HttpClient in DART IO to make a request, the other can use the DIO library, and the other can use the HTTP library. Learn about get and POST first, put and delete later. Here’s how to practice:

1. Dart I/O initiated request

1.1. The get request

import 'dart:io';/ / IO package
import 'dart:convert';// Decode and encode JSON
void main(a) {
  _get();
}

_get() async{
  var responseBody;
  / / 1. Create an HttpClient
  var httpClient = new HttpClient();
  / / 2. Construct the Uri
  var requset = await httpClient.getUrl(Uri.parse("http://gank.io/api/data/%E7%A6%8F%E5%88%A9/10/1"));
  Close the request and wait for a response
  var response = await requset.close();
  //4. Decode to obtain data
  if(response.statusCode == 200) {// Get the requested data
      responseBody = await response.transform(utf8.decoder).join();
      // Do not parse the printed data first
      print(responseBody);
  }else{
    print("error"); }}Copy the code

The results are as follows:

1.2. A post request

_post() async{
  var responseBody;
  / / 1. Create an HttpClient
  var httpClient = new HttpClient();
  / / 2. Construct the Uri
  var requset = await httpClient.postUrl(Uri.parse("http://www.wanandroid.com/user/login?username=1&password=123456"));
  Close the request and wait for a response
  var response = await requset.close();
  //4. Decode to obtain data
  if(response.statusCode == 200) {// Get the requested data
  responseBody = await response.transform(utf8.decoder).join();
  // Do not parse the printed data first
    print(responseBody);
  }else{
    print("error"); }}Copy the code

The result is as follows:

2. Dio request

Dio is a powerful Dart Http request library that supports Restful apis, FormData, interceptors, error handling, converters, setting up Http proxies, request cancellation, Cookie management, file upload and download, timeouts, and more. Search for the latest dependency packages at pub.flutter-io.cn/packages. This website is so useful that you want to search for some of the three repositories:

pubspec.yaml

dio: ^2.014.Copy the code

Import dependencies:

import 'package:dio/dio.dart';
Copy the code

2.1. The get request

/ / dio a get request
dio_get() async{
  try{
      Response response;
      // Wait for a response
      response = await Dio(a).get("http://gank.io/api/data/%E7%A6%8F%E5%88%A9/10/1");
      if(response.statusCode == 200){
        print(response);
      }else{
        print("error"); }}catch(e){ print(e); }}Copy the code

2.2. A post request

dio_post() async{
  try{
    Response response;
    response = await Dio(a).post("http://www.wanandroid.com/user/login?username=1&password=123456");
    if(response.statusCode == 200){
      print(response);
    }else{
      print("error"); }}catch(e){ print(e); }}Copy the code

The effect is also OK.

3. HTTP library

Go to the link above to find the latest package, which is HTTP 0.12.0+1, add the dependency under pubspec.yaml, and import the package in the file:

import 'package:http/http.dart' as my_http;
Copy the code

There is a little difference in the way of the above import library, more as this keyword, what does this mean? Using AS is a way to resolve variable name conflicts, because importing different libraries may encounter variable name conflicts between different libraries.

3.1. The get request

// HTTP library get request mode
http_get() async{
  try{
    // As XXX is used to import HTTP, so the object requests are xxx.get
    var response = await my_http.get("http://gank.io/api/data/%E7%A6%8F%E5%88%A9/10/1");
    if(response.statusCode == 200) {// Prints the returned data
      print(response.body);
    }else{
      print("error"); }}catch(e){ print(e); }}Copy the code

3.2. A post request

// HTTP library post request mode
http_post() async{
  try{
    // As XXX is used to import HTTP, so the object requests are xxx.get
    var response = await my_http.post("http://www.wanandroid.com/user/login?username=1&password=123456");
    if(response.statusCode == 200) {// Prints the returned data
      print(response.body);
    }else{
      print("error"); }}catch(e){ print(e); }}Copy the code

Dart IO uses HttpClient to initiate requests. The HttpClient function is weak, and many common functions are not supported.

Seven, JSON

It’s hard to imagine a mobile application today that doesn’t need to interact with the background or store structured data. Currently developed, the data transfer method is mostly JSON. There are no GSON/Jackson/Moshi libraries in Flutter because these libraries require runtime reflection and are disabled in Flutter. Run-time reflection interferes with Dart’s _tree shaking_. Using _tree shaking_, you can optimize the size of your software by “stripping” unused code at release time. Since reflection uses all code by default, _tree shaking_ is difficult to work with. These tools have no way of knowing which widgets are not being used at runtime, so redundant code is difficult to strip off. Application dimensions cannot be easily optimized with reflection. However, some libraries provide apis that are easy to use for types, but they are based on code generation. Learn how to manipulate JSON data in Flutter. There are two general strategies for using JSON:

  1. Manual serialization and deserialization
  2. Automatic serialization and deserialization through code generation Different projects have different complexity and scenarios, and for small projects, using code generators can be a dead end. For having more than oneJSON modelFor complex applications, manual serialization can be tedious and error-prone.

1. Manually serialize JSON

The basic JSON serialization in Flutter is very simple. Flutter has a built-in DART: Convert library that contains a simple JSON decoder and encoder. Here is a simple implementation:

1.1. Serialize JSON internally

First remember the guide library:

import 'dart:convert';
Copy the code

Then parse according to the string:

// inline serialize JSON
decodeJson() {
    var data= '{"name": "Knight","email": "[email protected]"}';
    Map<String,dynamic> user = json.decode(data);
    // Print the name
    print("Hello,my name is ${user['name']}");
    // Output the mailbox
    print("Hello,This is my email ${user['email']}");
}
Copy the code

Result output:

I/flutter ( 5866): Hello,my name is Knight
I/flutter ( 5866): Hello,This is my email Knight@163.com
Copy the code

Here we get the data we want, which I find useful and easy to understand, but unfortunately json.decode () only returns a Map

, which means that when the type of the value is not known until run, this method loses most of the statically typed language features: Type safety, auto-completion, and compile-time exceptions. This makes the code very error-prone, like the one above where we accessed the name field and typed it namr. But the JSON is in a map structure, and the compiler does not know about the wrong field name (no errors are reported at compile time). To solve the problem, the serialization JSON function in the model class comes into play.
,dynamci>

1.2. Serialize JSON in model classes

To solve this problem, we introduce a simple model class. Create a User class with two methods inside the class:

  1. User.fromJsonConstructor, used to construct one from a mapUserInstance map structure
  2. toJsonMethods,UserInstantiating a map calls code that is type-safe, auto-complete, and compile-time exceptions, preventing a run-time crash when a spelling error or a field type is deemed to be of another type and the program will not compile.
1.2.1. The user. The dart

Dart: Create a new model folder for entities and create user.dart under its file:

class User {
  final String name;
  final String email;

  User(this.name, this.email);

  User.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        email = json['email'];

  Map<String, dynamic> toJson(a) = > {'name': name,
      'email': email,
    };
}
Copy the code

Call as follows:

import 'model/User.dart';// Remember to add.// Deserialize using model classes
decodeModelJson(){
  var data= '{"name": "Knight","email": "[email protected]"}';
  Map userMap = json.decode(data);
  var user = new User.fromJson(userMap);
  // Print out the name
  print("Hello,my name is ${user.name}");
  // Print out the mailbox
  print("Hello,my name is ${user.email}");
}
Copy the code

Deserializing the data is easy by moving the serialization logic inside the model itself. Serialize a user, just pass the user object to the json.encode method:

// serialize a user
encodeModelJson(){
  var user = new User("Knight"."Knight163.com");
  String user_json = json.encode(user);
  print(user_json);
}
Copy the code

Result output:

I/flutter ( 6684) : {"name":"Knight"."email":"Knight163.com"}
Copy the code

2. Serialize JSON using the code production library

The following uses the JSON_serialIZABLE package, which is an automated source code generator that can generate JSON serialization templates for developers.

2.1. Add dependencies

To include jSON_serializable into your project, you need one general and two development dependencies, which are dependencies that are not included in the application source code:

dependencies: # Your other regular dependencies here json_annotation: ^2.0.0 dev_dependencies:--> Dev_dependencies: ^1.1.3 --> latest version 1.2.8 JSON_serializable: ^2.0.2Copy the code

2.2. Code generation

There are two ways to run a code generator:

  1. Once generated and run in the project root directoryflutter packages pub run build_runner buildCan be in need for ourmodelgeneratejsonSerialization code. This triggers a one-time build, which picks the relevant ones through the source files and generates the necessary serialization code for them. This is very convenient, but it would be nice if we didn’t have to run the build command manually every time we made a change in the Model class.
  2. Continuous generation, using _watcher_ makes the process of source code generation easier, monitors cultural changes in the project, and automatically builds the necessary files as needed, throughflutter packages pub run build_runner watchIt is safe to run boot_watcher_ at the project root, just start the observer once, and then let it run in the background.

Change the above user.dart to the following:

import 'package:json_annotation/json_annotation.dart';
part 'User.g.dart'; --> < p style = "max-width: 100%; clear: both// This annotation tells the generator that this class is needed to generate the Model class
@JsonSerializable(a)class User{
  User(this.name, this.email); String name; String email; Factory user.fromjson (Map<String, dynamic> json){--return _$UserFromJson(json);
  }
  
  Map<String, dynamic> toJson(a) {--> At first it went viralreturn _$UserToJson(this); }}Copy the code

The following is a one-time build command that opens the command line in the project root directory:

User.g.dart

Note: not generatedUser.g.dartRun the command several times.
json_serializable
JSON

2.3. Deserialization

  var data= '{"name": "Knight","email": "[email protected]"}';
  Map userMap = json.decode(data);
  var user = new User.fromJson(userMap);
  // Print out the name
  print("Hello,my name is ${user.name}");
  // Print out the mailbox
  print("Hello,my name is ${user.email}");
Copy the code

2.4. The serialization

  var user = new User("Knight"."Knight163.com");
  String user_json = json.encode(user);
  print(user_json);
Copy the code

The result is the same as above, but with the added benefit of generating a file…

Eight, examples,

Here is a simple example, the effect is as follows:

{
	"error": false."results": [{
		"_id": "5c6a4ae99d212226776d3256"."createdAt": "The 2019-02-18 T06:04:25. 571 z"."desc": "2019-02-18"."publishedAt": "The 2019-02-18 T06:05:41. 975 z"."source": "web"."type": "\u798f\u5229"."url": "https://ws1.sinaimg.cn/large/0065oQSqly1g0ajj4h6ndj30sg11xdmj.jpg"."used": true."who": "lijinshanmx"
	}, {
		"_id": "5c6385b39d21225dd7a417ce"."createdAt": "The 2019-02-13 T02: mothers. 946 z"."desc": "2019-02-13"."publishedAt": 16 "z" is the 2019-02-13 T02: a.."source": "web"."type": "\u798f\u5229"."url": "https://ws1.sinaimg.cn/large/0065oQSqly1g04lsmmadlj31221vowz7.jpg"."used": true."who": "lijinshanmx"}}]Copy the code

We need to add two entity classes as follows:

import 'ResultModel.dart';

class ViewResult{
  bool error;
  List<ResultModel> list;
  ViewResult(joinData){
    // Get the error value returned
    error = joinData['error'];
    list = [];
    print(joinData['results']);
    // Get the contents of "Results"
    if(joinData['results'] != null) {for(var dataItem in joinData['results']){
         list.add(newResultModel(dataItem)); }}}Copy the code

The ResultModel class is as follows:

class ResultModel{
   String _id;
   String createdAt;
   String desc;
   String publishedAt;
   String source;
   String type;
   String url;
   bool used;
   String who;

   ResultModel(jsonData){
     _id = jsonData['_id'];
     createdAt = jsonData['createdAt'];
     desc = jsonData['desc'];
     publishedAt = jsonData['publishedAt'];
     source = jsonData['source'];
     type = jsonData['type'];
     url = jsonData['url'];
     used = jsonData['used'];
     who = jsonData['who']; }}Copy the code

ListView Item layout:

// We need to pass the list and the corresponding subscript
Widget photoWidget(List<ResultModel> resultLists,int index){
  return Card(
    child: Container(
      height: 300,
      child: Row(
        children: <Widget>[
          Expanded(
            child: ClipRRect(
              borderRadius: BorderRadius.circular(4.0),
              child: Image.network(resultLists[index].url,
                fit:BoxFit.fitWidth,
                / / scale: 2.5.(() [() [() [() [() [() [() }Copy the code

All codes are as follows:

import 'package:flutter/material.dart';
import 'dart:convert';// Decode and encode JSON
import 'package:http/http.dart' as my_http;
import 'model/ViewResult.dart';
import 'model/ResultModel.dart';



/ / app entrance
void main(a) {
  runApp(MyApp());
}

// Display with stateless controls
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      / / theme color
      theme: ThemeData(
        // Set it to blue
          primarySwatch: Colors.red),
      // This is a Widget object that defines the interface to display when the current application is openedhome: BigPhotoWidget(), ); }}/ / main frame
class BigPhotoWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState(a) {
    return new_BigPhotoState(); }}class _BigPhotoState extends State<BigPhotoWidget> {
  ViewResult viewresult;
  // A specific data set
  List<ResultModel> resultLists = [];
  @override
  void initState(a){
    super.initState();
    getData();
  }

  getData() async{
    try{
      // As XXX is used to import HTTP, so the object requests are xxx.get
      / / way
/ / await my_http. Get (" welfare / 10/1 "http://gank.io/api/data/
// .then((response){
// if(response.statusCode == 200){
// var ViewData = json.decode(response.body);
// viewresult = ViewResult(ViewData);
// if(! viewresult.error){
// //
// for(int i = 0; i < viewresult.list.length; i++){
// resultLists.add(viewresult.list[i]);
/ /}
// // remember to call refresh
// setState(() {
//
/ /});
/ /}
// }else{
// print("error");
/ /}
/ /});
      // Mode 2 request
      var response = await my_http.get(Welfare / 10/1 "http://gank.io/api/data/");
      // Determine the status
      if(response.statusCode == 200) {/ / parsing
          var ViewData = json.decode(response.body);
          viewresult = ViewResult(ViewData);
          if(! viewresult.error){// Continue parsing
            for(int i = 0; i < viewresult.list.length; i++){ resultLists.add(viewresult.list[i]); }// Remember to call refreshsetState(() { }); }}else{
          print("error"); }}catch(e){ print(e); }}@override
  Widget build(BuildContext context) {
    return new Scaffold(
      //appBar
      appBar: AppBar(
        title: Text("Girl map"),
        // The title is centered
        centerTitle: true,
      ),
      body: ListView.builder(
          itemCount: resultLists.length,
          itemBuilder: (BuildContext context,int index){
            returnColumn( children: <Widget>[ photoWidget(resultLists,index), ], ); },),); }}// We need to pass the list and the corresponding subscript
Widget photoWidget(List<ResultModel> resultLists,int index){
  return Card(
    child: Container(
      height: 300,
      child: Row(
        children: <Widget>[
          Expanded(
            child: ClipRRect(
              borderRadius: BorderRadius.circular(4.0),
              child: Image.network(resultLists[index].url,
                fit:BoxFit.fitWidth,
                / / scale: 2.5.(() [() [() [() [() [() [() }Copy the code

There are two ways to get the data above.

Nine,

  1. Knowing the simple summary execution model of Dart, when a Flutter application is started, an ISOLATE is created and two queues, microTasks, Events, and a message loop are initialized. The code execution and order depends on the MicroTask and Event queues.
  2. Neither Future nor Async executes in parallel.
  3. Simple local database operations.
  4. Simple handling (serialization and deserialization) of Json data.
  5. Network simple request.

As meng xin, there must be a lot of technology not in place, if there is any mistake, welcome to point out correction, thank you ~