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

Flutter项目从Firebase实时数据库下载数据时陷入循环求助

Flutter Firebase 数据读取循环问题排查与修复

问题描述

将SwiftUI学习项目迁移到Flutter时,Firebase Realtime Database数据读取环节出现循环问题:逻辑是先匿名登录Firebase,下载"Tests"数据存到SharedPreferences;若当日已下载则读本地缓存。但调用ViewModel的authAndDownload()时程序陷入循环,反复执行该函数,数据下载成功后仍无法正常跳转页面。

问题根源

  • FutureBuilder重复触发异步任务:IntroView的build方法中直接传入introViewModel.authAndDownload(),每次Widget重建(比如ViewModel调用notifyListeners()、父组件刷新)都会重新执行这个异步函数,导致无限循环。
  • 状态通知不完整:authAndDownload()和downloadTests()完成后未及时调用notifyListeners(),视图无法感知状态变化。
  • 异步任务无缓存:没有对authAndDownload()的执行结果做缓存,导致每次重建都重复发起请求。

修复方案

1. 缓存异步任务避免重复执行

在ViewModel中缓存异步任务的Future实例,确保仅执行一次。

2. 完善状态通知逻辑

在数据加载完成、状态变更时调用notifyListeners(),让视图及时更新。

3. 将IntroView改为StatefulWidget

在initState中触发异步任务,避免build方法重复调用。

4. 规范数据序列化逻辑

确保Test类实现toJson和fromJson方法,保证SharedPreferences存储与解析正常。

修改后的代码示例

1. 更新IntroViewModel

class IntroViewModel extends ChangeNotifier {
  List<Test> tests = [];
  String? alertError;
  bool isLoading = false;

  final String storedTestsKey = "storedTests";
  final String lastDownloadedKey = "LastDownloadedDate";

  final FirebaseRepository firebaseRepository = FirebaseRepository();
  Future<void>? _authAndDownloadFuture;

  Future<void> authAndDownload() async {
    if (_authAndDownloadFuture != null) return;
    isLoading = true;
    notifyListeners();

    try {
      _authAndDownloadFuture = _performAuthAndDownload();
      await _authAndDownloadFuture;
    } catch (error) {
      alertError = error.toString();
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }

  Future<void> _performAuthAndDownload() async {
    await firebaseRepository.signInAnonymously();

    if (!await isTestsDownloadedForToday()) {
      await downloadTests();
      await saveLastDownloadedDate();
    } else {
      await getStoredTests();
    }
  }

  Future<void> downloadTests() async {
    try {
      Test todayTest = await firebaseRepository.readTodayTest();
      List<Test> last30Tests = await firebaseRepository.readLast30Tests();

      tests = [todayTest, ...last30Tests];
      await storeDownloadedTests(testData: tests);
      notifyListeners();
    } catch (error) {
      alertError = error.toString();
      notifyListeners();
    }
  }

  Future<bool> isTestsDownloadedForToday() async {
    final currentDate = firebaseRepository.getCurrentDateUTC();
    final String? lastDownloadedDate = await getLastDownloadedDate();
    final currentDateString = firebaseRepository.formatDateString(date: currentDate);
    return currentDateString == lastDownloadedDate;
  }

  Future<String?> getLastDownloadedDate() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString(lastDownloadedKey);
  }

  Future<void> saveLastDownloadedDate() async {
    final currentDate = firebaseRepository.getCurrentDateUTC();
    final currentDateString = firebaseRepository.formatDateString(date: currentDate);
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(lastDownloadedKey, currentDateString);
  }

  Future<void> storeDownloadedTests({required List<Test> testData}) async {
    try {
      SharedPreferences prefs = await SharedPreferences.getInstance();
      String encodedData = json.encode(testData.map((e) => e.toJson()).toList());
      await prefs.setString(storedTestsKey, encodedData);
    } catch (error) {}
  }

  Future<void> getStoredTests() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final String? data = prefs.getString(storedTestsKey);

      if (data != null) {
        final List<dynamic> decodedTests = json.decode(data);
        tests = decodedTests.map((json) => Test.fromJson(json)).toList();
        notifyListeners();
      }
    } catch (error) {
      print("Unable to process decoding ($error)");
    }
  }
}

2. 修改IntroView为StatefulWidget

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

  @override
  State<IntroView> createState() => _IntroViewState();
}

class _IntroViewState extends State<IntroView> {
  @override
  void initState() {
    super.initState();
    Provider.of<IntroViewModel>(context, listen: false).authAndDownload();
  }

  @override
  Widget build(BuildContext context) {
    final SettingsViewModel settingsViewModel = Provider.of<SettingsViewModel>(context);
    final IntroViewModel introViewModel = Provider.of<IntroViewModel>(context);

    if (introViewModel.isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    if (introViewModel.alertError != null) {
      return Center(child: Text(introViewModel.alertError!));
    }

    return settingsViewModel.userOnboarded ? const MainTabView() : const OnboardingView();
  }
}

3. 完善Test类序列化

class Test {
  final String a;
  final String b;
  final String c;
  final String testDate;

  Test({required this.a, required this.b, required this.c, required this.testDate});

  Map<String, dynamic> toJson() {
    return {
      'a': a,
      'b': b,
      'c': c,
      'testDate': testDate,
    };
  }

  factory Test.fromJson(Map<String, dynamic> json) {
    return Test(
      a: json['a'],
      b: json['b'],
      c: json['c'],
      testDate: json['testDate'],
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 16:35:00