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

如何在App中实现按指定时间间隔(每周一次)展示卡片

原有写法的问题

你现在的实现从根上就跑不通,核心问题有3个:

  • Future.delayed是进程内存级的计时,只要App被杀、后台被系统回收,计时直接清零。正常用户不可能连续7天不杀App、不让系统回收后台进程,这个延时任务99%的场景下根本触发不了。就算用户不杀App,只要进了主页切去其他页面,7天后照样会弹,根本不管用户当前在不在主页。
  • 生命周期写法违规:super.initState()必须写在initState方法的最开头,你放在延时任务后面,很容易出现组件未初始化完成就调用context、触发框架层报错。
  • 没有状态持久化和去重:就算计时成功触发了,只要用户下次重进App,又会重新注册一个7天的延时任务,完全做不到“每7天展示一次”的规则,甚至页面rebuild的时候还可能重复注册多个延时任务,导致一次弹好几个卡片。
正确实现思路

长周期的定时触发逻辑,绝对不能靠内存里跑延时,正确逻辑是:

  • 用本地轻量KV存储,记录上一次成功展示卡片的时间戳
  • 每次进入主页、或者App从后台切回前台的时候,读取本地存的上次展示时间,和当前时间做差
  • 如果时间差满7天,就弹卡片,弹完(或者用户看完卡片关闭后)把当前时间更新到本地存储,作为下一轮7天周期的起点
可直接复用的代码示例

首先接入项目里常用的轻量本地KV存储(比如shared_preferences,你用其他存储方案逻辑也完全一致),然后按下面的写法实现:

import 'package:flutter/material.dart';
// 替换为你项目实际使用的本地存储引入
import 'package:shared_preferences/shared_preferences.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  // 本地存储用的key,存上次展示卡片的时间戳(毫秒级)
  static const _lastShowCardKey = 'last_show_weekly_card_ts';
  // 7天间隔对应的毫秒数
  static const _weekInMs = 7 * 24 * 60 * 60 * 1000;

  @override
  void initState() {
    // 必须第一行调用父类初始化方法
    super.initState();
    // 等页面首帧渲染完成后再检查触发条件,避免刚进页面就弹、或者组件未挂载报错
    WidgetsBinding.instance.addPostFrameCallback((_) => _checkAndShowCard());
  }

  Future<void> _checkAndShowCard() async {
    // 页面已经销毁就终止流程,避免崩溃
    if (!mounted) return;

    final storage = await SharedPreferences.getInstance();
    final lastShowTs = storage.getInt(_lastShowCardKey) ?? 0;
    final nowTs = DateTime.now().millisecondsSinceEpoch;

    // 距离上次展示没满7天,直接不弹
    if (nowTs - lastShowTs < _weekInMs) return;

    // 满足条件,弹底部卡片
    showModalBottomSheet(
      context: context,
      builder: (context) {
        // 替换为你自己的卡片组件
        return SizedBox(
          height: 220,
          child: Center(child: Text('每周专属卡片内容')),
        );
      },
    );

    // 弹完立刻更新本地时间,标记本周已经展示过
    // 如果要求用户必须看完/关闭卡片才算展示,把这行逻辑移到卡片关闭的回调里就行
    await storage.setInt(_lastShowCardKey, nowTs);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('主页')),
      body: const Center(child: Text('主页内容区域')),
    );
  }
}
额外优化点
  • 如果需要支持App挂后台满7天、切回来也能触发卡片,可以给State混入WidgetsBindingObserver,监听App生命周期变化,在App回到前台的时候再调用一次_checkAndShowCard即可,不用等用户重启App。
  • 不要用任何长延时任务做跨天/跨周的触发逻辑,只要进程被杀,内存里的所有任务都会消失,这类需求一律用*“持久化时间点+启动/前台事件触发判断”*的方案实现。
  • 弹卡片前一定要加mounted判断,避免异步任务执行完页面已经销毁,调用context触发崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 04:55:18