使用async / await / ...编码可以通过许多不同的方式完成。是否有官方的模式/惯例/最佳做法集?
在下面的代码示例中,我使用了(不一致地)与状态相关的调用,数据标志来驱动小部件的行为...(我没有使用Flutter: Best Practices of Calling Async Codes from UI中所述的FutureBuilder或StreamBuilder来专注于其他方式)。
所有这些不同的方法在技术上都可以工作,但是代码可能变得凌乱且对维护/团队工作不利。有关如何操作的任何准则/公约?
代码注释中的3个问题...全部显示,因为我也看到有些人在努力使其工作(以防它有所帮助)
//Don't forget the dependencies in the pubspec.yaml
//dependencies:
// flutter:
// sdk: flutter
// sqflite: any
// path_provider: any
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:io';
//for the DB
final String tblUsers = "users";
final String colUserid = "userid";
final String colDisplayName = "displayName";
//nothing interesting until the next comment
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
//Focus for the discussion
class _MyHomePageState extends State<MyHomePage> {
DataBroker data;
bool dataLoaded=false;
//These are just to play with async data fetching/loading...no functional need for that
User user;
User userTwo;
_MyHomePageState();
//I want my database to be ready before I do anything...so I put all the init data at the beginning
@override
void initState() {
super.initState();
//Calling a function with all the sequential data fetching I need to init
//this doesn't prevent the UI from being created so some objects will be null until populated
initData().then((result) {
setState(() {
//I now know that the db has been initialised
dataLoaded=true;
});
});
}
Future<void> initData() async {
data = DataBroker();
//The await ensures that I don't fetch User data before the db has been initialised
await data.initialise();
//the await ensures I have user set before I assign it to userTwo
await data.getUser().then((u) {user=u;});
//Question 1: is it better to do it here
//Or put the 'userTwo = user' in the then()?
userTwo = user;
}
//No functional sense, i basically just want to get 'user' to be one step ahead of 'userTwo'
void _updateUser() {
user.displayName = user.displayName + '+';
data.getUser().then((res) {userTwo=res;});
data.updateUser(user).then((res){
//Empty function, just triggering setState to rebuild the UI
//Question 2: I am setting variables outside setState...should I be doing it another way?
setState(() {});
});
}
//Yet another pattern, here I directly create a function to display what I want while waiting for flags to be set
//Question 3: if I do that, I will end up with a lot of mini functions to show Widgets in 2 different cases
Widget _dataStatusWidget() {
if(dataLoaded) {
return Text('Data has been loaded');
} else {
return Text('Data is loading please wait...');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_dataStatusWidget(),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'User: ' + user.toString(),
),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'UserTwo: ' + userTwo.toString(),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _updateUser,
tooltip: 'update user',
child: Icon(Icons.update),
),
);
}
}
//simple data access class...nothing amazing
class DataBroker {
static final DataBroker _databroker = new DataBroker._internal();
static final _dbName = "dbtest.db";
static Database _db;
DataBroker._internal();
factory DataBroker() {
return _databroker;
}
Database get db => _db;
Future<User> getUser() async {
List<Map> maps = await _db.query(
tblUsers,
columns: [colUserid, colDisplayName],
);
if (maps.length > 0) {
return new User.fromMap(maps.first);
}
return null;
}
Future<int> updateUser(User user) async {
return await _db.update(tblUsers, user.toMap(),
where: "$colUserid = ?", whereArgs: [user.userid]);
}
Future<void> initialise() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = dir.path + _dbName;
await deleteDatabase(path);
_db = await openDatabase(path, version: 1, onCreate:_createDB);
}
}
//Creating the db with one user by default for simplicity
Future<Database> _createDB(Database db, int newVersion) async {
await db.execute('''
create table $tblUsers (
$colUserid integer primary key autoincrement,
$colDisplayName text
)''');
User user = User.withIDs(1,'John Doe');
await db.insert(tblUsers, user.toMap());
//just to be slow
await Future.delayed(Duration(milliseconds: 2000), () {});
return db;
}
//data class for completeness...nothing amazing
class User {
int _userid;
String _displayName;
User(this._displayName);
User.withIDs(this._userid, this._displayName);
int get userid => _userid;
String get displayName => _displayName;
set displayName(String displayName) {
_displayName = displayName;
}
String toString() {
return
"userid: " + _userid.toString() + "\n" +
"displayName: " + _displayName + "\n";
}
User.fromMap(dynamic o) {
this._userid=o["userid"];
this._displayName=o["displayName"];
}
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
if (_userid != null) {
map["userid"] = _userid;
}
map["displayName"] = _displayName;
return map;
}
}