Skip to content

Commit

Permalink
feat: remove error generic type
Browse files Browse the repository at this point in the history
BREAKING CHANGE: user must remove E type from their code
  • Loading branch information
vaetas committed Dec 20, 2021
1 parent e703bce commit b93268d
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 65 deletions.
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ background. Inspired by [Revolut's RxData library](https://github.com/revolut-mo

```shell
flutter pub add rxdata
flutter pub add flutter_bloc
```

## Usage

First, define `DataDelagete` object and specify `Data` type and `Exception` type.
First, define `DataDelagete` object and specify `Data` type.

```dart
final dataDelegate = DataDelegate<ApiResponse, Exception>(
fromNetwork: () async* {
// [fromNetwork] can yield multiple values before closing. You can sequentially fetch data and
// and yield them step by step.
final response = await getRequest();
yield response;
},
fromStorage: () async {
Expand All @@ -38,19 +38,21 @@ You can also use `DataListener` and `DataConsumer`.
class ExampleWidget extends StatelessWidget {
const ExampleWidget({Key? key, required this.dataDelegate}) : super(key: key);
final DataDelegate<ApiResponse, Exception> dataDelegate;
final DataDelegate<ApiResponse> dataDelegate;
@override
Widget build(BuildContext context) {
return Scaffold(
body: DataBuilder<ApiResponse, Exception>(
body: DataBuilder<ApiResponse>(
bloc: dataDelegate,
builder: (context, state) {
if (state.hasValue) {
return Text(state.value!.toString());
} else {
return Text('No data');
}
return Column(
children: [
if (state.isLoading) const CircularProgressIndicator(),
if (state.hasError) Text(state.error!.toString()),
if (state.hasValue) Text(state.value!.toString()),
],
);
},
),
);
Expand All @@ -68,4 +70,5 @@ class ExampleWidget extends StatelessWidget {
You can then call `dataDelegate.reload()` to fetch data again. Delegate will handle caching by
itself, provided that you specified your callbacks.

See `example` project for full usage.
See [example project](https://github.com/vaetas/rxdata/blob/main/example/lib/main.dart) for full
usage.
7 changes: 5 additions & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class HomeScreen extends StatefulWidget {
}

class _HomeScreenState extends State<HomeScreen> {
late final DataDelegate<ApiResponse, Object> dataDelegate;
late final DataDelegate<ApiResponse> dataDelegate;

bool wasErrorThrown = false;

Expand Down Expand Up @@ -79,6 +79,9 @@ class _HomeScreenState extends State<HomeScreen> {
);
},
fromStorage: () async {
// Uncomment line below to simulate storage IO failure.
// throw Exception('Failed to load from storage');

print('[_HomeScreenState.initState] fromStorage');
final box = await Hive.openBox<String>('storage');
final data = box.get('data');
Expand All @@ -103,7 +106,7 @@ class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: DataBuilder<ApiResponse, Object>(
body: DataBuilder<ApiResponse>(
bloc: dataDelegate,
builder: (context, state) {
return CustomScrollView(
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.2.2"
version: "0.2.1"
sky_engine:
dependency: transitive
description: flutter
Expand Down
28 changes: 14 additions & 14 deletions lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rxdata/rxdata.dart';

/// Wrapper around [BlocBuilder] for shorter generics definition.
class DataBuilder<V, E> extends BlocBuilder<DataDelegate<V, E>, Data<V, E>> {
class DataBuilder<V> extends BlocBuilder<DataDelegate<V>, Data<V>> {
const DataBuilder({
Key? key,
required BlocWidgetBuilder<Data<V, E>> builder,
DataDelegate<V, E>? bloc,
BlocBuilderCondition<Data<V, E>>? buildWhen,
required BlocWidgetBuilder<Data<V>> builder,
DataDelegate<V>? bloc,
BlocBuilderCondition<Data<V>>? buildWhen,
}) : super(key: key, bloc: bloc, buildWhen: buildWhen, builder: builder);
}

/// Wrapper around [BlocListener] for shorter generics definition.
class DataListener<V, E> extends BlocListener<DataDelegate<V, E>, Data<V, E>> {
class DataListener<V> extends BlocListener<DataDelegate<V>, Data<V>> {
const DataListener({
Key? key,
required BlocWidgetListener<Data<V, E>> listener,
DataDelegate<V, E>? bloc,
BlocListenerCondition<Data<V, E>>? listenWhen,
required BlocWidgetListener<Data<V>> listener,
DataDelegate<V>? bloc,
BlocListenerCondition<Data<V>>? listenWhen,
Widget? child,
}) : super(
key: key,
Expand All @@ -30,14 +30,14 @@ class DataListener<V, E> extends BlocListener<DataDelegate<V, E>, Data<V, E>> {
}

/// Wrapper around [BlocConsumer] for shorter generics definition.
class DataConsumer<V, E> extends BlocConsumer<DataDelegate<V, E>, Data<V, E>> {
class DataConsumer<V> extends BlocConsumer<DataDelegate<V>, Data<V>> {
const DataConsumer({
Key? key,
required BlocWidgetListener<Data<V, E>> listener,
required BlocWidgetBuilder<Data<V, E>> builder,
DataDelegate<V, E>? bloc,
BlocListenerCondition<Data<V, E>>? listenWhen,
BlocBuilderCondition<Data<V, E>>? buildWhen,
required BlocWidgetListener<Data<V>> listener,
required BlocWidgetBuilder<Data<V>> builder,
DataDelegate<V>? bloc,
BlocListenerCondition<Data<V>>? listenWhen,
BlocBuilderCondition<Data<V>>? buildWhen,
}) : super(
key: key,
listener: listener,
Expand Down
11 changes: 4 additions & 7 deletions lib/src/data.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
/// Wrapper around your data of type [V].
///
/// [E] is a type for a possible exceptions. You can either provide your own
/// exception class/enum or use [Object].
class Data<V, E> {
class Data<V> {
const Data({
this.value,
this.error,
Expand All @@ -13,7 +10,7 @@ class Data<V, E> {
final V? value;

/// Current error
final E? error;
final Object? error;

/// Whether you can except the data to refresh soon. Remember that you can
/// have [isLoading] set to true and still have some value and/or error at the
Expand All @@ -27,9 +24,9 @@ class Data<V, E> {
bool get hasError => error != null;

/// Create copy of this [Data] object with new params.
Data<V, E> copyWith({
Data<V> copyWith({
V? value,
E? error,
Object? error,
bool? isLoading,
}) {
return Data(
Expand Down
58 changes: 28 additions & 30 deletions lib/src/delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rxdata/src/data.dart';

/// Delegates fetching and caching behavior of specified [Data] object.
class DataDelegate<V, E> extends Cubit<Data<V, E>> {
class DataDelegate<V> extends Cubit<Data<V>> {
DataDelegate({
required this.fromNetwork,
this.fromMemory,
this.toMemory,
this.fromStorage,
this.toStorage,
this.onClearCache,
}) : super(Data<V, E>(isLoading: true)) {
}) : super(Data<V>(isLoading: true)) {
_init();
}

Expand All @@ -37,22 +37,29 @@ class DataDelegate<V, E> extends Cubit<Data<V, E>> {
bool _locked = false;

Future<void> _init() async {
final memoryValue = fromMemory?.call();
if (memoryValue != null) {
emit(Data(value: memoryValue));
} else {
await _loadFromStorage();
try {
final memoryValue = fromMemory?.call();
if (memoryValue != null) {
emit(Data(value: memoryValue));
} else {
await _loadFromStorage();
}
} catch (e, s) {
onError(e, s);
}

await _fetch();
}

Future<void> _loadFromStorage() async {
final value = await fromStorage?.call();

if (value != null) {
emit(state.copyWith(value: value));
toMemory?.call(value);
try {
final value = await fromStorage?.call();
if (value != null) {
emit(state.copyWith(value: value));
toMemory?.call(value);
}
} catch (e, s) {
onError(e, s);
}
}

Expand All @@ -74,24 +81,8 @@ class DataDelegate<V, E> extends Cubit<Data<V, E>> {
toMemory?.call(event);
await toStorage?.call(event);
}
} catch (e) {
if (e is E) {
// .copyWith throws a type error once in a time:
// type '_Exception' is not a subtype of type 'Null' of 'error'

// emit(state.copyWith(error: e as E));
emit(
Data(
value: state.value,
error: e as E,
),
);
} else {
throw ArgumentError(
'Exception ${e.runtimeType} is not subtype of E in Data<V, E>.'
'Ensure you only throw exceptions of type E.',
);
}
} catch (e, s) {
onError(e, s);
} finally {
emit(state.copyWith(isLoading: false));
}
Expand Down Expand Up @@ -121,6 +112,13 @@ class DataDelegate<V, E> extends Cubit<Data<V, E>> {
Future<void> clearCache() async {
await onClearCache?.call();
}

@override
void onError(Object error, StackTrace stackTrace) {
print('[DataDelegate.onError] ERROR: $error');
emit(state.copyWith(error: error));
super.onError(error, stackTrace);
}
}

typedef _FromNetwork<V> = Stream<V> Function();
Expand Down

0 comments on commit b93268d

Please sign in to comment.