Flutter本地存储需求:如何持久化API数据避免重复请求
Hey there! As a fellow Flutter dev, I totally get this frustration—route arguments work for one-off data passes, but they don’t cut it for persistent, globally accessible data. Let’s fix this with a straightforward, beginner-friendly solution that combines local storage and a central data manager, exactly matching your needs.
The Root of the Problem
Right now, you’re passing data via route arguments, which only lives as long as the receiving page is in memory. When you navigate away and come back, the page gets reinitialized, and that data is gone. We need two things:
- Persistent local storage to save data even when the app restarts or pages are destroyed
- A global data access point so any page can fetch the data without re-requesting the API
Solution 1: Singleton Data Manager + Hive (Simple & Perfect for Beginners)
We’ll use a singleton class to handle API calls, local storage, and in-memory caching. For local storage, Hive is ideal here—it’s lightweight, easy to use with custom objects (like your CurrInfo), and doesn’t require complex setup.
Step 1: Add Dependencies
First, add these to your pubspec.yaml:
dependencies: flutter: sdk: flutter http: ^1.1.0 hive: ^2.2.3 hive_flutter: ^1.1.0 dev_dependencies: hive_generator: ^1.1.5 build_runner: ^2.4.6
Step 2: Prepare Your CurrInfo Class for Hive
Hive needs an adapter to store custom objects. Annotate your class and generate the adapter:
import 'package:hive/hive.dart'; part 'curr_info.g.dart'; @HiveType(typeId: 0) // Unique ID for your object type class CurrInfo { @HiveField(0) final double price; // Example field—replace with your actual data CurrInfo({required this.price}); // Your existing API parsing logic factory CurrInfo.fromJson(Map<String, dynamic> json) { return CurrInfo(price: json['price'] as double); } // Existing API call method Future<CurrInfo> getInfo() async { // Your existing HTTP request logic here } }
Run this command in your terminal to generate the adapter:
flutter packages pub run build_runner build
Step 3: Create the Singleton Data Manager
This class will handle all data operations—fetching from API, reading/writing to local storage, and caching in memory:
import 'package:hive/hive.dart'; import 'curr_info.dart'; class DataManager { // Singleton instance—only one exists across the app static final DataManager _instance = DataManager._internal(); factory DataManager() => _instance; DataManager._internal(); final Box<CurrInfo> _infoBox = Hive.box<CurrInfo>('curr_info_box'); CurrInfo? _cachedData; // In-memory cache for fast access // Get data: prioritize cache → local storage → API Future<CurrInfo> getCurrInfo({bool forceRefresh = false}) async { // Return cached data if available and not forcing refresh if (!forceRefresh && _cachedData != null) { return _cachedData!; } // Check local storage next final localData = _infoBox.get('saved_info'); if (!forceRefresh && localData != null) { _cachedData = localData; return localData; } // Fetch from API if no cache/local data, or forced refresh final apiData = await CurrInfo().getInfo(); // Save to local storage and cache _infoBox.put('saved_info', apiData); _cachedData = apiData; return apiData; } // Force refresh data from API Future<CurrInfo> refreshCurrInfo() async { return getCurrInfo(forceRefresh: true); } }
Step 4: Initialize Hive & Update Your Pages
Modify your main.dart to initialize Hive before running the app:
import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'curr_info.dart'; import 'data_manager.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Hive.initFlutter(); Hive.registerAdapter(CurrInfoAdapter()); await Hive.openBox<CurrInfo>('curr_info_box'); runApp(const MyApp()); }
Update your loading page to use the data manager (no more route arguments!):
class LoadingPage extends StatefulWidget { const LoadingPage({super.key}); @override State<LoadingPage> createState() => _LoadingPageState(); } class _LoadingPageState extends State<LoadingPage> { @override void initState() { super.initState(); _loadData(); } Future<void> _loadData() async { // Preload data into cache/local storage await DataManager().getCurrInfo(); // Navigate to home page Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const HomePage()), ); } @override Widget build(BuildContext context) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } }
In your home page (or any other page), fetch data directly from the manager:
class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { late Future<CurrInfo> _dataFuture; @override void initState() { super.initState(); _dataFuture = DataManager().getCurrInfo(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('计算APP')), body: FutureBuilder<CurrInfo>( future: _dataFuture, builder: (context, snapshot) { if (snapshot.hasData) { final currInfo = snapshot.data!; return Center(child: Text('当前价格: ${currInfo.price}')); } else if (snapshot.hasError) { return Center(child: Text('加载失败: ${snapshot.error}')); } return const Center(child: CircularProgressIndicator()); }, ), floatingActionButton: FloatingActionButton( onPressed: () async { // Refresh data and update the UI await DataManager().refreshCurrInfo(); setState(() => _dataFuture = DataManager().getCurrInfo()); }, child: const Icon(Icons.refresh), ), ); } }
Bonus: Handling Offline Mode
Add a check in your getInfo() method to return local data if there’s no network:
Future<CurrInfo> getInfo() async { try { final response = await http.get(Uri.parse('YOUR_API_URL')); if (response.statusCode == 200) { return CurrInfo.fromJson(jsonDecode(response.body)); } else { throw Exception('请求失败'); } } on SocketException { // No internet—return local data if available final localData = Hive.box<CurrInfo>('curr_info_box').get('saved_info'); if (localData != null) { return localData; } else { throw Exception('无网络且无本地缓存数据'); } } }
Why This Works
- Global Access: The singleton
DataManagerlets any page fetch data without route arguments. - No Repeated API Calls: In-memory cache and local storage mean you only hit the API when needed.
- Offline Support: Local storage keeps data available even without internet.
- Easy Refresh: The
refreshCurrInfo()method lets you force an API update anytime.
内容的提问来源于stack exchange,提问作者Himanshu Thakur

