Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Learn Caching and Offline Mode | Clean Architecture for API Apps
Practice
Projects
Quizzes & Challenges
Quizzes
Challenges
/
Flutter REST API Integration

bookCaching and Offline Mode

When building API-driven Flutter apps, you often face challenges with unreliable networks or slow connections. To provide a seamless experience, you can implement local caching and an offline mode. Caching stores API data locally, so your app can quickly display information without needing to fetch it from the server every time. Offline mode ensures your app remains usable even when the device loses internet connectivity. Together, these techniques improve both speed and reliability for users.

Types of Caching in Flutter Apps
expand arrow
  • Memory cache: stores data in RAM for fast, temporary access; data is lost when the app closes.
  • File-based cache: saves data to the device's storage (such as with the path_provider package); data persists between app sessions.
  • Database cache: uses a local database (like sqflite or hive) to store structured data for offline access.
Cache Invalidation Strategies
expand arrow
  • Time-based expiration: set a time limit after which cached data is considered stale and must be refreshed from the API.
  • Manual refresh: allow users to trigger a refresh (such as with a pull-to-refresh gesture or button).
  • Conditional requests: use HTTP headers like ETag or Last-Modified to check if data has changed on the server before updating the cache.
main.dart

main.dart

copy
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
// main.dart import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:http/http.dart' as http; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Caching Example', home: PostsPage(), ); } } class PostsPage extends StatefulWidget { @override _PostsPageState createState() => _PostsPageState(); } class _PostsPageState extends State<PostsPage> { List<dynamic> _posts = []; bool _loading = true; bool _offline = false; @override void initState() { super.initState(); _loadPosts(); } Future<File> _getCacheFile() async { final dir = await getApplicationDocumentsDirectory(); return File('${dir.path}/posts_cache.json'); } Future<void> _saveToCache(List<dynamic> posts) async { final file = await _getCacheFile(); await file.writeAsString(jsonEncode(posts)); } Future<List<dynamic>?> _readFromCache() async { try { final file = await _getCacheFile(); if (await file.exists()) { final contents = await file.readAsString(); return jsonDecode(contents); } } catch (_) {} return null; } Future<void> _loadPosts() async { setState(() { _loading = true; _offline = false; }); try { final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts')); if (response.statusCode == 200) { final posts = jsonDecode(response.body); setState(() { _posts = posts; }); await _saveToCache(posts); } else { throw Exception('Failed to load'); } } catch (_) { // Fallback to cache final cached = await _readFromCache(); if (cached != null) { setState(() { _posts = cached; _offline = true; }); } } setState(() { _loading = false; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Posts'), actions: [ IconButton( icon: Icon(Icons.refresh), onPressed: _loadPosts, ), ], ), body: _loading ? Center(child: CircularProgressIndicator()) : _posts.isEmpty ? Center(child: Text('No posts available')) : Column( children: [ if (_offline) Container( color: Colors.amber, padding: EdgeInsets.all(8), child: Text('You are offline. Showing cached data.'), ), Expanded( child: ListView.builder( itemCount: _posts.length, itemBuilder: (context, index) { final post = _posts[index]; return ListTile( title: Text(post['title']), subtitle: Text(post['body']), ); }, ), ), ], ), ); } }

The code above uses a simple file-based cache to store API responses locally. When the app loads, it tries to fetch fresh data from the API. If the network request fails, it falls back to show cached data. Cache invalidation and refresh are handled by allowing the user to refresh manually (with the refresh button), which attempts to fetch new data and overwrite the cache. This approach ensures users see up-to-date information when online, but always have access to the last known data if offline.

question mark

What is a main benefit of implementing caching in API apps?

Select the correct answer

Everything was clear?

How can we improve it?

Thanks for your feedback!

SectionΒ 3. ChapterΒ 3

Ask AI

expand

Ask AI

ChatGPT

Ask anything or try one of the suggested questions to begin our chat

bookCaching and Offline Mode

Swipe to show menu

When building API-driven Flutter apps, you often face challenges with unreliable networks or slow connections. To provide a seamless experience, you can implement local caching and an offline mode. Caching stores API data locally, so your app can quickly display information without needing to fetch it from the server every time. Offline mode ensures your app remains usable even when the device loses internet connectivity. Together, these techniques improve both speed and reliability for users.

Types of Caching in Flutter Apps
expand arrow
  • Memory cache: stores data in RAM for fast, temporary access; data is lost when the app closes.
  • File-based cache: saves data to the device's storage (such as with the path_provider package); data persists between app sessions.
  • Database cache: uses a local database (like sqflite or hive) to store structured data for offline access.
Cache Invalidation Strategies
expand arrow
  • Time-based expiration: set a time limit after which cached data is considered stale and must be refreshed from the API.
  • Manual refresh: allow users to trigger a refresh (such as with a pull-to-refresh gesture or button).
  • Conditional requests: use HTTP headers like ETag or Last-Modified to check if data has changed on the server before updating the cache.
main.dart

main.dart

copy
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
// main.dart import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:http/http.dart' as http; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Caching Example', home: PostsPage(), ); } } class PostsPage extends StatefulWidget { @override _PostsPageState createState() => _PostsPageState(); } class _PostsPageState extends State<PostsPage> { List<dynamic> _posts = []; bool _loading = true; bool _offline = false; @override void initState() { super.initState(); _loadPosts(); } Future<File> _getCacheFile() async { final dir = await getApplicationDocumentsDirectory(); return File('${dir.path}/posts_cache.json'); } Future<void> _saveToCache(List<dynamic> posts) async { final file = await _getCacheFile(); await file.writeAsString(jsonEncode(posts)); } Future<List<dynamic>?> _readFromCache() async { try { final file = await _getCacheFile(); if (await file.exists()) { final contents = await file.readAsString(); return jsonDecode(contents); } } catch (_) {} return null; } Future<void> _loadPosts() async { setState(() { _loading = true; _offline = false; }); try { final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts')); if (response.statusCode == 200) { final posts = jsonDecode(response.body); setState(() { _posts = posts; }); await _saveToCache(posts); } else { throw Exception('Failed to load'); } } catch (_) { // Fallback to cache final cached = await _readFromCache(); if (cached != null) { setState(() { _posts = cached; _offline = true; }); } } setState(() { _loading = false; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Posts'), actions: [ IconButton( icon: Icon(Icons.refresh), onPressed: _loadPosts, ), ], ), body: _loading ? Center(child: CircularProgressIndicator()) : _posts.isEmpty ? Center(child: Text('No posts available')) : Column( children: [ if (_offline) Container( color: Colors.amber, padding: EdgeInsets.all(8), child: Text('You are offline. Showing cached data.'), ), Expanded( child: ListView.builder( itemCount: _posts.length, itemBuilder: (context, index) { final post = _posts[index]; return ListTile( title: Text(post['title']), subtitle: Text(post['body']), ); }, ), ), ], ), ); } }

The code above uses a simple file-based cache to store API responses locally. When the app loads, it tries to fetch fresh data from the API. If the network request fails, it falls back to show cached data. Cache invalidation and refresh are handled by allowing the user to refresh manually (with the refresh button), which attempts to fetch new data and overwrite the cache. This approach ensures users see up-to-date information when online, but always have access to the last known data if offline.

question mark

What is a main benefit of implementing caching in API apps?

Select the correct answer

Everything was clear?

How can we improve it?

Thanks for your feedback!

SectionΒ 3. ChapterΒ 3
some-alt