Shared Preferences vs Hive for storing objects | Flutter
Recently I started a new side project. It's a simple Flutter app for searching for stuff from the DnD 5e SRD.
I made the first implementation with Shared Preferences without thinking too much. I thought this was lightweight, reliable small sized dependency with no bullshit. Then I noticed hive dependency is almost half the size 🤯
shared_preferences | hive + hive_flutter |
---|---|
178 KB |
104 KB |
The object
There are 1994 objects which needs to be stored on the first app launch. They are stored immediately so that the filtering is smooth (when typing something to the search box) and there is no need to make HTTP requests when searching for items.
It's a simple class with 3 strings and 1 enum.
class SearchResult {
final String index;
final String name;
final String url;
late ItemType type;
SearchResult(this.index, this.name, this.url, ItemType? type) {
this.type = type ?? getItemTypeFromUrl(url);
}
}
Shared Preferences
Setup
Unfortunately you can't store objects with prefs (I'm just gonna call it prefs instead of Shared Preferences). The workaround is simple, just convert them into json and save as a List of strings. When retrieving the items, all you gotta do is create new objects from the json strings.
factory SearchResult.fromJson(Map<String, dynamic> json) {
ItemType? type =
json["type"] != null ? getItemTypeFromString(json["type"]) : null;
return SearchResult(json["index"], json["name"], json["url"], type);
}
Map<String, dynamic> toJson() =>
{"index": index, "name": name, "url": url, "type": type.toShortString()};
That's not a lot of code, but it is quite frustrating to write that kind of boilerplate when you have dozens of different classes which needs to be saved. Anyway, that's not the point of this post.
Saving items
I already have all the items in a neat list, let's see how long it takes to a) convert them all to json and b) save the string list into SharedPreferences
static void saveAllItems(List<SearchResult> items) async {
Stopwatch sw = Stopwatch()..start();
SharedPreferences prefs = await SharedPreferences.getInstance();
List<String> json = items.map((e) => jsonEncode(e.toJson())).toList();
prefs.setStringList(KEY_ALL_ITEMS, json);
sw.stop();
print("saveAllItems executed in ${sw.elapsed}");
}
I/flutter ( 5421): Fetched total of 1994 search results
I/flutter ( 5421): saveAllItems executed in 0:00:00.017765
That's 0.017765
seconds. What about loading them all?
Loading items
static Future<List<SearchResult>> getAllItems() async {
Stopwatch sw = Stopwatch()..start();
SharedPreferences prefs = await SharedPreferences.getInstance();
List<String> rawString = prefs.getStringList(KEY_ALL_ITEMS) ?? [];
List<SearchResult> items =
rawString.map((e) => SearchResult.fromJson(jsonDecode(e))).toList();
sw.stop();
print("getAllItems executed in ${sw.elapsed}");
return items;
}
I/flutter ( 5421): getAllItems executed in 0:00:00.262851
Quarter of a second, I'd say that's actually pretty bad for just a list of 2000 objects 🤔 Let's see how well Hive performs
Hive
Setup
Hive supports saving custom objects, all you gotta do is add some annotations and then run flutter packages pub run build_runner build
to build some adapters. Here's what it looks like.
part "search_result.g.dart";
@HiveType(typeId: 1)
class SearchResult {
@HiveField(0)
final String index;
@HiveField(1)
final String name;
@HiveField(2)
final String url;
@HiveField(3)
late ItemType type;
SearchResult(this.index, this.name, this.url, ItemType? type) {
this.type = type ?? getItemTypeFromUrl(url);
}
}
No fromJson
or toJson
, I love it. Same annotations also need to be added to the ItemType
enum.
We need to do some initialization stuff before building the UI. This is different from prefs, since with that implementation the initialization is done when we get the instance of the prefs for the first time (in the getAllItems
function).
void main() async {
await initHive();
runApp(const MyApp());
}
Future<void> initHive() async {
Stopwatch sw = Stopwatch()..start();
Hive.registerAdapter(SearchResultAdapter());
Hive.registerAdapter(ItemTypeAdapter());
await Hive.initFlutter();
await Hive.openBox(HIVE_BOX);
sw.stop();
print("initHive executed in ${sw.elapsed}");
}
I/flutter ( 5794): initHive executed in 0:00:00.096165
Tenth of a second, not too bad.
Saving items
static void saveAllItems(List<SearchResult> items) async {
Stopwatch sw = Stopwatch()..start();
var box = Hive.box(HIVE_BOX);
box.put(KEY_ALL_ITEMS, items);
sw.stop();
print("saveAllItems executed in ${sw.elapsed}");
}
I/flutter ( 5794): saveAllItems executed in 0:00:00.003747
At least saving is a LOT faster. But If I've understood correctly saving stuff is the Achilles' heel of prefs, loading stuff should be more even.
Loading items
static Future<List<SearchResult>> getAllItems() async {
Stopwatch sw = Stopwatch()..start();
var box = await Hive.openBox(HIVE_BOX);
var items = box.get(KEY_ALL_ITEMS);
sw.stop();
print("getAllItems executed in ${sw.elapsed}");
return items.cast<SearchResult>();
}
I/flutter ( 5794): getAllItems executed in 0:00:00.000686
Okay the results seem to be in a different level altogether :D Since the implementation is a little different between Hive and prefs (Hive needs initialization before building the UI, prefs is initialized first time when we run getAllItems
) we need to do initHive time + getAllItems time
to get more accurate comparison
Results
function | shared_preferences | hive |
---|---|---|
saveAllItems |
0.017765s |
0.003747s |
(initHive +) getAllItems |
0.262851s |
0.096165s + 0.000686s = 0.096851s |
Hive seems to be superior in every way. It's faster at saving stuff, faster at loading stuff, simpler, and has smaller dependency size.
There's no comment box but you can send me an email 📧