summaryrefslogtreecommitdiff
path: root/lib/jotai.dart
diff options
context:
space:
mode:
Diffstat (limited to 'lib/jotai.dart')
-rw-r--r--lib/jotai.dart70
1 files changed, 47 insertions, 23 deletions
diff --git a/lib/jotai.dart b/lib/jotai.dart
index a415e13..16de4e7 100644
--- a/lib/jotai.dart
+++ b/lib/jotai.dart
@@ -1,3 +1,19 @@
+// When I started this project, I knew I would eventually need either a library
+// similar to streaming_shared_preferences, or a global state management system
+// to cache the shared preferences. But I could not get that library to work,
+// and I don't like any of the state management solutions that exist for
+// Flutter.
+//
+// What I really wanted was something like jotai, and eventually I decided to
+// just write my own. I'm very happy with how simple it is. It's just an
+// Observable class, and a widget that listens for changes to the observable.
+// It was very easy to extend it to save the preferences, and it's blazingly
+// fast.
+//
+// There is a library called fl_observable that does essentially this but
+// better. I didn't find it until after I finished the settings page, and it
+// hasn't been updated for over a year. It also has very few users.
+
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -9,9 +25,10 @@ String _defaultToString(dynamic value) => value.toString();
void Function(T) storageObserver<T>(
String key, [
String Function(T) toString = _defaultToString,
-]) {
- return (T value) => SharedPreferencesAsync().setString(key, toString(value));
-}
+]) =>
+ // Loading the shared preferences for asll of these might not be
+ // particularly efficient, but it seems fine on my Moto G Stylus.
+ (T value) => SharedPreferencesAsync().setString(key, toString(value));
Future<T> fromString<T>(
String key,
@@ -19,8 +36,11 @@ Future<T> fromString<T>(
String Function(T) toString = _defaultToString,
]) {
return SharedPreferencesAsync()
+ // This takes time to load in, but I've never noticed the delay
.getString(key)
.then(
+ // Going through all of these sounds like it's slower than necessary,
+ // but it's very simple, and most of the options lists are very small.
(value) => options.firstWhere((option) => value == toString(option)),
);
}
@@ -30,9 +50,9 @@ Observable<T> observablePreference<T>(
T defaultValue,
List<T> options, [
String Function(T) toString = _defaultToString,
-]) => Observable(
- defaultValue,
- loadValue: fromString(key, options, toString),
+]) => Observable.fromFuture(
+ initialValue: defaultValue,
+ future: fromString(key, options, toString),
observers: [storageObserver(key, toString)],
);
@@ -64,20 +84,26 @@ final locationAccuracyObservable = observablePreference(
class Observable<T> {
T _value;
- final List<void Function(T)?> _observers = [];
+ int _nextId = 0;
+ // A map is used instead of a list for fast deletions and to avoid memory
+ // leaks. Storing IDs in a list would make deletion slow, and setting entries
+ // in the list to null would cause memory leaks.
+ final Map<int, void Function(T)> _observers = {};
- Observable(
- this._value, {
- Future<T>? loadValue,
- List<void Function(T)?>? observers,
- }) {
+ Observable(this._value, {List<void Function(T)>? observers}) {
if (observers != null) {
- _observers.addAll(observers);
+ _observers.addAll(observers.asMap());
}
+ }
- if (loadValue != null) {
- loadValue.then((value) => this.value = value);
- }
+ factory Observable.fromFuture({
+ required T initialValue,
+ required Future<T> future,
+ List<void Function(T)>? observers,
+ }) {
+ final self = Observable(initialValue, observers: observers);
+ future.then((value) => self.value = value);
+ return self;
}
T get value => _value;
@@ -88,21 +114,19 @@ class Observable<T> {
_value = value;
- for (var observer in _observers) {
- if (observer != null) {
- observer(_value);
- }
+ for (var observer in _observers.values) {
+ observer(_value);
}
}
int subscribe(void Function(T) onChange) {
- final id = _observers.length;
- _observers.add(onChange);
+ final id = _nextId++;
+ _observers[id] = onChange;
return id;
}
void unsubscribe(int subscriberId) {
- _observers[subscriberId] = null;
+ _observers.remove(subscriberId);
}
}