Skip to content

Commit ed789eb

Browse files
committed
Add paging bloc as alternative to paging package
1 parent 15709a2 commit ed789eb

File tree

13 files changed

+401
-47
lines changed

13 files changed

+401
-47
lines changed

app/lib/blocs/sourced_paging.dart

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter_bloc/flutter_bloc.dart';
4+
import 'package:dart_mappable/dart_mappable.dart';
5+
import 'package:equatable/equatable.dart';
6+
import 'package:flow/cubits/flow.dart';
7+
import 'package:flow_api/models/model.dart';
8+
import 'package:flow_api/services/source.dart';
9+
10+
part 'sourced_paging_event.dart';
11+
part 'sourced_paging_state.dart';
12+
13+
part 'sourced_paging.mapper.dart';
14+
15+
class SourcedPagingBloc<T>
16+
extends Bloc<SourcedPagingEvent, SourcedPagingState<T>> {
17+
final FlowCubit cubit;
18+
final int pageSize;
19+
final Future<List<T>?> Function(
20+
String source,
21+
SourceService service,
22+
int offset,
23+
int limit,
24+
) _fetch;
25+
26+
SourcedPagingBloc(
27+
{required this.cubit,
28+
required Future<List<T>?> Function(String, SourceService, int, int) fetch,
29+
this.pageSize = 50})
30+
: _fetch = fetch,
31+
super(const SourcedPagingInitial()) {
32+
on<SourcedPagingFetched<T>>(_onFetched);
33+
}
34+
35+
Future<void> _onFetched(
36+
SourcedPagingFetched<T> event,
37+
Emitter<SourcedPagingState<T>> emit,
38+
) async {
39+
final state = this.state;
40+
if (state.hasReachedMax) return;
41+
42+
try {
43+
final currentPageKey = state.currentPageKey ??
44+
SourcedModel(cubit.getCurrentSources().first, 0);
45+
final previousItems = state is SourcedPagingSuccess<T> ? state.items : [];
46+
47+
final fetchedItems = (await _fetch(
48+
currentPageKey.source,
49+
cubit.getService(currentPageKey.source),
50+
currentPageKey.model * pageSize,
51+
pageSize) ??
52+
<T>[])
53+
.map((e) => SourcedModel(currentPageKey.source, e))
54+
.toList();
55+
56+
final sources = cubit.getCurrentSources();
57+
final currentSourceIndex = sources.indexOf(currentPageKey.source);
58+
final keepSource = fetchedItems.length >= pageSize;
59+
final isLastSource = currentSourceIndex >= sources.length - 1;
60+
61+
if (isLastSource && !keepSource) {
62+
emit(SourcedPagingSuccess(
63+
currentPageKey: currentPageKey,
64+
items: [...previousItems, ...fetchedItems],
65+
hasReachedMax: true,
66+
));
67+
} else if (keepSource) {
68+
emit(SourcedPagingSuccess(
69+
items: [...previousItems, ...fetchedItems],
70+
currentPageKey: SourcedModel(
71+
currentPageKey.source,
72+
currentPageKey.model + 1,
73+
),
74+
));
75+
} else {
76+
final nextSource = sources[currentSourceIndex + 1];
77+
emit(SourcedPagingSuccess(
78+
items: [...previousItems, ...fetchedItems],
79+
currentPageKey: SourcedModel(nextSource, 0),
80+
));
81+
}
82+
} catch (e) {
83+
emit(SourcedPagingFailure(e));
84+
}
85+
}
86+
87+
void refresh() => add(SourcedPagingRefresh());
88+
void fetch() => add(SourcedPagingFetched<T>());
89+
}

app/lib/blocs/sourced_paging.mapper.dart

Lines changed: 153 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
part of 'sourced_paging.dart';
2+
3+
abstract class SourcedPagingEvent extends Equatable {
4+
@override
5+
List<Object> get props => [];
6+
}
7+
8+
class SourcedPagingFetched<T> extends SourcedPagingEvent {
9+
SourcedPagingFetched();
10+
11+
@override
12+
List<Object> get props => [];
13+
}
14+
15+
class SourcedPagingRefresh<T> extends SourcedPagingEvent {
16+
SourcedPagingRefresh();
17+
18+
@override
19+
List<Object> get props => [];
20+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
part of 'sourced_paging.dart';
2+
3+
enum SourcedPagingStatus { initial, success, failure }
4+
5+
sealed class SourcedPagingState<T> {
6+
const SourcedPagingState();
7+
8+
SourcedModel<int>? get currentPageKey => null;
9+
bool get hasReachedMax => false;
10+
List<SourcedModel<T>> get items => const [];
11+
}
12+
13+
final class SourcedPagingInitial<T> extends SourcedPagingState<T> {
14+
const SourcedPagingInitial();
15+
}
16+
17+
@MappableClass(
18+
generateMethods: GenerateMethods.copy |
19+
GenerateMethods.stringify |
20+
GenerateMethods.equals)
21+
final class SourcedPagingSuccess<T> extends SourcedPagingState<T>
22+
with SourcedPagingSuccessMappable<T> {
23+
@override
24+
final List<SourcedModel<T>> items;
25+
@override
26+
final SourcedModel<int> currentPageKey;
27+
@override
28+
final bool hasReachedMax;
29+
30+
const SourcedPagingSuccess({
31+
this.items = const [],
32+
required this.currentPageKey,
33+
this.hasReachedMax = false,
34+
});
35+
}
36+
37+
final class SourcedPagingFailure<T> extends SourcedPagingState<T> {
38+
final Object error;
39+
@override
40+
final List<SourcedModel<T>> items;
41+
42+
const SourcedPagingFailure(this.error, {this.items = const []});
43+
44+
@override
45+
bool get hasReachedMax => true;
46+
}

0 commit comments

Comments
 (0)