Flutter中Riverpod Stream无法触发新值发射问题排查
问题
我用Flutter的Riverpod和Drift包做了一个监听当月已记录费用的简单Widget,采用Provider > Repository > DAO的架构。但在本地数据库(已通过SQLite表确认)添加或删除当月交易记录时,Widget不会更新显示最新的累计费用,Stream似乎没发射新值,调试很久都没找到问题。
预期行为:
- 修改transactions表中的记录(添加/删除)
- expensesToDateProvider发射新值
- Widget重建并显示新值
// Widget to display latest expenseToDate class DebugExpensesWidget extends ConsumerWidget { const DebugExpensesWidget({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final expensesAsyncValue = ref.watch(expensesToDateProvider(budgetDate: DateTime(DateTime.now().year, DateTime.now().month))); return expensesAsyncValue.when( data: (data) { return Text('Expenses to date: $data'); }, loading: () => CircularProgressIndicator(), error: (error, _) { return Text('Error: $error'); }, ); } }
// Provider that watches spentToDate for the month @riverpod Stream<double> expensesToDate(ExpensesToDateRef ref, {required DateTime budgetDate}) { return ref.watch(budgetsRepositoryProvider).getExpensesToDate(budgetDate); }
// Repository to call the DAO to return total expenses to date based on a given budgetDate Stream<double> getExpensesToDate(DateTime budgetDate) { return database.appDatabaseDao.calculateExpensesToDateDao(budgetDate.year, budgetDate.month); }
// DAO to calculate Total Expenses to date based on a given month and year Stream<double> calculateExpensesToDateDao(int year, int month) { final yearStr = year.toString(); final monthStr = month.toString().padLeft(2, '0'); const sql = ''' SELECT SUM(amount) as total FROM transactions WHERE is_income = 0 AND strftime('%m', datetime(transaction_date, 'unixepoch')) = ? AND strftime('%Y', datetime(transaction_date, 'unixepoch')) = ? '''; return customSelect(sql, variables: [ Variable.withString(monthStr), Variable.withString(yearStr), ]).watch().map((rows) { final row = rows.first; final total = row.read<double?>('total') ?? 0.0; return total; }); }
解决方案
问题核心是Drift无法自动识别自定义SQL对应的表依赖,导致customSelect的watch方法不会在transactions表变化时发射新值。需要手动指定该查询监听的表。
修复代码
修改DAO中的calculateExpensesToDateDao方法,给customSelect添加readsFrom参数,明确指定监听transactions表:
// DAO to calculate Total Expenses to date based on a given month and year Stream<double> calculateExpensesToDateDao(int year, int month) { final yearStr = year.toString(); final monthStr = month.toString().padLeft(2, '0'); const sql = ''' SELECT SUM(amount) as total FROM transactions WHERE is_income = 0 AND strftime('%m', datetime(transaction_date, 'unixepoch')) = ? AND strftime('%Y', datetime(transaction_date, 'unixepoch')) = ? '''; // 添加readsFrom参数,指定监听transactions表 return customSelect(sql, variables: [ Variable.withString(monthStr), Variable.withString(yearStr), ], readsFrom: {transactions} // 替换为你实际的Transactions表实例 ).watch().map((rows) { final row = rows.first; final total = row.read<double?>('total') ?? 0.0; return total; }); }
额外说明
- Drift的自动监听仅对生成式查询(如
select(table).watch())生效,自定义SQL必须手动声明依赖表。 - 确保你的
transactions表是通过Drift注解生成的(比如class Transactions extends Table {...}),此时会存在对应的transactions常量实例,直接传入readsFrom即可。 - 可优化Widget中的日期计算:当前在
build方法内每次创建DateTime实例,可能触发不必要的Provider重建,建议提前计算:
@override Widget build(BuildContext context, WidgetRef ref) { // 提前计算当月日期,避免每次build生成新实例 final currentMonth = DateTime(DateTime.now().year, DateTime.now().month); final expensesAsyncValue = ref.watch(expensesToDateProvider(budgetDate: currentMonth)); // ... 其余代码不变 }
内容的提问来源于stack exchange,提问作者harumon
相关产品推荐
相关产品推荐

