cached_request.dart 5.7 KB

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