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

Flutter应用中如何缓存API响应,解决Tab切换重复加载问题?

Flutter API响应缓存与Tab切换加载优化方案

当然可以对API响应数据做缓存,针对你切换Tab重复加载、出现空白的问题,给你几个实用的解决思路:

方案一:让Tab页面保持状态(最直接解决重复加载)

TabBarView默认切换页面时会销毁Widget状态,导致每次切回来都重新执行FutureBuilder发起请求。用AutomaticKeepAliveClientMixin就能让页面保留状态,数据只加载一次:

实现步骤:

  • 你的Tab页面State类混入AutomaticKeepAliveClientMixin
  • 重写wantKeepAlive返回true
  • 在build方法里必须调用super.build(context)

示例代码:

class AvailableOffersTab extends StatefulWidget {
  const AvailableOffersTab({Key? key}) : super(key: key);

  @override
  State<AvailableOffersTab> createState() => _AvailableOffersTabState();
}

class _AvailableOffersTabState extends State<AvailableOffersTab> with AutomaticKeepAliveClientMixin {
  Future<List<Offer>>? _offersFuture;

  @override
  void initState() {
    super.initState();
    // 只在页面初始化时发起一次API请求
    _offersFuture = _fetchAvailableOffers();
  }

  Future<List<Offer>> _fetchAvailableOffers() async {
    // 替换成你的API请求逻辑
    final response = await http.get(Uri.parse('https://your-api.com/available-offers'));
    // 解析JSON为Offer模型,这里假设你有Offer.fromJson方法
    return List<Offer>.from(jsonDecode(response.body).map((item) => Offer.fromJson(item)));
  }

  // 保持页面状态
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用这行代码
    return isdataNull 
        ? Center(
            child: Column(
              children: [
                SizedBox(height: MediaQuery.of(context).size.height * .25),
                Container(
                  height: 120,
                  width: 120,
                  decoration: const BoxDecoration(
                    image: DecorationImage(
                      image: AssetImage('assets/nocollection.png'),
                      fit: BoxFit.fill
                    )
                  ),
                ),
                const SizedBox(height: 10),
                const Text('No offer available', style: TextStyle(color: Colors.black, fontSize: 14))
              ],
            ),
          )
        : FutureBuilder<List<Offer>>(
            future: _offersFuture,
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return const Center(child: CircularProgressIndicator());
              } else if (snapshot.hasError) {
                return const Center(child: Text('加载失败'));
              } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
                // 显示你的空白状态
                return Center(
                  child: Column(
                    children: [
                      SizedBox(height: MediaQuery.of(context).size.height * .25),
                      Container(
                        height: 120,
                        width: 120,
                        decoration: const BoxDecoration(
                          image: DecorationImage(
                            image: AssetImage('assets/nocollection.png'),
                            fit: BoxFit.fill
                          )
                        ),
                      ),
                      const SizedBox(height: 10),
                      const Text('No offer available', style: TextStyle(color: Colors.black, fontSize: 14))
                    ],
                  ),
                );
              } else {
                // 构建你的网格布局
                return GridView.builder(
                  itemCount: snapshot.data!.length,
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
                  itemBuilder: (context, index) => OfferItem(offer: snapshot.data![index]),
                );
              }
            },
          );
  }
}

方案二:本地缓存API响应(兼顾数据新鲜度)

如果需要数据定期更新,同时切换Tab时先显示缓存数据、后台静默刷新,可以用本地存储工具,比如shared_preferences(适合简单数据)或hive(适合复杂对象):

核心逻辑:

  1. 页面初始化时先读取本地缓存,判断是否过期(比如设置5分钟有效期)
  2. 缓存未过期则直接显示,同时后台请求最新数据
  3. 请求成功后更新缓存并刷新UI;请求失败则继续用缓存

示例代码(用shared_preferences):

import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

// 缓存工具方法
Future<String?> _getCachedData(String tabKey) async {
  final prefs = await SharedPreferences.getInstance();
  final cachedData = prefs.getString('cache_$tabKey');
  final cacheTime = prefs.getInt('cache_time_$tabKey');
  
  // 设置5分钟有效期
  if (cachedData != null && cacheTime != null) {
    final now = DateTime.now().millisecondsSinceEpoch;
    if (now - cacheTime < 5 * 60 * 1000) {
      return cachedData;
    }
  }
  return null;
}

Future<void> _saveDataToCache(String tabKey, String data) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('cache_$tabKey', data);
  await prefs.setInt('cache_time_$tabKey', DateTime.now().millisecondsSinceEpoch);
}

// Tab页面实现
class _AvailableOffersTabState extends State<AvailableOffersTab> {
  List<Offer>? _offers;

  @override
  void initState() {
    super.initState();
    // 先加载缓存
    _loadCachedOffers();
    // 后台刷新最新数据
    _refreshOffers();
  }

  Future<void> _loadCachedOffers() async {
    final cachedData = await _getCachedData('available_offers');
    if (cachedData != null) {
      setState(() {
        _offers = List<Offer>.from(jsonDecode(cachedData).map((item) => Offer.fromJson(item)));
      });
    }
  }

  Future<void> _refreshOffers() async {
    try {
      final response = await http.get(Uri.parse('https://your-api.com/available-offers'));
      final newOffers = List<Offer>.from(jsonDecode(response.body).map((item) => Offer.fromJson(item)));
      await _saveDataToCache('available_offers', jsonEncode(newOffers));
      setState(() {
        _offers = newOffers;
      });
    } catch (e) {
      // 请求失败,不更新缓存,继续用旧数据
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_offers == null) {
      // 首次加载无缓存时显示加载状态
      return const Center(child: CircularProgressIndicator());
    }
    return _offers!.isEmpty
        ? Center(
            child: Column(
              children: [
                SizedBox(height: MediaQuery.of(context).size.height * .25),
                Container(
                  height: 120,
                  width: 120,
                  decoration: const BoxDecoration(
                    image: DecorationImage(
                      image: AssetImage('assets/nocollection.png'),
                      fit: BoxFit.fill
                    )
                  ),
                ),
                const SizedBox(height: 10),
                const Text('No offer available', style: TextStyle(color: Colors.black, fontSize: 14))
              ],
            ),
          )
        : GridView.builder(
            itemCount: _offers!.length,
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
            itemBuilder: (context, index) => OfferItem(offer: _offers![index]),
          );
  }
}

方案三:全局状态管理(多Tab数据联动)

如果三个Tab的数据需要联动(比如收藏优惠后同步更新「可用优惠」和「已收藏优惠」),可以用全局状态管理工具(比如Provider、GetX)把数据存在全局状态中,切换Tab时直接读取状态数据,无需重复请求。

示例(用Provider):

  1. 创建全局数据Provider:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

class OfferProvider extends ChangeNotifier {
  List<Offer>? _availableOffers;
  List<Offer>? _favoritedOffers;
  List<Offer>? _myOffers;

  List<Offer>? get availableOffers => _availableOffers;
  List<Offer>? get favoritedOffers => _favoritedOffers;
  List<Offer>? get myOffers => _myOffers;

  Future<void> fetchAvailableOffers() async {
    final response = await http.get(Uri.parse('https://your-api.com/available-offers'));
    _availableOffers = List<Offer>.from(jsonDecode(response.body).map((item) => Offer.fromJson(item)));
    notifyListeners();
  }

  Future<void> fetchFavoritedOffers() async {
    final response = await http.get(Uri.parse('https://your-api.com/favorited-offers'));
    _favoritedOffers = List<Offer>.from(jsonDecode(response.body).map((item) => Offer.fromJson(item)));
    notifyListeners();
  }

  Future<void> fetchMyOffers() async {
    final response = await http.get(Uri.parse('https://your-api.com/my-offers'));
    _myOffers = List<Offer>.from(jsonDecode(response.body).map((item) => Offer.fromJson(item)));
    notifyListeners();
  }
}
  1. 在App根节点注入Provider:
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => OfferProvider(),
      child: const MyApp(),
    ),
  );
}
  1. Tab页面中使用:
class AvailableOffersTab extends StatelessWidget {
  const AvailableOffersTab({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final offerProvider = Provider.of<OfferProvider>(context);
    
    // 仅在数据为空时发起请求
    if (offerProvider.availableOffers == null) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        offerProvider.fetchAvailableOffers();
      });
    }

    final offers = offerProvider.availableOffers;
    if (offers == null) {
      return const Center(child: CircularProgressIndicator());
    }
    return offers.isEmpty
        ? Center(
            child: Column(
              children: [
                SizedBox(height: MediaQuery.of(context).size.height * .25),
                Container(
                  height: 120,
                  width: 120,
                  decoration: const BoxDecoration(
                    image: DecorationImage(
                      image: AssetImage('assets/nocollection.png'),
                      fit: BoxFit.fill
                    )
                  ),
                ),
                const SizedBox(height: 10),
                const Text('No offer available', style: TextStyle(color: Colors.black, fontSize: 14))
              ],
            ),
          )
        : GridView.builder(
            itemCount: offers.length,
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
            itemBuilder: (context, index) => OfferItem(offer: offers[index]),
          );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 19:24:10