hanki.dev

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 📧

#flutter #study