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(适合复杂对象):
核心逻辑:
- 页面初始化时先读取本地缓存,判断是否过期(比如设置5分钟有效期)
- 缓存未过期则直接显示,同时后台请求最新数据
- 请求成功后更新缓存并刷新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):
- 创建全局数据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(); } }
- 在App根节点注入Provider:
void main() { runApp( ChangeNotifierProvider( create: (context) => OfferProvider(), child: const MyApp(), ), ); }
- 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
相关产品推荐
相关产品推荐

