如何将 Provider 用于异步数据? (扑)
How can I use Provider for async data? (Flutter)
背景
我正在构建超级简单的 TODO 列表应用程序来练习 Flutter。如果您输入一个字符串并单击"add",则文本会显示在上部的列表中。
问题
何时以及如何调用 model.getTasks()
(= 异步更新数据)和 model.refresh()
(= notifyListeners
)?
我发现列表没有立即更新,因为我没有为 model.getTasks()
使用 await
。我尝试了这些,但我想知道是否有更好的方法。
- 使用
await model.getTasks()
并使 build
成为异步函数。 --> 编译错误
- 在
model.refresh()
之前添加 await model.getTasks()
。 --> model.tasks
在打开应用程序时呈现 ListView
之前未更新。 model.getTasks()
在创建新任务时被调用两次。
截图
代码详情
model.tasks
是List<Task>
,就是存放从DB(sqflite
)中抓取的任务。
model.getTasks()
是一个异步函数,用于从数据库中获取数据并覆盖 model.tasks
。
model.refresh()
仅用于调用 notifyListeners()
.
TodoListPage
是模型 notifyListeners
的监听器。
class TodoListPage extends StatelessWidget {
final TextEditingController _textFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
print('TodoListPage.build');
final model = Provider.of<TodoListModel>(context);
model.getTasks();
Future createTask() async {
TaskData taskData = await DatabaseHandler()
.insertTask(new TaskData(name: _textFieldController.text));
model.refresh();
}
return Scaffold(
appBar: AppBar(
title: Text(
'TODO List',
),
),
body: Column(
children: <Widget>[
Container(
height: 150,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text(model.tasks[index].name);
},
itemCount: model.tasks.length,
),
),
Text(
'Add',
),
Form(
child: Column(
children: <Widget>[
TextFormField(
controller: _textFieldController,
),
RaisedButton(
onPressed: () {
print('onPressed');
print(_textFieldController.text);
createTask();
},
child: Text('New'),
),
],
),
)
],
),
);
}
}
您可以在 StatefulWidget
中使用 FutureBuilder
:
class TodoListPage extends StatefulWidget {
@override
_TodoListPageState createState() => _TodoListPageState();
}
class _TodoListPageState extends State<TodoListPage> {
TodoListModel model;
Future<List<Tasks>> _getTasks;
@override
void didChangeDependencies() {
model = Provider.of<TodoListModel>(context);
_getTasks = model.getTasks();
}
@override
Widget build() {
FutureBuilder<List<Tasks>>(
future: _getTasks,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Text("Loading tasks...");
}
final tasks = snapshot.data;
// ...
}
);
}
}
在您的模型中:
Future<List<Tasks>> getTasks() async {
if (tasks == null) {
tasks = await loadFromDatabase();
}
return tasks;
}
当您设置 ChangeNotifierProvider
时,它会调用 TodoListModel()
只有一次 refreshTasks()
这类似于 Widget build()
中的 model.getTastks()
然后我将一个 Consumer 放置在 Widget 树中尽可能深的位置
当我按下 RaiseButton 或 notifyListeners();
时,这个 Consumer 每次都会得到 newbuild
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class TodoList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => TodoListModel()),
],
child: TodoListPage()
);
}
}
class TodoListPage extends StatelessWidget {
final TextEditingController _textFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
print('TodoListPage.build');
// listen: false, if the provider sends e notify, this part is not refreshing
final model = Provider.of<TodoListModel>(context, listen: false);
// i place this future in the TodoListModel Class
/*Future createTask() async {
//TaskData taskData = await DatabaseHandler().insertTask(new TaskData(name: _textFieldController.text));
model.refreshTasks();
}*/
return Scaffold(
appBar: AppBar(
title: Text(
'TODO List',
),
),
body: Column(
children: <Widget>[
Container(
height: 150,
child: new Consumer<TodoListModel>(
builder: (context, prov2, child) {
// this part is updating every notifyListeners
// it's better when you place the consumer,as deep as possible in the widget tree
print("Consumer build");
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text(model.tasks[index].name);
},
itemCount: model.tasks.length,
);
},
),
),
Text(
'Add',
),
Form(
child: Column(
children: <Widget>[
TextFormField(
controller: _textFieldController,
),
RaisedButton(
onPressed: () {
print('onPressed');
//print(_textFieldController.text);
//createTask();
model.createTask(new TaskData(name: _textFieldController.text));
},
child: Text('New'),
),
],
),
)
],
),
);
}
}
class TaskData {
final String name;
TaskData({this.name});
}
class TodoListModel extends ChangeNotifier {
// this Function only calls when the Provider is set at ChangeNotifierProvider like initTodoListModel
TodoListModel () {
print("TodoListModel Class");
refreshTasks();
}
// define TaskData, with getter
List<TaskData> _tasks = [new TaskData(name: "test"), new TaskData(name: "test2")];
List<TaskData> get tasks => _tasks;
// getTasks async
Future refreshTasks() async {
// fetch something in the Database
// _tasks = await await DatabaseHandler().getTasks();
notifyListeners();
}
Future createTask(TaskData newTask) async {
// add Task with DatabseHandler, or in this example a simple list.add
_tasks.add(newTask);
//refreshing with notifyListeners
refreshTasks();
}
}
背景
我正在构建超级简单的 TODO 列表应用程序来练习 Flutter。如果您输入一个字符串并单击"add",则文本会显示在上部的列表中。
问题
何时以及如何调用 model.getTasks()
(= 异步更新数据)和 model.refresh()
(= notifyListeners
)?
我发现列表没有立即更新,因为我没有为 model.getTasks()
使用 await
。我尝试了这些,但我想知道是否有更好的方法。
- 使用
await model.getTasks()
并使build
成为异步函数。 --> 编译错误 - 在
model.refresh()
之前添加await model.getTasks()
。 -->model.tasks
在打开应用程序时呈现ListView
之前未更新。model.getTasks()
在创建新任务时被调用两次。
截图
代码详情
model.tasks
是List<Task>
,就是存放从DB(sqflite
)中抓取的任务。model.getTasks()
是一个异步函数,用于从数据库中获取数据并覆盖model.tasks
。model.refresh()
仅用于调用notifyListeners()
.TodoListPage
是模型notifyListeners
的监听器。
class TodoListPage extends StatelessWidget {
final TextEditingController _textFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
print('TodoListPage.build');
final model = Provider.of<TodoListModel>(context);
model.getTasks();
Future createTask() async {
TaskData taskData = await DatabaseHandler()
.insertTask(new TaskData(name: _textFieldController.text));
model.refresh();
}
return Scaffold(
appBar: AppBar(
title: Text(
'TODO List',
),
),
body: Column(
children: <Widget>[
Container(
height: 150,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text(model.tasks[index].name);
},
itemCount: model.tasks.length,
),
),
Text(
'Add',
),
Form(
child: Column(
children: <Widget>[
TextFormField(
controller: _textFieldController,
),
RaisedButton(
onPressed: () {
print('onPressed');
print(_textFieldController.text);
createTask();
},
child: Text('New'),
),
],
),
)
],
),
);
}
}
您可以在 StatefulWidget
中使用 FutureBuilder
:
class TodoListPage extends StatefulWidget {
@override
_TodoListPageState createState() => _TodoListPageState();
}
class _TodoListPageState extends State<TodoListPage> {
TodoListModel model;
Future<List<Tasks>> _getTasks;
@override
void didChangeDependencies() {
model = Provider.of<TodoListModel>(context);
_getTasks = model.getTasks();
}
@override
Widget build() {
FutureBuilder<List<Tasks>>(
future: _getTasks,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Text("Loading tasks...");
}
final tasks = snapshot.data;
// ...
}
);
}
}
在您的模型中:
Future<List<Tasks>> getTasks() async {
if (tasks == null) {
tasks = await loadFromDatabase();
}
return tasks;
}
当您设置 ChangeNotifierProvider
时,它会调用 TodoListModel()
只有一次 refreshTasks()
这类似于 Widget build()
然后我将一个 Consumer 放置在 Widget 树中尽可能深的位置
当我按下 RaiseButton 或 notifyListeners();
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class TodoList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => TodoListModel()),
],
child: TodoListPage()
);
}
}
class TodoListPage extends StatelessWidget {
final TextEditingController _textFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
print('TodoListPage.build');
// listen: false, if the provider sends e notify, this part is not refreshing
final model = Provider.of<TodoListModel>(context, listen: false);
// i place this future in the TodoListModel Class
/*Future createTask() async {
//TaskData taskData = await DatabaseHandler().insertTask(new TaskData(name: _textFieldController.text));
model.refreshTasks();
}*/
return Scaffold(
appBar: AppBar(
title: Text(
'TODO List',
),
),
body: Column(
children: <Widget>[
Container(
height: 150,
child: new Consumer<TodoListModel>(
builder: (context, prov2, child) {
// this part is updating every notifyListeners
// it's better when you place the consumer,as deep as possible in the widget tree
print("Consumer build");
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text(model.tasks[index].name);
},
itemCount: model.tasks.length,
);
},
),
),
Text(
'Add',
),
Form(
child: Column(
children: <Widget>[
TextFormField(
controller: _textFieldController,
),
RaisedButton(
onPressed: () {
print('onPressed');
//print(_textFieldController.text);
//createTask();
model.createTask(new TaskData(name: _textFieldController.text));
},
child: Text('New'),
),
],
),
)
],
),
);
}
}
class TaskData {
final String name;
TaskData({this.name});
}
class TodoListModel extends ChangeNotifier {
// this Function only calls when the Provider is set at ChangeNotifierProvider like initTodoListModel
TodoListModel () {
print("TodoListModel Class");
refreshTasks();
}
// define TaskData, with getter
List<TaskData> _tasks = [new TaskData(name: "test"), new TaskData(name: "test2")];
List<TaskData> get tasks => _tasks;
// getTasks async
Future refreshTasks() async {
// fetch something in the Database
// _tasks = await await DatabaseHandler().getTasks();
notifyListeners();
}
Future createTask(TaskData newTask) async {
// add Task with DatabseHandler, or in this example a simple list.add
_tasks.add(newTask);
//refreshing with notifyListeners
refreshTasks();
}
}