In our increasingly connected world, people who use mobile apps expect a smooth experience even when they aren’t connected to the internet. Offline data access is important for current mobile apps because it makes sure they work well even when they don’t have an internet connection. In this blog post, we’ll talk about how to make a local cache in your Flutter app so that users can access data even when they’re not online. When users are online, the cache can be synchronised with a remote data source.
Understanding the Importance of Offline Data Access
Offline access to data is important for many reasons. First of all, it makes the user experience much better by letting people keep using your app even when there is little or no internet connection. Also, if you have a limited data plan, offline data access can help you use less data and save battery life by reducing network calls.
Choosing the Right Local Storage Solution for Your Flutter App
Flutter has a number of options for local storage, such as Shared Preferences, SQLite, Hive, and Drift. Depending on what your app needs, each has its own pros and cons. In this part, we’ll go into more detail about each storage option. We’ll talk about their pros and cons and give code examples for each.
Shared Preferences
Shared Preferences is a great way to store small amounts of data in pairs of key and value. This lightweight storage option is easy to set up and can keep simple data types like strings, integers, and booleans even after an app is closed and reopened.
Advantages:
- Easy to implement and use.
- Suitable for storing small amounts of data.
Disadvantages:
- Not suitable for complex data structures or large datasets.
- Limited data types supported.
Example usage:
import 'package:shared_preferences/shared_preferences.dart';
// Saving data to Shared Preferences
Future<void> saveData(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
}
// Loading data from Shared Preferences
Future<String?> loadData(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}
SQLite
SQLite is a strong relational database that can handle data models with many parts. It requires writing SQL queries and might have a steeper learning curve, but it is a powerful way to store bigger datasets and keep relational data.
Advantages:
- Supports complex data structures.
- Robust and reliable, suitable for larger datasets.
Disadvantages:
- Steeper learning curve due to SQL query requirements.
- More complex to set up and manage.
Example usage:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
// Initialize the database
Future<Database> getDatabase() async {
return openDatabase(
join(await getDatabasesPath(), 'my_database.db'),
onCreate: (db, version) {
return db.execute(
"CREATE TABLE my_data(id TEXT PRIMARY KEY, name TEXT)",
);
},
version: 1,
);
}
// Insert data into the database
Future<void> insertData(MyDataModel data) async {
final db = await getDatabase();
await db.insert(
'my_data',
data.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// Query data from the database
Future<List<MyDataModel>> getData() async {
final db = await getDatabase();
final List<Map<String, dynamic>> maps = await db.query('my_data');
return List.generate(maps.length, (i) {
return MyDataModel(
id: maps[i]['id'],
name: maps[i]['name'],
);
});
}
Hive
Hive is a small, fast, and easy-to-use NoSQL database that works well with complex data structures. It’s a great choice for Flutter apps because it’s fast and easy to use, but it doesn’t have all the features of a relational database.
Advantages:
- Easy to set up and use.
- High-performance and suitable for complex data structures.
Disadvantages:
- Lacks the full capabilities of a relational database.
Example usage:
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
// Initialize Hive
void main() async {
await Hive.initFlutter();
Hive.registerAdapter(MyDataModelAdapter());
runApp(MyApp());
}
// Open a Hive box and store data
Future<void> saveData(MyDataModel data) async {
final box = await Hive.openBox<MyDataModel>('myDataBox');
await box.put(data.id, data);
}
// Load data from Hive
Future<List<MyDataModel>> loadData() async {
final box = await Hive.openBox<MyDataModel>('myDataBox');
return box.values.toList();
}
Drift
Drift is a type-safe, high-level tool for persistence that uses SQLite behind the scenes. It makes working with relational databases in Dart easier, and it lets you use complicated queries and data structures.
Advantages:
- Type-safe and high-level API for working with SQLite.
- Streamlines SQLite usage in Dart.
Disadvantages:
- Has a learning curve for developers new to the library.
Example usage:
import 'package:drift/drift.dart';
// Define the data model and database
part 'drift_database.g.dart';
class MyData extends Table {
TextColumn get id => text().withLength(min: 1, max: 50)();
TextColumn get name => text().withLength(min: 1, max: 50)();
}
@DriftDatabase(tables: [MyData])
class DriftDatabase extends _$DriftDatabase {
DriftDatabase(QueryExecutor e) : super(e);
Future<List<MyDataModel>> getAllData() => select(myData).get();
Future<void> insertData(MyDataModel data) => into(myData).insert(data);
}
// Use the Drift database in your app
final driftDatabase = DriftDatabase();
// Query data from the Drift database
List<MyDataModel> data = await driftDatabase.getAllData();
Implementing a Local Cache for Your App: A Step-by-Step Guide
In this example, we’ll make a local cache in a Flutter app by using the Hive library. First, add the requirements for Hive and Hive Flutter to your pubspec.yaml
file:
dependencies:
hive: ^2.0.4
hive_flutter: ^1.1.0
Next, make a new class for your data model that extends the HiveObject class. You also need to give your data model a unique type ID:
import 'package:hive/hive.dart';
part 'my_data_model.g.dart';
@HiveType(typeId: 0)
class MyDataModel extends HiveObject {
@HiveField(0)
String id;
@HiveField(1)
String name;
MyDataModel({required this.id, required this.name});
}
Now that we have a data model and a way to store it, let’s set up the caching system:
Fetch data from the server
Create a method that gets data from the server and gives back a list of your data model items. In the real world, you would use an HTTP request or another remote data source to get the info you need.
Future<List<MyDataModel>> fetchDataFromServer() async {
// Replace this with an actual network request to fetch data
List<MyDataModel> data = [
MyDataModel(id: '1', name: 'Sample Data 1'),
MyDataModel(id: '2', name: 'Sample Data 2'),
];
return Future.delayed(Duration(seconds: 1), () => data);
}
Store data locally
Create a method that stores the fetched data locally in the Hive box:
Future<void> storeDataLocally(List<MyDataModel> data) async {
final box = await Hive.openBox<MyDataModel>('myDataBox');
for (final item in data) {
await box.put(item.id, item);
}
await box.close();
}
Load data from the local cache
Create a method that loads data from the local Hive box and returns a list of your data model objects:
Future<List<MyDataModel>> loadLocalData() async {
final box = await Hive.openBox<MyDataModel>('myDataBox');
return box.values.toList();
}
Check for internet connectivity
Create a method that checks whether the device has an active internet connection. This method can use the connectivity
package or other approaches, depending on your requirements.
Future<bool> isOnline() async {
// Implement a method to check internet connectivity.
}
Implement the caching mechanism
Create a method that fetches data from the server when the device is online, stores it in the local Hive box, and loads the data from the local cache when the device is offline:
void loadData() async {
List<MyDataModel> data;
if (await isOnline()) {
data = await fetchDataFromServer();
await storeDataLocally(data);
} else {
data = await loadLocalData();
}
// Use the 'data' variable to display the data in your app.
}
Synchronize data with the remote server
To keep your local cache up-to-date with the latest data, you can set up a periodic background task that checks for connectivity and updates the local data if the device is online:
void syncDataWithServer() async {
if (await isOnline()) {
List<MyDataModel> data = await fetchDataFromServer();
await storeDataLocally(data);
}
}
void startSyncTask() {
Timer.periodic(Duration(hours: 1), (timer) {
syncDataWithServer();
});
}
Call the startSyncTask()
method in your main.dart
file, after initializing Hive:
void main() async {
await Hive.initFlutter();
Hive.registerAdapter(MyDataModelAdapter());
startSyncTask();
runApp(MyApp());
}
Conclusion
Adding offline data access to your Flutter app is important if you want users to have a smooth experience even when they can’t connect to the internet. By using local storage options like Hive, you can set up a local cache that lets your app work well even when users aren’t connected to the internet. Also, syncing your local cache with the faraway server makes sure that your app always has the most up-to-date information. Happy Fluttering!