我是 flutter 的新手。我实现了这个示例应用程序以了解 SQLite 和提供程序状态管理。这是一个简单的任务管理系统。我使用 MOOR 来处理 SQLite 数据库。我遇到的问题是任务列表在添加任务后没有更新。如果我重新启动应用程序,我可以看到该任务已成功保存在数据库中。
泊位查询实现
Future<List<Task>> getAllTasks() => select(tasks).get();
Stream<List<Task>> watchAllTasks() => select(tasks).watch();
Future<int> insertTask(TasksCompanion task) => into(tasks).insert(task);
任务库 在这里,我将粘贴整个类以了解有关我的实现的想法。
class TaskRepository{
Stream<List<DisplayTaskData>> getAllTasks(){
var watchAllTasks = AppDatabase().watchAllTasks();
final formattedTasks = List<DisplayTaskData>();
return watchAllTasks.map((tasks) {
tasks.forEach((element) {
var date = element.dueDate;
String dueDateInString;
if(date != null){
dueDateInString = ""
"${date.year.toString()}-"
"${date.month.toString().padLeft(2,'0')}-"
"${date.day.toString().padLeft(2,'0')}";
}
var displayTaskData = DisplayTaskData(task : element.task, dueDate: dueDateInString);
formattedTasks.add(displayTaskData);
});
return formattedTasks;
});
}
Future<Resource<int>> insertTask(TasksCompanion task) async{
try {
int insertTaskId = await AppDatabase().insertTask(task);
return Resource(DataProcessingStatus.SUCCESS, insertTaskId);
}on InvalidDataException catch (e) {
var displayMessage = DisplayMessage(
title : "Data Insertion Error",
description : "Something went wrong please try again"
);
return Resource.displayConstructor(DataProcessingStatus.PROCESSING_ERROR,displayMessage);
}
}
}
我有一个 View 模型层将值推送到 UI 端基础 View 模型
class BaseViewModel extends ChangeNotifier {
ViewState _state = ViewState.IDLE;
ViewState get state => _state;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
}
任务 View 模型 class TaskViewModel extends BaseViewModel{
final TaskRepository _repository = TaskRepository();
DateTime _deuDate;
Stream<List<DisplayTaskData>> getAllTasks(){
return _repository.getAllTasks().map((formattedTasks) => formattedTasks);
}
Future<void> insertTask() async {
setState(ViewState.PROCESSING);
var tasksCompanion = TasksCompanion(task: Value(_taskValidation.value),dueDate: Value(_deuDate));
insertTaskStatus = await _repository.insertTask(tasksCompanion);
setState(ViewState.IDLE);
}
}
主要功能 void main() {
Stetho.initialize();
final taskViewModel = TaskViewModel();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => taskViewModel),
StreamProvider(create:(context) => taskViewModel.getAllTasks())
],
child: MyApp(),
),
);
}
任务 View class TaskView extends StatelessWidget {
DateTime selectedDate = DateTime.now();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Tasks'),
),
body : ListView.builder(
itemCount: context.watch<List<DisplayTaskData>>()?.length,
itemBuilder: (context, index) => _taskListView(context, index),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showMaterialModalBottomSheet(
context: context,
builder: (context, scrollController) =>
Container(
child: bottomSheet(context),
),
);
},
child: Icon(
Icons.add,
color: Colors.white,
),
backgroundColor: Colors.blueAccent,
));
}
Widget _taskListView(BuildContext context, int index) {
var task = context.watch<List<DisplayTaskData>>()[index];
return Row(
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: Column(children: <Widget>[
Text(task.task),
SizedBox(height: 8),
Text("hardcoded value")
],),
)
],
);
}
}
我期望发生的是当我插入一个任务时 getAllTasks()
流应该流式传输新添加的任务,列表应该会自动更新。粘贴存储库和 ViewModel 的整个代码的原因是为了表明我在插入和检索任务中使用了相同的类。我将它们结合在一起,因为这两个操作都与任务相关,并且计划在同一类中也有更新和删除功能。我强调这一点是因为我怀疑 main 函数中的实现是否正确。但是,我仍然为
ChangeNotifierProvider
使用相同的对象和 StreamProvider
更新 首先,我为两次粘贴 TaskRepository 类代码表示歉意。在那个地方,我的 ViewModel 类应该来了,我已经在上面更正了。
我对原始代码的更改很少。一、
AppDatabase
class 不是单例类,因此即使应用程序没有崩溃,Logcat 上也会出现错误。正如我之前提到的,当应用程序重新启动时,所有数据库值都会加载到 ListView 中。但是通过放置调试点,我注意到第一次
_taskListView
方法触发器结果列表为空,并且由于我访问了该索引而出现错误。我不知道原因,但我添加了一个空检查并解决了这个问题。完成这些更改后,我尝试插入一个任务,看看会发生什么。所以在插入重新编码时,Logcat 给出了以下日志。
2020-10-28 21:02:29.845 4258-4294/com.example.sqlite_test I/flutter: Moor: Sent INSERT INTO tasks (task, due_date) VALUES (?, ?) with args [Task 8, 1604082600]
2020-10-28 21:02:29.905 4258-4294/com.example.sqlite_test I/flutter: Moor: Sent SELECT * FROM tasks; with args []
所以根据这个日志watchAllTasks()
插入重新编码后触发。但是用户界面没有得到更新。我把调试点放在一起检查 getAllTasks()
在 View 模型中被触发。这似乎是一个 UI 错误。并且渲染的 List 在任务名称之前也有一个边距。 ListView 不适合手机屏幕。查看下面的屏幕截图。![enter image description here](https://i.sstatic.net/slQsy.png)
我做了一些关于在 Flutter 中处理流的研究。而这个 example上来了。据此,我的语法在
getAllTasks()
中是错误的在存储库中。所以我根据如下所示更改了功能。Stream<List<DisplayTaskData>> getAllTasks() async*{
var watchAllTasks = AppDatabase().watchAllTasks();
final formattedTasks = List<DisplayTaskData>();
watchAllTasks.map((tasks) {
tasks.forEach((element) {
var date = element.dueDate;
String dueDateInString;
if(date != null){
dueDateInString = ""
"${date.year.toString()}-"
"${date.month.toString().padLeft(2,'0')}-"
"${date.day.toString().padLeft(2,'0')}";
}
var displayTaskData = DisplayTaskData(task : element.task, dueDate: dueDateInString);
formattedTasks.add(displayTaskData);
});
});
yield formattedTasks;
}
通过此更改,该列表甚至不会在应用程序重启时加载。在这一点上,我不清楚错误在哪里。我试图缩小范围。我希望此更新的信息将帮助某人查明此代码中的问题并帮助我。感谢所有的答案。
最佳答案
直接将ListView包裹在一个StreamBuilder中监听感兴趣的流来监听
StreamBuilder<List<DisplayTaskData>>(
stream: getAllTasks(), // provide the correct reference to the stream
builder: (context, snapshot) {
if (snapshot.data != null)
// here your old ListView
return ListView.builder(
// itemCount: snapshot.data.length, // <- this is better
itemCount: contextsnapshot.watch<List<DisplayTaskData>>()?data.length,
// here you probably can pass directly the element snapshot.data[index]
itemBuilder: (context, index) => _taskListView(context, index),
);
else
return Text("No Tasks"),
}
)
关于Flutter StreamProvider 不将数据推送到 UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64448051/