Skip to content

Commit 5351002

Browse files
authored
refactor! rename JsonCacheMem.mem to JsonCacheMem.ext
1 parent 1f91e76 commit 5351002

11 files changed

+465
-68
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- JsonCacheHollow: literally a "hollow" implementation of the JsonCache
13+
interfaces. Indeed, there is no implementation under its methods. It is aimed
14+
to be used as a placeholder whenever there is no need for a level2 cache.
15+
16+
### Changed
17+
18+
- rename JsonCacheMem.mem constructor to JsonCacheMem.ext — **BREAKING CHANGE**.
19+
- improvements in several unit tests.
20+
- general improvements in many doc comments.
21+
22+
### Fixed
23+
24+
- The internal copying logic of the JsonCacheMem.init contructor.
25+
1026
## [0.2.1] - 2021-08-23
1127

1228
### Added

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ It can also be thought of as a layer on top of Flutter's local storage packages
4444
like the [sharable_preferences](https://pub.dev/packages/shared_preferences) and
4545
[localstorage](https://pub.dev/packages/localstorage) packages.
4646

47-
The ultimate goal of this package is to unify Flutter's main local caching packages
48-
into an elegant caching API.
47+
The ultimate goal of this package is to unify Flutter's main local caching
48+
packages into an elegant caching API.
4949

5050
**Why Json?**
5151

@@ -126,6 +126,18 @@ object. For example:
126126
127127
```
128128

129+
In addition, `JsonCacheMem` has the `JsonCacheMem.init` constructor whose
130+
purpose is the initialize the cache. Data is deep copied from the initialization
131+
buffer to its internal in-memory cache and to its level2 cache as well.
132+
133+
```dart
134+
135+
final LocalStorage storage = LocalStorage('my_data');
136+
final Map<String, Map<String, dynamic>?> initData = await fetchInfo();
137+
final JsonCacheMem jsonCache = JsonCacheMem.init(initData, level2:JsonCacheLocalStorage(storage));
138+
139+
```
140+
129141
### JsonCachePrefs
130142

131143
[JsonCachePrefs](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCachePrefs-class.html)
@@ -143,7 +155,7 @@ is an implementation on top of the
143155

144156
[JsonCacheLocalStorage](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheLocalStorage-class.html)
145157
is an implementation on top of the
146-
[shared_preferences](https://pub.dev/packages/shared_preferences) package.
158+
[localstorage](https://pub.dev/packages/localstorage)
147159

148160
```dart
149161

lib/json_cache.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ library json_cache;
33

44
export 'src/json_cache.dart';
55
export 'src/json_cache_fake.dart';
6+
export 'src/json_cache_hollow.dart';
67
export 'src/json_cache_local_storage.dart';
78
export 'src/json_cache_mem.dart';
89
export 'src/json_cache_prefs.dart';

lib/src/json_cache_fake.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import 'package:json_cache/json_cache.dart';
22

3-
/// In-memory cache. It is intended for unit testing and prototyping.
3+
/// In-memory cache without synchronization.
4+
///
5+
/// It is intended for unit testing and prototyping.
46
///
57
/// **Warning**: do not use it in production code. It is not thread safe.
68
class JsonCacheFake implements JsonCache {
79
/// It will share a static memory with other instances.
810
JsonCacheFake() : this.mem(_shrMem);
911

12+
/// Initializes the cache with [init] — by performing a deep copy.
13+
JsonCacheFake.init(Map<String, Map<String, dynamic>?> init)
14+
: this.mem(Map<String, Map<String, dynamic>?>.of(init));
15+
1016
/// Cache with custom memory.
1117
JsonCacheFake.mem(this._memory);
1218

lib/src/json_cache_hollow.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'json_cache.dart';
2+
3+
/// Hollow (empty) [JsonCache] — It is intended to serve as a placeholder for
4+
/// [JsonCache] instances.
5+
///
6+
/// There will always be at most one [JsonCacheHollow] object in memory during
7+
/// program execution. It doesn't matter how many times someone instantiates
8+
/// objects using `const JsonCacheHollow()`.
9+
///
10+
/// > hollow
11+
/// >
12+
/// > having a hole or empty space inside:
13+
/// > - a hollow tube
14+
/// > - Hollow blocks are used because they are lighter
15+
/// > - a hollow log
16+
/// > — [Cambridge
17+
/// Dictionary](https://dictionary.cambridge.org/dictionary/english/hollow)
18+
class JsonCacheHollow implements JsonCache {
19+
/// This const constructor ensures that there will be only one
20+
/// [JsonCacheHollow] instance throughout the program.
21+
const JsonCacheHollow();
22+
23+
/// Does nothing.
24+
@override
25+
Future<void> clear() async {}
26+
27+
/// Does nothing.
28+
@override
29+
Future<void> refresh(String key, Map<String, dynamic> value) async {}
30+
31+
/// Does nothing.
32+
@override
33+
Future<void> remove(String key) async {}
34+
35+
/// Does nothing.
36+
@override
37+
Future<Map<String, dynamic>?> value(String key) async {}
38+
}

lib/src/json_cache_mem.dart

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
import 'dart:async';
2+
13
import 'package:mutex/mutex.dart';
24

35
import 'json_cache.dart';
6+
import 'json_cache_hollow.dart';
7+
8+
// ignore_for_file: prefer_void_to_null
9+
10+
/// [JsonCacheMem.init] initialization error callback.
11+
typedef OnInitError = FutureOr<Null> Function(Object, StackTrace);
412

513
/// Thread-safe in-memory [JsonCache] decorator.
614
///
@@ -9,17 +17,70 @@ import 'json_cache.dart';
917
/// TODO: limit the maximum number of cache entries via "size" parameter in
1018
/// constructors.
1119
///
12-
/// It encapsulates a slower chache but keeps its own data in-memory.
20+
/// It encapsulates a slower cache and keeps its own data in-memory.
1321
class JsonCacheMem implements JsonCache {
14-
/// Encapsulates a slower [level2] cache and utilizes a static memory that
15-
/// will be shared (without race conditions) among all [JsonCacheMem] objects
16-
/// that have been instantiated with this constructor.
17-
JsonCacheMem(JsonCache level2) : this.mem(level2, _shrMem, _shrMutex);
18-
19-
/// Cache with custom memory and an optional custom mutex.
20-
JsonCacheMem.mem(JsonCache level2, Map<String, Map<String, dynamic>?> mem,
21-
[ReadWriteMutex? mutex])
22-
: _level2 = level2,
22+
/// In-memory level1 cache with an optional level2 instance.
23+
///
24+
/// ATTENTION: if you do not pass an object to the parameter [level2], the
25+
/// data will remain cached in-memory only; that is, no data will be persited
26+
/// to the user's device's local storage area. Indeed, not persisting data on
27+
/// the user's device might be the desired behavior if you are at the
28+
/// prototyping or mocking phase. However, its unlikely to be the right
29+
/// behavior in production code.
30+
JsonCacheMem([JsonCache? level2])
31+
: this.ext(_shrMem, level2: level2, mutex: _shrMutex);
32+
33+
/// Cache with initial data.
34+
///
35+
/// Besides copying data from [init] to its internal shared memory, it
36+
/// encapsulates a [level2] cache that is supposed to persist data to the
37+
/// user's device's local storage area.
38+
///
39+
/// It also provides a type of transaction guarantee whereby, if an error
40+
/// occurs while copying [init] to the cache, it tries to revert the cached
41+
/// data to its previous state before rethrowing the exception. Finally, after
42+
/// reverting the cached data, it invokes [onInitError].
43+
JsonCacheMem.init(
44+
Map<String, Map<String, dynamic>?> init, {
45+
JsonCache? level2,
46+
OnInitError? onInitError,
47+
}) : _level2 = level2 ?? const JsonCacheHollow(),
48+
_memory = _shrMem,
49+
_mutex = _shrMutex {
50+
final copy = Map<String, Map<String, dynamic>?>.of(init);
51+
final initFut = _mutex.protectWrite(() async {
52+
final writes = <String>[]; // the list of written keys.
53+
for (final entry in copy.entries) {
54+
final key = entry.key;
55+
final value = entry.value;
56+
if (value != null) {
57+
writes.add(key);
58+
try {
59+
await _level2.refresh(key, value);
60+
_memory[key] = value;
61+
} catch (error) {
62+
for (final write in writes) {
63+
await _level2.remove(write);
64+
_memory.remove(write);
65+
}
66+
rethrow;
67+
}
68+
}
69+
}
70+
});
71+
if (onInitError != null) {
72+
initFut.onError(onInitError);
73+
}
74+
}
75+
76+
/// Cache with an external memory and an optional custom mutex.
77+
///
78+
/// **Note**: the memory [mem] will **not** be copied deeply.
79+
JsonCacheMem.ext(
80+
Map<String, Map<String, dynamic>?> mem, {
81+
JsonCache? level2,
82+
ReadWriteMutex? mutex,
83+
}) : _level2 = level2 ?? const JsonCacheHollow(),
2384
_memory = mem,
2485
_mutex = mutex ?? ReadWriteMutex();
2586

@@ -80,11 +141,16 @@ class JsonCacheMem implements JsonCache {
80141
@override
81142
Future<Map<String, dynamic>?> value(String key) async {
82143
return _mutex.protectRead(() async {
83-
if (!_memory.containsKey(key)) {
84-
_memory[key] = await _level2.value(key);
144+
final cachedL1 = _memory[key];
145+
if (cachedL1 != null) return Map<String, dynamic>.of(cachedL1);
146+
147+
final cachedL2 = await _level2.value(key);
148+
if (cachedL2 != null) {
149+
_memory[key] = cachedL2;
150+
return Map<String, dynamic>.of(cachedL2);
85151
}
86-
final cached = _memory[key];
87-
return cached == null ? cached : Map<String, dynamic>.of(cached);
152+
153+
return null;
88154
});
89155
}
90156
}

pubspec.lock

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ packages:
77
name: async
88
url: "https://pub.dartlang.org"
99
source: hosted
10-
version: "2.6.1"
10+
version: "2.8.1"
1111
boolean_selector:
1212
dependency: transitive
1313
description:
@@ -28,7 +28,7 @@ packages:
2828
name: charcode
2929
url: "https://pub.dartlang.org"
3030
source: hosted
31-
version: "1.2.0"
31+
version: "1.3.1"
3232
clock:
3333
dependency: transitive
3434
description:
@@ -43,6 +43,13 @@ packages:
4343
url: "https://pub.dartlang.org"
4444
source: hosted
4545
version: "1.15.0"
46+
cross_local_storage:
47+
dependency: "direct main"
48+
description:
49+
name: cross_local_storage
50+
url: "https://pub.dartlang.org"
51+
source: hosted
52+
version: "2.0.1"
4653
fake_async:
4754
dependency: transitive
4855
description:
@@ -113,7 +120,7 @@ packages:
113120
name: meta
114121
url: "https://pub.dartlang.org"
115122
source: hosted
116-
version: "1.3.0"
123+
version: "1.7.0"
117124
mutex:
118125
dependency: "direct main"
119126
description:
@@ -272,7 +279,7 @@ packages:
272279
name: test_api
273280
url: "https://pub.dartlang.org"
274281
source: hosted
275-
version: "0.3.0"
282+
version: "0.4.2"
276283
typed_data:
277284
dependency: transitive
278285
description:

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ environment:
99
flutter: ">=1.17.0"
1010

1111
dependencies:
12+
cross_local_storage: ^2.0.1
1213
flutter:
1314
sdk: flutter
1415
localstorage: ^4.0.0+1

0 commit comments

Comments
 (0)