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

预算超支提醒函数的常驻执行/监听位置咨询

自动触发预算超支检测的合适调用位置

你已经实现了检测预算超支的fetchData函数,要实现无需手动点击自动执行/监听数据变化,以下是几个最优的调用方案:

一、监听Firestore数据变化(推荐方案)

因为你的预算和支出数据存储在Firestore,直接监听集合的实时变化是最高效的方式——数据更新时自动触发检测,无需轮询,能第一时间捕捉到超支情况。

修改原函数为实时监听版本:

把原函数里一次性获取数据的get()改成snapshots()实时监听,数据变动时自动执行检测逻辑:

StreamSubscription? _budgetSubscription;
StreamSubscription? _expenseSubscription;

void startBudgetMonitoring() {
  // 监听预算集合变化
  _budgetSubscription = budgetCollectionRef.snapshots().listen((budgetSnapshot) {
    // 同步监听支出集合变化
    _expenseSubscription = expenseCollectionRef.snapshots().listen((expenseSnapshot) {
      _processBudgetAndExpenses(budgetSnapshot, expenseSnapshot);
    });
  });
}

void _processBudgetAndExpenses(QuerySnapshot budgetSnapshot, QuerySnapshot expenseSnapshot) {
  try {
    List<Map<String, dynamic>> budgetDataList = [];
    List<Map<String, dynamic>> expenseDataList = [];

    // 处理预算数据
    if (budgetSnapshot.docs.isNotEmpty) {
      for (var document in budgetSnapshot.docs) {
        String documentId = document.id;
        Map<String, dynamic> data = document.data() as Map<String, dynamic>;
        data['documentId'] = documentId;
        budgetDataList.add(data);
      }
    }

    // 处理支出数据
    if (expenseSnapshot.docs.isNotEmpty) {
      for (var document in expenseSnapshot.docs) {
        Map<String, dynamic> data = document.data() as Map<String, dynamic>;
        expenseDataList.add(data);
      }
    }

    // 遍历所有预算检测超支
    for (var budget in budgetDataList) {
      String category = budget['CategoryName'];
      Timestamp fromDate = budget['FromDate'];
      Timestamp tillDate = budget['TillDate'];
      double percentage = budget['Percentage'].toDouble();
      double budgetAmount = budget['Amount'].toDouble();
      List<Map<String, dynamic>> expenseFilteredList;

      if (category == 'All Categories') {
        expenseFilteredList = expenseDataList.where((expense) {
          Timestamp expenseDate = expense['UserSelectedDate'];
          return expenseDate.compareTo(fromDate) >= 0 &&
              expenseDate.compareTo(tillDate) <= 0;
        }).toList();
      } else {
        expenseFilteredList = expenseDataList.where((expense) {
          Timestamp expenseDate = expense['UserSelectedDate'];
          return expenseDate.compareTo(fromDate) >= 0 &&
              expenseDate.compareTo(tillDate) <= 0 &&
              expense['Category'] == category;
        }).toList();
      }

      double expenseTotalAmount = expenseFilteredList.fold(
        0.0,
        (previous, expense) => previous + (expense['Amount'] as double),
      );

      // 计算支出占预算的百分比
      double calPercentage = calculatePercentage(expenseTotalAmount, budgetAmount);

      if (calPercentage * 100 >= percentage) {
        // 不要用return,否则第一个超支就停止检测其他预算
        NotificationService().showNotification(
          title: '预算超支提醒!',
          body: '你已超出${category}的预算',
        );
      }
    }
  } catch (e) {
    print('处理数据错误: $e');
  }
}

// 页面销毁时取消监听,避免内存泄漏
@override
void dispose() {
  _budgetSubscription?.cancel();
  _expenseSubscription?.cancel();
  super.dispose();
}

调用时机:

在页面的initState中启动监听,页面加载后立即开始监控:

@override
void initState() {
  super.initState();
  startBudgetMonitoring();
}

二、定时轮询(备选方案)

如果不需要实时检测,可设置定时任务每隔一段时间执行一次fetchData,适合对实时性要求不高的场景:

Timer? _pollingTimer;

@override
void initState() {
  super.initState();
  // 每隔5分钟执行一次检测,可根据需求调整时长
  _pollingTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
    fetchData();
  });
  // 页面初始化时先执行一次检测
  fetchData();
}

@override
void dispose() {
  _pollingTimer?.cancel();
  super.dispose();
}

三、数据操作后立即触发

当用户完成添加支出、修改预算、删除支出/预算等操作后,直接调用fetchData,确保即时检测超支:
比如在添加支出的函数中:

Future<void> addExpense(Map<String, dynamic> expenseData) async {
  try {
    await expenseCollectionRef.add(expenseData);
    // 添加支出后立即检测预算
    fetchData();
  } catch (e) {
    print('添加支出失败: $e');
  }
}

原函数的优化建议

  1. 移除return:原函数触发通知后用return会终止循环,导致后续预算无法被检测,应删除return,让所有预算都能被检查。
  2. 避免全局变量:calPercentage应定义为循环内的局部变量,避免全局变量的状态污染。
  3. 类型安全处理:计算expenseTotalAmount时,给expense['Amount']加上as double类型转换,避免运行时类型错误。

内容的提问来源于stack exchange,提问作者user8

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 14:35:04