Flutter普通类中替代setState实现动态更新的方案咨询
问题描述
我是Flutter新手,正在使用异步编程(Asynchronous programming)。Flutter通过async、await支持异步编程,使用这些关键字时,**setState()**在动态修改值方面起着重要作用。但setState方法仅能在StatefulWidget类中使用,无法在普通类(非StatefulWidget)中调用。
我的核心目标是将所有异步逻辑放在单独文件中,在需要的Widget视图中调用,以此减少代码冗余,让代码结构更清晰。
以下是我的示例代码:
ScreenTime.dart
import 'package:app_usage/app_usage.dart'; import 'dart:typed_data'; import 'package:installed_apps/installed_apps.dart'; import 'package:installed_apps/app_info.dart'; class ScreenTimeAlgorithm { List<AppUsageInfo> _usages = []; List<AppInfo> _apps = []; List<AppModel> _finalApps = []; late int totalScreenTime = 0; late int hr = 0, min = 0; void getStats() async { try { DateTime endTime = DateTime.now(); int daySubtracter = endTime.hour; int minSubtracter = endTime.minute; int secSubtracter = endTime.second; DateTime startTime = endTime.subtract( Duration( hours: daySubtracter, minutes: minSubtracter, seconds: secSubtracter, ), ); print(startTime); //00.00.00 print(endTime); //14.47.23 List<AppUsageInfo> usageList = await AppUsage.getAppUsage(startTime, endTime); List<AppInfo> apps = await InstalledApps.getInstalledApps(true, true); _finalApps = []; for (var app in apps) { for (var Usage in usageList) { if (app.packageName! == Usage.packageName) { var item = AppModel( title: app.name!, package: app.packageName!, icon: app.icon!, usagetime: Usage.usage.inMinutes, ); _finalApps.add(item); } } } _finalApps.sort( (a, b) => b.usagetime.compareTo(a.usagetime), ); for (var TTime in usageList) { totalScreenTime += TTime.usage.inMinutes; } setState(() { _usages = usageList; _apps = apps; }); } on AppUsageException catch (exception) { print(exception); } } } class AppModel { final String title; final String package; final Uint8List icon; final int usagetime; late int hr = 0, min = 0; late String _formatTime = ""; AppModel({ required this.title, required this.package, required this.icon, required this.usagetime, }); String timeFormatting() { if (usagetime != 0) { hr = (usagetime ~/ 60); min = usagetime % 60; if (hr == 0 || min == 0) { if (hr == 0 && min != 1) { _formatTime = '$min minutes'; } else if (hr == 0 && min == 1) { _formatTime = '$min minute'; } else if (hr != 1 && min == 0) { _formatTime = '$hr hours'; } else if (hr == 1 && min == 0) { _formatTime = '$hr hour'; } else { _formatTime = 'Less than 1 minute'; } } else { if (hr == 1) { _formatTime = '$hr hour $min minutes'; } else if (min == 1) { _formatTime = '$hr hours $min minute'; } else { _formatTime = '$hr hours $min minutes'; } } } else { _formatTime = "Less than one minute"; } return _formatTime; } String get getTimeFormat { return timeFormatting(); } }
由于该类不是StatefulWidget,调用setState()会报错;但不使用setState的话,异步操作始终返回空列表。请问该如何解决这个问题?这类操作的通用处理方式是什么?
解决方案
通用处理思路
把数据逻辑和UI更新彻底解耦:普通类只负责数据的获取、计算和存储,UI更新的逻辑完全交给StatefulWidget或状态管理工具处理。常见实现方式有两种:
- 回调函数:数据处理完成后通过回调通知Widget更新UI,适合小型项目或简单场景
- 状态管理工具:比如Provider、Riverpod、Bloc,适合中大型项目或复杂状态场景
针对你的代码的修改方案
方案1:使用回调函数(简单直接)
修改ScreenTimeAlgorithm类,让getStats方法接收一个回调参数,在异步逻辑完成后把处理好的数据通过回调返回给Widget,由Widget调用setState更新UI:
class ScreenTimeAlgorithm { List<AppUsageInfo> _usages = []; List<AppInfo> _apps = []; List<AppModel> _finalApps = []; late int totalScreenTime = 0; late int hr = 0, min = 0; // 新增回调参数,传递处理后的应用列表和总时长 void getStats(void Function(List<AppModel>, int) onDataLoaded) async { try { DateTime endTime = DateTime.now(); int daySubtracter = endTime.hour; int minSubtracter = endTime.minute; int secSubtracter = endTime.second; DateTime startTime = endTime.subtract( Duration( hours: daySubtracter, minutes: minSubtracter, seconds: secSubtracter, ), ); print(startTime); //00.00.00 print(endTime); //14.47.23 List<AppUsageInfo> usageList = await AppUsage.getAppUsage(startTime, endTime); List<AppInfo> apps = await InstalledApps.getInstalledApps(true, true); _finalApps = []; for (var app in apps) { for (var Usage in usageList) { if (app.packageName! == Usage.packageName) { var item = AppModel( title: app.name!, package: app.packageName!, icon: app.icon!, usagetime: Usage.usage.inMinutes, ); _finalApps.add(item); } } } _finalApps.sort( (a, b) => b.usagetime.compareTo(a.usagetime), ); totalScreenTime = 0; // 重置累计时间,避免重复累加 for (var TTime in usageList) { totalScreenTime += TTime.usage.inMinutes; } // 数据处理完成,调用回调返回结果 onDataLoaded(_finalApps, totalScreenTime); // 更新类内部变量 _usages = usageList; _apps = apps; } on AppUsageException catch (exception) { print(exception); } } }
在你的StatefulWidget中调用:
class ScreenTimeScreen extends StatefulWidget { @override State<ScreenTimeScreen> createState() => _ScreenTimeScreenState(); } class _ScreenTimeScreenState extends State<ScreenTimeScreen> { final ScreenTimeAlgorithm _algorithm = ScreenTimeAlgorithm(); List<AppModel> _appList = []; int _totalTime = 0; @override void initState() { super.initState(); _loadScreenTime(); } void _loadScreenTime() { _algorithm.getStats((appList, totalTime) { setState(() { _appList = appList; _totalTime = totalTime; }); }); } @override Widget build(BuildContext context) { return Scaffold( body: _appList.isEmpty ? const Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: _appList.length, itemBuilder: (context, index) { final app = _appList[index]; return ListTile( leading: Image.memory(app.icon), title: Text(app.title), subtitle: Text(app.getTimeFormat), ); }, ), ); } }
方案2:使用状态管理(适合复杂场景)
如果项目后续有更多状态需求,用Provider来管理状态:
- 修改
ScreenTimeAlgorithm为ChangeNotifier:
import 'package:flutter/foundation.dart'; class ScreenTimeAlgorithm extends ChangeNotifier { List<AppUsageInfo> _usages = []; List<AppInfo> _apps = []; List<AppModel> _finalApps = []; int _totalScreenTime = 0; // 对外暴露只读数据 List<AppModel> get finalApps => _finalApps; int get totalScreenTime => _totalScreenTime; Future<void> getStats() async { try { // 保留原有异步逻辑... _finalApps = []; for (var app in apps) { for (var Usage in usageList) { if (app.packageName! == Usage.packageName) { var item = AppModel( title: app.name!, package: app.packageName!, icon: app.icon!, usagetime: Usage.usage.inMinutes, ); _finalApps.add(item); } } } _finalApps.sort( (a, b) => b.usagetime.compareTo(a.usagetime), ); _totalScreenTime = 0; for (var TTime in usageList) { _totalScreenTime += TTime.usage.inMinutes; } _usages = usageList; _apps = apps; // 通知所有监听的Widget更新UI notifyListeners(); } on AppUsageException catch (exception) { print(exception); } } }
- 在Widget树中提供Provider:
void main() { runApp( ChangeNotifierProvider( create: (context) => ScreenTimeAlgorithm(), child: const MyApp(), ), ); }
- 在Widget中监听状态变化:
class ScreenTimeScreen extends StatelessWidget { const ScreenTimeScreen({super.key}); @override Widget build(BuildContext context) { final algorithm = Provider.of<ScreenTimeAlgorithm>(context); return Scaffold( body: algorithm.finalApps.isEmpty ? const Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: algorithm.finalApps.length, itemBuilder: (context, index) { final app = algorithm.finalApps[index]; return ListTile( leading: Image.memory(app.icon), title: Text(app.title), subtitle: Text(app.getTimeFormat), ); }, ), floatingActionButton: FloatingActionButton( onPressed: () => algorithm.getStats(), child: const Icon(Icons.refresh), ), ); } }
关键说明
- 普通数据类只负责数据逻辑,不处理UI相关操作,符合单一职责原则
- 异步操作完成后,必须通过回调或状态管理触发UI更新,否则Widget无法感知数据变化
setState是UI层方法,绝不能在数据类中直接调用
内容的提问来源于stack exchange,提问作者Christo_Nimal S
相关产品推荐
相关产品推荐

