cached_request.dart 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:flutter/material.dart'; // 引入 Flutter 核心包
  4. import 'package:http/http.dart' as http;
  5. import 'package:puzzleweave/models/api_helper.dart';
  6. import 'package:puzzleweave/models/data.dart';
  7. import 'package:puzzleweave/utils/utils.dart';
  8. import 'package:logging/logging.dart';
  9. final Logger _log = Logger('cached_request.dart');
  10. typedef TransformFunction = Future<dynamic> Function(dynamic json);
  11. // 混入 WidgetsBindingObserver 以监听应用生命周期
  12. class CachedRequest with WidgetsBindingObserver {
  13. // !!! 新增属性 1: 标记最近一次请求是否通过网络成功完成
  14. bool _hasRecentSuccessfulFetch = false;
  15. bool get hasRecentSuccessfulFetch => _hasRecentSuccessfulFetch;
  16. static final Map<String, CachedRequest> _cache = {};
  17. final String url;
  18. TransformFunction? transformFunction;
  19. // 仅使用 .broadcast(),但 onListen 只会触发一次
  20. final StreamController _streamController = StreamController.broadcast();
  21. dynamic _transformed;
  22. // --- 【新增】Getter:允许外部同步访问最新的缓存数据 ---
  23. dynamic get cachedData => _transformed;
  24. CachedRequest._internal(this.url, this.transformFunction) {
  25. _log.info('New cached request: $url');
  26. // 注册生命周期监听器
  27. WidgetsBinding.instance.addObserver(this);
  28. _init();
  29. }
  30. factory CachedRequest.fromUrl(String url, {TransformFunction? transformFunction}) {
  31. // 确保单例模式下,只注册一次监听器
  32. if (!_cache.containsKey(url)) {
  33. _cache[url] = CachedRequest._internal(url, transformFunction);
  34. }
  35. return _cache[url]!;
  36. }
  37. // --- 关键修改:生命周期监听 ---
  38. @override
  39. void didChangeAppLifecycleState(AppLifecycleState state) {
  40. if (state == AppLifecycleState.resumed) {
  41. _log.info('App Resumed from background. Forcing refresh for $url');
  42. // 应用程序从后台恢复到前台时,强制刷新数据
  43. refresh();
  44. }
  45. }
  46. // 由于 CachedRequest 是一个单例,它不会被销毁,除非应用完全关闭。
  47. // 但是,为了严谨性,如果添加了 Dispose 逻辑,应记得移除 Observer。
  48. // 注意:在 Flutter Provider 或 InheritedWidget 依赖的单例中,通常不需要手动调用 dispose。
  49. // 如果需要清理:
  50. /*
  51. void dispose() {
  52. WidgetsBinding.instance.removeObserver(this);
  53. _streamController.close();
  54. // ... clean up
  55. }
  56. */
  57. // ---------------------------------
  58. _init() {
  59. // FIX: 首次初始化时,立即尝试加载缓存(如果有)
  60. _cacheLoad();
  61. _remoteLoad();
  62. // _streamController.onListen = () {
  63. // if (_transformed != null) {
  64. // // 如果有缓存数据,立即发送给新的监听者
  65. // _streamController.add(_transformed);
  66. // }
  67. // };
  68. _streamController.onListen = () {
  69. _log.info('Stream listener added for $url. Current data available? ${_transformed != null}');
  70. if (_transformed != null) {
  71. // 确保新订阅者立即获得缓存数据 (用于热重载或首次进入)
  72. // 使用 Future.microtask 确保在 onListen 结束后再触发 add,避免同步递归
  73. Future.microtask(() => _streamController.add(_transformed));
  74. }
  75. };
  76. }
  77. Future<void> refresh() async {
  78. await _remoteLoad();
  79. }
  80. Future<void> reload() async {
  81. await _cacheLoad();
  82. }
  83. _remoteLoad() async {
  84. try {
  85. final response = await http.get(Uri.parse(url));
  86. if (response.statusCode != 200) {
  87. // 如果状态码失败,则标记为失败,并抛出异常
  88. _hasRecentSuccessfulFetch = false; // !!! 关键:网络失败
  89. throw Exception('Invalid status code: ${response.statusCode} when fetching: $url');
  90. }
  91. // !!! 关键:网络请求成功,标记为成功
  92. _hasRecentSuccessfulFetch = true;
  93. _log.info('${response.statusCode}, $url, Network Success: true');
  94. final data = jsonDecode(response.body);
  95. _emit(data);
  96. await saveString(cachePath, response.body);
  97. } catch (error) {
  98. _streamController.addError(error);
  99. _log.severe('Remote load failed for $url: $error');
  100. // 即使在 catch 块中,也再次确认标记为失败(以防万一)
  101. _hasRecentSuccessfulFetch = false;
  102. }
  103. }
  104. _emit(dynamic data) async {
  105. _log.info('Emiting data..... ');
  106. try {
  107. if (transformFunction != null) {
  108. data = await transformFunction?.call(data);
  109. }
  110. // 仅当有监听者时才尝试添加数据
  111. if (_streamController.hasListener) {
  112. _streamController.add(data);
  113. }
  114. _transformed = data;
  115. } catch (error) {
  116. _streamController.addError(error);
  117. _log.severe('Data transformation or emission failed: $error');
  118. }
  119. }
  120. _cacheLoad() async {
  121. try {
  122. final file = await localFile(cachePath);
  123. if (await file.exists()) {
  124. _log.info('File $file exists, try loading from cache..');
  125. final data = await loadJson(cachePath);
  126. _emit(data);
  127. } else {
  128. if (url == ApiHelper.latestUri) {
  129. _log.info('Loading builtin latest asset data...');
  130. final data = await loadJSONFromAsset('assets/builtin/latest.json');
  131. _emit(data);
  132. }
  133. if (url == ApiHelper.collectionUri) {
  134. _log.info('Loading builtin collection asset data...');
  135. final data = await loadJSONFromAsset('assets/builtin/collection.json');
  136. _emit(data);
  137. }
  138. }
  139. } catch (error) {
  140. // 缓存加载失败不应该阻止远程加载
  141. _streamController.addError(error);
  142. }
  143. }
  144. String get hash => md5Hash(url);
  145. String get cachePath => 'api_cache/$hash';
  146. Stream get stream => _streamController.stream;
  147. @override
  148. String toString() {
  149. return 'CachedRequest(url=$url)';
  150. }
  151. }