Flutter异步约定/关于状态和小部件的最佳实践

时间:2018-11-21 21:28:19

标签: flutter conventions

使用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;
  }
}

0 个答案:

没有答案