Skip to content

Commit 7168e7b

Browse files
authored
feat: added page view support (#51)
1 parent 484162c commit 7168e7b

File tree

6 files changed

+172
-14
lines changed

6 files changed

+172
-14
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ import 'package:firebase_pagination/firebase_pagination.dart';
4444
```dart
4545
FirestorePagination(
4646
query: FirebaseFirestore.instance.collection('scores').orderBy('score'),
47-
itemBuilder: (context, documentSnapshot, index) {
48-
final data = documentSnapshot.data() as Map<String, dynamic>;
47+
itemBuilder: (context, docs, index) {
48+
final data = docs[index].data() as Map<String, dynamic>;
4949
5050
// Do something cool with the data
5151
},
@@ -58,8 +58,8 @@ FirestorePagination(
5858
RealtimeDBPagination(
5959
query: FirebaseDatabase.instance.ref().child('scores').orderByChild('score'),
6060
orderBy: 'score',
61-
itemBuilder: (context, dataSnapshot, index) {
62-
final data = dataSnapshot.value as Map<String, dynamic>;
61+
itemBuilder: (context, dataNodes, index) {
62+
final data = dataNodes[index].value as Map<String, dynamic>;
6363
6464
// Do something cool with the data
6565
},
@@ -96,6 +96,7 @@ RealtimeDBPagination(
9696
| `isLive` | **Whether to fetch newly added items as they are added to Database.** | _bool_ | `false` |
9797
| `gridDelegate` | **The delegate to use for the GridView.** | _SliverGridDelegate_ | `crossAxisCount: 2` |
9898
| `wrapOptions` | **The Wrap widget properties to use.** | _WrapOptions_ | `WrapOptions()` |
99+
| `pageOptions` | **The PageView widget properties to use.** | _PageOptions_ | `PageOptions()` |
99100
| `onEmpty` | **The widget to use when data is empty.** | _Widget_ | `EmptyScreen()` |
100101
| `bottomLoader` | **The widget to use when more data is loading.** | _Widget_ | `BottomLoader()` |
101102
| `initialLoader` | **The widget to use when data is loading initially.** | _Widget_ | `InitialLoader()` |
@@ -104,7 +105,8 @@ RealtimeDBPagination(
104105
| `shrinkWrap` | **Should the ScrollView be shrink-wrapped.** | _bool_ | `false` |
105106
| `physics` | **The scroll behavior to use for the ScrollView.** | _ScrollPhysics_ | - |
106107
| `padding` | **The padding to use for the ScrollView.** | _EdgeInsetsGeometry_ | - |
107-
| `controller` | **The controller to use for the ScrollView.** | _ScrollController_ | - |
108+
| `controller` | **The controller to use for the ScrollView.** | _ScrollController_ | ScrollController() |
109+
| `pageController` | **The controller to use for the PageView.** | _PageController_ | PageController() |
108110
| `descending` | **Whether the data should be fetched in descending order or not. Only works for RealtimeDBPagination** | _bool_ | `false` |
109111

110112
### If you liked the package, then please give it a [Like 👍🏼][package] and [Star ⭐][repository]

lib/src/firestore_pagination.dart

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
// Dart Packages
2+
import 'dart:async';
3+
14
// Flutter Packages
25
import 'package:flutter/material.dart';
36
import 'package:flutter/scheduler.dart';
47

5-
// Dart Packages
6-
import 'dart:async';
7-
88
// Firebase Packages
99
import 'package:cloud_firestore/cloud_firestore.dart';
1010

1111
// Data Models
12+
import 'models/page_options.dart';
1213
import 'models/view_type.dart';
1314
import 'models/wrap_options.dart';
1415

@@ -52,6 +53,7 @@ class FirestorePagination extends StatefulWidget {
5253
crossAxisCount: 2,
5354
),
5455
this.wrapOptions = const WrapOptions(),
56+
this.pageOptions = const PageOptions(),
5557
this.onEmpty = const EmptyScreen(),
5658
this.bottomLoader = const BottomLoader(),
5759
this.initialLoader = const InitialLoader(),
@@ -61,6 +63,7 @@ class FirestorePagination extends StatefulWidget {
6163
this.physics,
6264
this.padding,
6365
this.controller,
66+
this.pageController,
6467
});
6568

6669
/// The query to use to fetch data from Firestore.
@@ -108,6 +111,11 @@ class FirestorePagination extends StatefulWidget {
108111
/// Defaults to [WrapOptions].
109112
final WrapOptions wrapOptions;
110113

114+
/// The [PageView] properties to use.
115+
///
116+
/// Defaults to [PageOptions].
117+
final PageOptions pageOptions;
118+
111119
/// The widget to use when data is empty.
112120
///
113121
/// Defaults to [EmptyScreen].
@@ -143,6 +151,11 @@ class FirestorePagination extends StatefulWidget {
143151
/// Defaults to [ScrollController].
144152
final ScrollController? controller;
145153

154+
/// The page controller to use for the [PageView].
155+
///
156+
/// Defaults to [PageController].
157+
final PageController? pageController;
158+
146159
@override
147160
State<FirestorePagination> createState() => _FirestorePaginationState();
148161
}
@@ -164,6 +177,10 @@ class _FirestorePaginationState extends State<FirestorePagination> {
164177
late final ScrollController _controller =
165178
widget.controller ?? ScrollController();
166179

180+
/// [PageController] to listen to page changes and load more data.
181+
late final PageController _pageController =
182+
widget.pageController ?? PageController();
183+
167184
/// Whether initial data is loading.
168185
bool _isInitialLoading = true;
169186

@@ -228,7 +245,8 @@ class _FirestorePaginationState extends State<FirestorePagination> {
228245
// scroll to the bottom and load more data.
229246
if (_isInitialLoading || _isFetching || _isEnded) return;
230247
SchedulerBinding.instance.addPostFrameCallback((_) {
231-
if (_controller.position.maxScrollExtent <= 0) {
248+
if (_controller.hasClients &&
249+
_controller.position.maxScrollExtent <= 0) {
232250
_loadDocuments();
233251
}
234252
});
@@ -268,6 +286,7 @@ class _FirestorePaginationState extends State<FirestorePagination> {
268286
/// To handle scroll end event and load more data.
269287
void _scrollListener() {
270288
if (_isInitialLoading || _isFetching || _isEnded) return;
289+
if (!_controller.hasClients) return;
271290

272291
final position = _controller.position;
273292
if (position.pixels >= (position.maxScrollExtent - 50)) {
@@ -289,6 +308,7 @@ class _FirestorePaginationState extends State<FirestorePagination> {
289308
_controller
290309
..removeListener(_scrollListener)
291310
..dispose();
311+
_pageController.dispose();
292312
super.dispose();
293313
}
294314

@@ -307,12 +327,17 @@ class _FirestorePaginationState extends State<FirestorePagination> {
307327
bottomLoader: widget.bottomLoader,
308328
gridDelegate: widget.gridDelegate,
309329
wrapOptions: widget.wrapOptions,
330+
pageOptions: widget.pageOptions,
310331
scrollDirection: widget.scrollDirection,
311332
reverse: widget.reverse,
312333
controller: _controller,
334+
pageController: _pageController,
313335
shrinkWrap: widget.shrinkWrap,
314336
physics: widget.physics,
315337
padding: widget.padding,
338+
onPageChanged: (index) {
339+
if (index >= _docs.length - 1) _loadDocuments();
340+
},
316341
);
317342
}
318343
}

lib/src/models/page_options.dart

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Flutter Packages
2+
import 'package:flutter/gestures.dart';
3+
import 'package:flutter/material.dart';
4+
5+
// Data Models
6+
import 'view_type.dart';
7+
8+
/// The properties of the [PageView] widget in the [ViewType.page] view.
9+
class PageOptions {
10+
/// Creates a object that contains the properties of the [PageView] widget.
11+
const PageOptions({
12+
this.clipBehavior = Clip.hardEdge,
13+
this.pageSnapping = true,
14+
this.padEnds = true,
15+
this.scrollBehavior,
16+
this.allowImplicitScrolling = false,
17+
this.dragStartBehavior = DragStartBehavior.start,
18+
});
19+
20+
/// {@macro flutter.material.Material.clipBehavior}
21+
///
22+
/// Defaults to [Clip.hardEdge].
23+
final Clip clipBehavior;
24+
25+
/// Set to false to disable page snapping, useful for custom scroll behavior.
26+
///
27+
/// If the [padEnds] is false and [PageController.viewportFraction] < 1.0,
28+
/// the page will snap to the beginning of the viewport; otherwise, the page
29+
/// will snap to the center of the viewport.
30+
final bool pageSnapping;
31+
32+
/// Whether to add padding to both ends of the list.
33+
///
34+
/// If this is set to true and [PageController.viewportFraction] < 1.0, padding will be added
35+
/// such that the first and last child slivers will be in the center of
36+
/// the viewport when scrolled all the way to the start or end, respectively.
37+
///
38+
/// If [PageController.viewportFraction] >= 1.0, this property has no effect.
39+
///
40+
/// This property defaults to true.
41+
final bool padEnds;
42+
43+
/// {@macro flutter.widgets.shadow.scrollBehavior}
44+
///
45+
/// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
46+
/// [ScrollPhysics] is provided in [physics], it will take precedence,
47+
/// followed by [scrollBehavior], and then the inherited ancestor
48+
/// [ScrollBehavior].
49+
///
50+
/// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be
51+
/// modified by default to not apply a [Scrollbar].
52+
final ScrollBehavior? scrollBehavior;
53+
54+
/// Controls whether the widget's pages will respond to
55+
/// [RenderObject.showOnScreen], which will allow for implicit accessibility
56+
/// scrolling.
57+
///
58+
/// With this flag set to false, when accessibility focus reaches the end of
59+
/// the current page and the user attempts to move it to the next element, the
60+
/// focus will traverse to the next widget outside of the page view.
61+
///
62+
/// With this flag set to true, when accessibility focus reaches the end of
63+
/// the current page and user attempts to move it to the next element, focus
64+
/// will traverse to the next page in the page view.
65+
final bool allowImplicitScrolling;
66+
67+
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
68+
final DragStartBehavior dragStartBehavior;
69+
}

lib/src/models/view_type.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ enum ViewType {
1313

1414
/// Loads the data as a scrollable [Wrap].
1515
wrap,
16+
17+
/// Loads the data as a [PageView].
18+
page,
1619
}

lib/src/realtime_db_pagination.dart

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
// Dart Packages
2+
import 'dart:async';
3+
14
// Flutter Packages
25
import 'package:flutter/material.dart';
36
import 'package:flutter/scheduler.dart';
47

5-
// Dart Packages
6-
import 'dart:async';
7-
88
// Firebase Packages
99
import 'package:firebase_database/firebase_database.dart';
1010

1111
// Data Models
12+
import 'models/page_options.dart';
1213
import 'models/view_type.dart';
1314
import 'models/wrap_options.dart';
1415

@@ -54,6 +55,7 @@ class RealtimeDBPagination extends StatefulWidget {
5455
crossAxisCount: 2,
5556
),
5657
this.wrapOptions = const WrapOptions(),
58+
this.pageOptions = const PageOptions(),
5759
this.onEmpty = const EmptyScreen(),
5860
this.bottomLoader = const BottomLoader(),
5961
this.initialLoader = const InitialLoader(),
@@ -63,6 +65,7 @@ class RealtimeDBPagination extends StatefulWidget {
6365
this.physics,
6466
this.padding,
6567
this.controller,
68+
this.pageController,
6669
});
6770

6871
/// The query to use to fetch data from Firebase Realtime Database.
@@ -129,6 +132,11 @@ class RealtimeDBPagination extends StatefulWidget {
129132
/// Defaults to [WrapOptions].
130133
final WrapOptions wrapOptions;
131134

135+
/// The [PageView] properties to use.
136+
///
137+
/// Defaults to [PageOptions].
138+
final PageOptions pageOptions;
139+
132140
/// The widget to use when data is empty.
133141
///
134142
/// Defaults to [EmptyScreen].
@@ -164,6 +172,11 @@ class RealtimeDBPagination extends StatefulWidget {
164172
/// Defaults to [ScrollController].
165173
final ScrollController? controller;
166174

175+
/// The page controller to use for the [PageView].
176+
///
177+
/// Defaults to [PageController].
178+
final PageController? pageController;
179+
167180
@override
168181
State<RealtimeDBPagination> createState() => _RealtimeDBPaginationState();
169182
}
@@ -185,6 +198,10 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
185198
late final ScrollController _controller =
186199
widget.controller ?? ScrollController();
187200

201+
/// [PageController] to listen to page changes and load more data.
202+
late final PageController _pageController =
203+
widget.pageController ?? PageController();
204+
188205
/// Whether initial data is loading.
189206
bool _isInitialLoading = true;
190207

@@ -275,7 +292,8 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
275292
// scroll to the bottom and load more data.
276293
if (_isInitialLoading || _isFetching || _isEnded) return;
277294
SchedulerBinding.instance.addPostFrameCallback((_) {
278-
if (_controller.position.maxScrollExtent <= 0) {
295+
if (_controller.hasClients &&
296+
_controller.position.maxScrollExtent <= 0) {
279297
_loadData();
280298
}
281299
});
@@ -335,6 +353,7 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
335353
/// To handle scroll end event and load more data.
336354
void _scrollListener() {
337355
if (_isInitialLoading || _isFetching || _isEnded) return;
356+
if (!_controller.hasClients) return;
338357

339358
final position = _controller.position;
340359
if (position.pixels >= (position.maxScrollExtent - 50)) {
@@ -356,6 +375,7 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
356375
_controller
357376
..removeListener(_scrollListener)
358377
..dispose();
378+
_pageController.dispose();
359379
super.dispose();
360380
}
361381

@@ -374,12 +394,17 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
374394
bottomLoader: widget.bottomLoader,
375395
gridDelegate: widget.gridDelegate,
376396
wrapOptions: widget.wrapOptions,
397+
pageOptions: widget.pageOptions,
377398
scrollDirection: widget.scrollDirection,
378399
reverse: widget.reverse,
379400
controller: _controller,
401+
pageController: _pageController,
380402
shrinkWrap: widget.shrinkWrap,
381403
physics: widget.physics,
382404
padding: widget.padding,
405+
onPageChanged: (index) {
406+
if (index >= _data.length - 1) _loadData();
407+
},
383408
);
384409
}
385410
}

0 commit comments

Comments
 (0)