-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add docs, fix some issues and improve some APIS (#4)
Co-authored-by: Bruno Ferrari <[email protected]>
- Loading branch information
Showing
22 changed files
with
327 additions
and
121 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
/// Used to notify unexpected errors | ||
class StockError extends Error { | ||
final String message; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,86 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:flutter/widgets.dart'; | ||
import 'package:stock/fetcher.dart'; | ||
import 'package:stock/src/key_value.dart'; | ||
import 'package:stock/src/source_of_truth_impl.dart'; | ||
import 'package:stock/store.dart'; | ||
import 'package:stock/store_extensions.dart'; | ||
|
||
class SourceOfTruth<Key, Output> { | ||
Stream<Output?> Function(Key key) reader; | ||
Future<void> Function(Key key, Output? output) writer; | ||
/// | ||
/// [SourceOfTruth], as its name implies, is the persistence API which [Store] uses to serve values to | ||
/// the collectors. If provided, [Store] will only return values received from [SourceOfTruth] back | ||
/// to the collectors. | ||
/// | ||
/// In other words, values coming from the [Fetcher] will always be sent to the [SourceOfTruth] | ||
/// and will be read back via [reader] to then be returned to the collector. | ||
/// | ||
/// This round-trip ensures the data is consistent across the application in case the [Fetcher] does | ||
/// not return all fields or returns a different class type than the app uses. It is particularly | ||
/// useful if your application has a local observable database which is directly modified by the app, | ||
/// as Store can observe these changes and update the collectors even before value is synced to the | ||
/// backend. | ||
/// | ||
/// [SourceOfTruth] takes care of making any source (no matter if it has flowing reads or not) into | ||
/// a common flowing API. | ||
/// | ||
/// A source of truth is usually backed by local storage. Its purpose is to eliminate the need | ||
/// for waiting on a network update before local modifications are available (via [Store.stream]). | ||
/// | ||
/// For maximal simplicity, [writer]'s record type ([T]] and [reader]'s record type | ||
/// ([T]) are identical. However, sometimes reading one type of objects from network and | ||
/// transforming them to another type when placing them in local storage is needed. | ||
/// For this case you can use the [mapTo] and [mapToUsingMapper] extensions. | ||
/// | ||
abstract class SourceOfTruth<Key, T> { | ||
/// Creates a source of truth that is accessed via [reader] and [writer]. | ||
/// | ||
/// The [reader] function is used to read records from the source of truth | ||
/// The [writer] function is used to write records to the source of truth | ||
factory SourceOfTruth({ | ||
required Stream<T?> Function(Key key) reader, | ||
required Future<void> Function(Key key, T? output) writer, | ||
}) => | ||
SourceOfTruthImpl(reader, writer); | ||
|
||
SourceOfTruth({required this.reader, required this.writer}); | ||
/// Used by [Store] to read records from the source of truth for the given [key]. | ||
Stream<T?> reader(Key key); | ||
|
||
/// Used by [Store] to write records **coming in from the fetcher (network)** to the source of | ||
/// truth. | ||
/// | ||
/// **Note:** [Store] currently does not support updating the source of truth with local user | ||
/// updates (i.e writing record of type [T]). However, any changes in the local database | ||
/// will still be visible via [Store.stream] APIs as long as you are using a local storage that | ||
/// supports observability (e.g. Floor, Drift, Realm). | ||
Future<void> write(Key key, T? value); | ||
} | ||
|
||
// A memory cache implementation of a [SourceOfTruth], which stores the latest value and notify new ones. | ||
class CachedSourceOfTruth<Key, T> implements SourceOfTruth<Key, T> { | ||
final _streamController = StreamController<T?>.broadcast(); | ||
final _streamController = StreamController<KeyValue<Key, T?>>.broadcast(); | ||
|
||
late Map<Key, T?> _cachedValues; | ||
|
||
@override | ||
late Stream<T?> Function(Key key) reader; | ||
@override | ||
late Future<void> Function(Key key, T? output) writer; | ||
|
||
CachedSourceOfTruth([Map<Key, T?>? cachedValues]) { | ||
_cachedValues = {if (cachedValues != null) ...cachedValues}; | ||
reader = generateReader; | ||
writer = generateWriter; | ||
} | ||
|
||
@protected | ||
@visibleForTesting | ||
void setCachedValue(Key key, T? t) => _cachedValues[key] = t; | ||
void setCachedValue(Key key, T? value) => _cachedValues[key] = value; | ||
|
||
@protected | ||
Stream<T?> generateReader(Key key) async* { | ||
@override | ||
Stream<T?> reader(Key key) async* { | ||
yield _cachedValues[key]; | ||
yield* _streamController.stream; | ||
yield* _streamController.stream | ||
.where((event) => event.key == key) | ||
.map((event) => event.value); | ||
} | ||
|
||
@protected | ||
Future<void> generateWriter(Key key, T? value) async { | ||
@override | ||
Future<void> write(Key key, T? value) async { | ||
setCachedValue(key, value); | ||
_streamController.add(value); | ||
_streamController.add(KeyValue(key, value)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
class KeyValue<Key, Value> { | ||
final Key key; | ||
final Value value; | ||
|
||
KeyValue(this.key, this.value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import 'package:stock/source_of_truth.dart'; | ||
|
||
class SourceOfTruthImpl<Key, T> implements SourceOfTruth<Key, T> { | ||
final Stream<T?> Function(Key key) _reader; | ||
final Future<void> Function(Key key, T? output) _writer; | ||
|
||
SourceOfTruthImpl(this._reader, this._writer); | ||
|
||
@override | ||
Stream<T?> reader(Key key) => _reader(key); | ||
|
||
@override | ||
Future<void> write(Key key, T? value) => _writer(key, value); | ||
} | ||
|
||
class WriteWrappedSourceOfTruth<Key, T> extends CachedSourceOfTruth<Key, T> { | ||
final SourceOfTruth<Key, T>? _realSourceOfTruth; | ||
|
||
WriteWrappedSourceOfTruth(this._realSourceOfTruth); | ||
|
||
@override | ||
Future<void> write(Key key, T? value) async { | ||
await _realSourceOfTruth?.write(key, value); | ||
await super.write(key, value); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,23 @@ | ||
import 'source_of_truth.dart'; | ||
import 'package:stock/type_mapper.dart'; | ||
|
||
abstract class StoreConverter<T0, T1> { | ||
T1 fromT0(T0 t0); | ||
import 'source_of_truth.dart'; | ||
|
||
T0 fromT1(T1 t1); | ||
} | ||
extension SourceOfTruthExtensions<Key, Input> on SourceOfTruth<Key, Input> { | ||
/// Transforms a [SourceOfTruth] of [Key], [Input] into a [SourceOfTruth] of [Key], [Output]. | ||
SourceOfTruth<Key, Output> mapToUsingMapper<Output>( | ||
StoreTypeMapper<Input, Output> mapper, | ||
) => | ||
mapTo(mapper.fromInput, mapper.fromOutput); | ||
|
||
extension SourceOfTruthExtensions<Key, Output1> on SourceOfTruth<Key, Output1> { | ||
SourceOfTruth<Key, Output2> mapTo<Output2>( | ||
StoreConverter<Output1, Output2> converter, | ||
/// Transforms a [SourceOfTruth] of [Key], [Input] into a [SourceOfTruth] of [Key], [Output]. | ||
SourceOfTruth<Key, Output> mapTo<Output>( | ||
Output Function(Input) fromInput, | ||
Input Function(Output) fromOutput, | ||
) => | ||
SourceOfTruth<Key, Output2>( | ||
reader: (key) => reader(key) | ||
.map((value) => value == null ? null : converter.fromT0(value)), | ||
SourceOfTruth<Key, Output>( | ||
reader: (key) => | ||
reader(key).map((value) => value == null ? null : fromInput(value)), | ||
writer: (key, value) => | ||
writer(key, value == null ? null : converter.fromT1(value)), | ||
write(key, value == null ? null : fromOutput(value)), | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/// Converts the [Input] type to the [Output] and vice versa. | ||
/// Used to transform DB entity to network entity and vice versa. | ||
abstract class StoreTypeMapper<Input, Output> { | ||
Output fromInput(Input value); | ||
|
||
Input fromOutput(Output value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.