Reputation: 1811
I am building super simple TODO list app for practicing Flutter. If you input a string and click "add", then the text is shown in the list on the upper part.
When and how should I call model.getTasks()
(= update data asynchronously) and model.refresh()
(= notifyListeners
)?
I find the list is not updated immediately because I don't use await
for model.getTasks()
. I tried these but I would like to know if there are better ways.
await model.getTasks()
and make build
an async function. --> Compile errorawait model.getTasks()
right before model.refresh()
. --> model.tasks
is not updated before rendering ListView
when opening the app. model.getTasks()
are called twice when creating new tasks.model.tasks
is List<Task>
, which is to store tasks fetched from DB (sqflite
).model.getTasks()
is an async function to fetch data from DB and overwrite model.tasks
.model.refresh()
is only for calling notifyListeners()
.TodoListPage
is the listener of the model's 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'),
),
],
),
)
],
),
);
}
}
Upvotes: 7
Views: 19107
Reputation: 1266
when you set the ChangeNotifierProvider
, it calls in the TodoListModel()
only one time refreshTasks()
that's similar to your model.getTastks() in the Widget build()
then i place a Consumer as deep in the Widget tree as possible
and this Consumer gets everytime newbuild when i Press the RaiseButton or on 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();
}
}
Upvotes: 1
Reputation: 767
You can use FutureBuilder
inside a StatefulWidget
:
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;
// ...
}
);
}
}
And in your model :
Future<List<Tasks>> getTasks() async {
if (tasks == null) {
tasks = await loadFromDatabase();
}
return tasks;
}
Upvotes: 10