You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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来管理状态:

  1. 修改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);
    }
  }
}
  1. 在Widget树中提供Provider:
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => ScreenTimeAlgorithm(),
      child: const MyApp(),
    ),
  );
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 09:48:13