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.
- The Event queue contains all foreign events: I/O, Mouse Events, Drawing Events, timers, messages between isolate, etc
- MicroTask queue in
Dart
This 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:
- This method queues something and returns an unfinished
Future
. - After this method is done,
Future
The 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:
- The await keyword must be used inside an async function, i.e. adding await without async will result in an error.
- 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
- When async is used as the suffix, the method returns a Future.
- When executing the method code marked with await keyword, the execution of the rest of the method is suspended.
- 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 to
NSTemporaryDirectory()
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 to
NSDocumentDirectory
. On Android, this isAppData
Directory.
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:
- Enter your user name and password first
- Click on the add
- 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:
- Manual serialization and deserialization
- 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 one
JSON model
For 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.
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:
User.fromJson
Constructor, used to construct one from a mapUser
Instance map structuretoJson
Methods,User
Instantiating 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:
- Once generated and run in the project root directory
flutter packages pub run build_runner build
Can be in need for ourmodel
generatejson
Serialization 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. - 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, through
flutter packages pub run build_runner watch
It 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.dart
Run 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,
- 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.
- Neither Future nor Async executes in parallel.
- Simple local database operations.
- Simple handling (serialization and deserialization) of Json data.
- 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 ~