| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- import 'dart:async';
- import 'dart:convert';
- import 'package:flutter/material.dart'; // 引入 Flutter 核心包
- import 'package:http/http.dart' as http;
- import 'package:image_puzzle/models/api_helper.dart';
- import 'package:image_puzzle/utils/utils.dart';
- import 'package:logging/logging.dart';
- final Logger _log = Logger('cached_request.dart');
- typedef TransformFunction = Future<dynamic> Function(dynamic json);
- // 混入 WidgetsBindingObserver 以监听应用生命周期
- class CachedRequest with WidgetsBindingObserver {
- static final Map<String, CachedRequest> _cache = {};
- final String url;
- TransformFunction? transformFunction;
- // 仅使用 .broadcast(),但 onListen 只会触发一次
- final StreamController _streamController = StreamController.broadcast();
- dynamic _transformed;
- // --- 【新增】Getter:允许外部同步访问最新的缓存数据 ---
- dynamic get cachedData => _transformed;
- CachedRequest._internal(this.url, this.transformFunction) {
- _log.info('New cached request: $url');
- // 注册生命周期监听器
- WidgetsBinding.instance.addObserver(this);
- _init();
- }
- factory CachedRequest.fromUrl(String url, {TransformFunction? transformFunction}) {
- // 确保单例模式下,只注册一次监听器
- if (!_cache.containsKey(url)) {
- _cache[url] = CachedRequest._internal(url, transformFunction);
- }
- return _cache[url]!;
- }
- // --- 关键修改:生命周期监听 ---
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- if (state == AppLifecycleState.resumed) {
- _log.info('App Resumed from background. Forcing refresh for $url');
- // 应用程序从后台恢复到前台时,强制刷新数据
- refresh();
- }
- }
- // 由于 CachedRequest 是一个单例,它不会被销毁,除非应用完全关闭。
- // 但是,为了严谨性,如果添加了 Dispose 逻辑,应记得移除 Observer。
- // 注意:在 Flutter Provider 或 InheritedWidget 依赖的单例中,通常不需要手动调用 dispose。
- // 如果需要清理:
- /*
- void dispose() {
- WidgetsBinding.instance.removeObserver(this);
- _streamController.close();
- // ... clean up
- }
- */
- // ---------------------------------
- _init() {
- // FIX: 首次初始化时,立即尝试加载缓存(如果有)
- _cacheLoad();
- _remoteLoad();
- // _streamController.onListen = () {
- // if (_transformed != null) {
- // // 如果有缓存数据,立即发送给新的监听者
- // _streamController.add(_transformed);
- // }
- // };
- _streamController.onListen = () {
- _log.info('Stream listener added for $url. Current data available? ${_transformed != null}');
- if (_transformed != null) {
- // 确保新订阅者立即获得缓存数据 (用于热重载或首次进入)
- // 使用 Future.microtask 确保在 onListen 结束后再触发 add,避免同步递归
- Future.microtask(() => _streamController.add(_transformed));
- }
- };
- }
- Future<void> refresh() async {
- await _remoteLoad();
- }
- Future<void> reload() async {
- await _cacheLoad();
- }
- _remoteLoad() async {
- try {
- final response = await http.get(Uri.parse(url));
- if (response.statusCode != 200) {
- throw Exception('Invalid status code: ${response.statusCode} when fetching: $url');
- }
- _log.info('${response.statusCode}, $url');
- final data = jsonDecode(response.body);
- _emit(data);
- await saveString(cachePath, response.body);
- } catch (error) {
- _streamController.addError(error);
- _log.severe('Remote load failed for $url: $error');
- }
- }
- _emit(dynamic data) async {
- _log.info('Emiting data..... ');
- try {
- if (transformFunction != null) {
- data = await transformFunction?.call(data);
- }
- // 仅当有监听者时才尝试添加数据
- if (_streamController.hasListener) {
- _streamController.add(data);
- }
- _transformed = data;
- } catch (error) {
- _streamController.addError(error);
- _log.severe('Data transformation or emission failed: $error');
- }
- }
- _cacheLoad() async {
- try {
- final file = await localFile(cachePath);
- if (await file.exists()) {
- _log.info('File $file exists, try loading from cache..');
- final data = await loadJson(cachePath);
- _emit(data);
- } else {
- if (url == ApiHelper.latestUri) {
- _log.info('Loading builtin latest asset data...');
- final data = await loadJSONFromAsset('assets/builtin/latest.json');
- _emit(data);
- }
- if (url == ApiHelper.collectionUri) {
- _log.info('Loading builtin collection asset data...');
- final data = await loadJSONFromAsset('assets/builtin/collection.json');
- _emit(data);
- }
- }
- } catch (error) {
- // 缓存加载失败不应该阻止远程加载
- _streamController.addError(error);
- }
- }
- String get hash => md5Hash(url);
- String get cachePath => 'api_cache/$hash';
- Stream get stream => _streamController.stream;
- @override
- String toString() {
- return 'CachedRequest(url=$url)';
- }
- }
|