Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ import 'package:firebase_pagination/firebase_pagination.dart';
```dart
FirestorePagination(
query: FirebaseFirestore.instance.collection('scores').orderBy('score'),
itemBuilder: (context, documentSnapshot, index) {
final data = documentSnapshot.data() as Map<String, dynamic>;
itemBuilder: (context, docs, index) {
final data = docs[index].data() as Map<String, dynamic>;

// Do something cool with the data
},
Expand All @@ -58,8 +58,8 @@ FirestorePagination(
RealtimeDBPagination(
query: FirebaseDatabase.instance.ref().child('scores').orderByChild('score'),
orderBy: 'score',
itemBuilder: (context, dataSnapshot, index) {
final data = dataSnapshot.value as Map<String, dynamic>;
itemBuilder: (context, dataNodes, index) {
final data = dataNodes[index].value as Map<String, dynamic>;

// Do something cool with the data
},
Expand Down Expand Up @@ -96,6 +96,7 @@ RealtimeDBPagination(
| `isLive` | **Whether to fetch newly added items as they are added to Database.** | _bool_ | `false` |
| `gridDelegate` | **The delegate to use for the GridView.** | _SliverGridDelegate_ | `crossAxisCount: 2` |
| `wrapOptions` | **The Wrap widget properties to use.** | _WrapOptions_ | `WrapOptions()` |
| `pageOptions` | **The PageView widget properties to use.** | _PageOptions_ | `PageOptions()` |
| `onEmpty` | **The widget to use when data is empty.** | _Widget_ | `EmptyScreen()` |
| `bottomLoader` | **The widget to use when more data is loading.** | _Widget_ | `BottomLoader()` |
| `initialLoader` | **The widget to use when data is loading initially.** | _Widget_ | `InitialLoader()` |
Expand All @@ -104,7 +105,8 @@ RealtimeDBPagination(
| `shrinkWrap` | **Should the ScrollView be shrink-wrapped.** | _bool_ | `false` |
| `physics` | **The scroll behavior to use for the ScrollView.** | _ScrollPhysics_ | - |
| `padding` | **The padding to use for the ScrollView.** | _EdgeInsetsGeometry_ | - |
| `controller` | **The controller to use for the ScrollView.** | _ScrollController_ | - |
| `controller` | **The controller to use for the ScrollView.** | _ScrollController_ | ScrollController() |
| `pageController` | **The controller to use for the PageView.** | _PageController_ | PageController() |
| `descending` | **Whether the data should be fetched in descending order or not. Only works for RealtimeDBPagination** | _bool_ | `false` |

### If you liked the package, then please give it a [Like 👍🏼][package] and [Star ⭐][repository]
Expand Down
33 changes: 29 additions & 4 deletions lib/src/firestore_pagination.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Dart Packages
import 'dart:async';

// Flutter Packages
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

// Dart Packages
import 'dart:async';

// Firebase Packages
import 'package:cloud_firestore/cloud_firestore.dart';

// Data Models
import 'models/page_options.dart';
import 'models/view_type.dart';
import 'models/wrap_options.dart';

Expand Down Expand Up @@ -52,6 +53,7 @@ class FirestorePagination extends StatefulWidget {
crossAxisCount: 2,
),
this.wrapOptions = const WrapOptions(),
this.pageOptions = const PageOptions(),
this.onEmpty = const EmptyScreen(),
this.bottomLoader = const BottomLoader(),
this.initialLoader = const InitialLoader(),
Expand All @@ -61,6 +63,7 @@ class FirestorePagination extends StatefulWidget {
this.physics,
this.padding,
this.controller,
this.pageController,
});

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

/// The [PageView] properties to use.
///
/// Defaults to [PageOptions].
final PageOptions pageOptions;

/// The widget to use when data is empty.
///
/// Defaults to [EmptyScreen].
Expand Down Expand Up @@ -143,6 +151,11 @@ class FirestorePagination extends StatefulWidget {
/// Defaults to [ScrollController].
final ScrollController? controller;

/// The page controller to use for the [PageView].
///
/// Defaults to [PageController].
final PageController? pageController;

@override
State<FirestorePagination> createState() => _FirestorePaginationState();
}
Expand All @@ -164,6 +177,10 @@ class _FirestorePaginationState extends State<FirestorePagination> {
late final ScrollController _controller =
widget.controller ?? ScrollController();

/// [PageController] to listen to page changes and load more data.
late final PageController _pageController =
widget.pageController ?? PageController();

/// Whether initial data is loading.
bool _isInitialLoading = true;

Expand Down Expand Up @@ -228,7 +245,8 @@ class _FirestorePaginationState extends State<FirestorePagination> {
// scroll to the bottom and load more data.
if (_isInitialLoading || _isFetching || _isEnded) return;
SchedulerBinding.instance.addPostFrameCallback((_) {
if (_controller.position.maxScrollExtent <= 0) {
if (_controller.hasClients &&
_controller.position.maxScrollExtent <= 0) {
_loadDocuments();
}
});
Expand Down Expand Up @@ -268,6 +286,7 @@ class _FirestorePaginationState extends State<FirestorePagination> {
/// To handle scroll end event and load more data.
void _scrollListener() {
if (_isInitialLoading || _isFetching || _isEnded) return;
if (!_controller.hasClients) return;

final position = _controller.position;
if (position.pixels >= (position.maxScrollExtent - 50)) {
Expand All @@ -289,6 +308,7 @@ class _FirestorePaginationState extends State<FirestorePagination> {
_controller
..removeListener(_scrollListener)
..dispose();
_pageController.dispose();
super.dispose();
}

Expand All @@ -307,12 +327,17 @@ class _FirestorePaginationState extends State<FirestorePagination> {
bottomLoader: widget.bottomLoader,
gridDelegate: widget.gridDelegate,
wrapOptions: widget.wrapOptions,
pageOptions: widget.pageOptions,
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
controller: _controller,
pageController: _pageController,
shrinkWrap: widget.shrinkWrap,
physics: widget.physics,
padding: widget.padding,
onPageChanged: (index) {
if (index >= _docs.length - 1) _loadDocuments();
},
);
}
}
69 changes: 69 additions & 0 deletions lib/src/models/page_options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Flutter Packages
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

// Data Models
import 'view_type.dart';

/// The properties of the [PageView] widget in the [ViewType.page] view.
class PageOptions {
/// Creates a object that contains the properties of the [PageView] widget.
const PageOptions({
this.clipBehavior = Clip.hardEdge,
this.pageSnapping = true,
this.padEnds = true,
this.scrollBehavior,
this.allowImplicitScrolling = false,
this.dragStartBehavior = DragStartBehavior.start,
});

/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;

/// Set to false to disable page snapping, useful for custom scroll behavior.
///
/// If the [padEnds] is false and [PageController.viewportFraction] < 1.0,
/// the page will snap to the beginning of the viewport; otherwise, the page
/// will snap to the center of the viewport.
final bool pageSnapping;

/// Whether to add padding to both ends of the list.
///
/// If this is set to true and [PageController.viewportFraction] < 1.0, padding will be added
/// such that the first and last child slivers will be in the center of
/// the viewport when scrolled all the way to the start or end, respectively.
///
/// If [PageController.viewportFraction] >= 1.0, this property has no effect.
///
/// This property defaults to true.
final bool padEnds;

/// {@macro flutter.widgets.shadow.scrollBehavior}
///
/// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
/// [ScrollPhysics] is provided in [physics], it will take precedence,
/// followed by [scrollBehavior], and then the inherited ancestor
/// [ScrollBehavior].
///
/// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be
/// modified by default to not apply a [Scrollbar].
final ScrollBehavior? scrollBehavior;

/// Controls whether the widget's pages will respond to
/// [RenderObject.showOnScreen], which will allow for implicit accessibility
/// scrolling.
///
/// With this flag set to false, when accessibility focus reaches the end of
/// the current page and the user attempts to move it to the next element, the
/// focus will traverse to the next widget outside of the page view.
///
/// With this flag set to true, when accessibility focus reaches the end of
/// the current page and user attempts to move it to the next element, focus
/// will traverse to the next page in the page view.
final bool allowImplicitScrolling;

/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
}
3 changes: 3 additions & 0 deletions lib/src/models/view_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ enum ViewType {

/// Loads the data as a scrollable [Wrap].
wrap,

/// Loads the data as a [PageView].
page,
}
33 changes: 29 additions & 4 deletions lib/src/realtime_db_pagination.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Dart Packages
import 'dart:async';

// Flutter Packages
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

// Dart Packages
import 'dart:async';

// Firebase Packages
import 'package:firebase_database/firebase_database.dart';

// Data Models
import 'models/page_options.dart';
import 'models/view_type.dart';
import 'models/wrap_options.dart';

Expand Down Expand Up @@ -54,6 +55,7 @@ class RealtimeDBPagination extends StatefulWidget {
crossAxisCount: 2,
),
this.wrapOptions = const WrapOptions(),
this.pageOptions = const PageOptions(),
this.onEmpty = const EmptyScreen(),
this.bottomLoader = const BottomLoader(),
this.initialLoader = const InitialLoader(),
Expand All @@ -63,6 +65,7 @@ class RealtimeDBPagination extends StatefulWidget {
this.physics,
this.padding,
this.controller,
this.pageController,
});

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

/// The [PageView] properties to use.
///
/// Defaults to [PageOptions].
final PageOptions pageOptions;

/// The widget to use when data is empty.
///
/// Defaults to [EmptyScreen].
Expand Down Expand Up @@ -164,6 +172,11 @@ class RealtimeDBPagination extends StatefulWidget {
/// Defaults to [ScrollController].
final ScrollController? controller;

/// The page controller to use for the [PageView].
///
/// Defaults to [PageController].
final PageController? pageController;

@override
State<RealtimeDBPagination> createState() => _RealtimeDBPaginationState();
}
Expand All @@ -185,6 +198,10 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
late final ScrollController _controller =
widget.controller ?? ScrollController();

/// [PageController] to listen to page changes and load more data.
late final PageController _pageController =
widget.pageController ?? PageController();

/// Whether initial data is loading.
bool _isInitialLoading = true;

Expand Down Expand Up @@ -275,7 +292,8 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
// scroll to the bottom and load more data.
if (_isInitialLoading || _isFetching || _isEnded) return;
SchedulerBinding.instance.addPostFrameCallback((_) {
if (_controller.position.maxScrollExtent <= 0) {
if (_controller.hasClients &&
_controller.position.maxScrollExtent <= 0) {
_loadData();
}
});
Expand Down Expand Up @@ -335,6 +353,7 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
/// To handle scroll end event and load more data.
void _scrollListener() {
if (_isInitialLoading || _isFetching || _isEnded) return;
if (!_controller.hasClients) return;

final position = _controller.position;
if (position.pixels >= (position.maxScrollExtent - 50)) {
Expand All @@ -356,6 +375,7 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
_controller
..removeListener(_scrollListener)
..dispose();
_pageController.dispose();
super.dispose();
}

Expand All @@ -374,12 +394,17 @@ class _RealtimeDBPaginationState extends State<RealtimeDBPagination> {
bottomLoader: widget.bottomLoader,
gridDelegate: widget.gridDelegate,
wrapOptions: widget.wrapOptions,
pageOptions: widget.pageOptions,
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
controller: _controller,
pageController: _pageController,
shrinkWrap: widget.shrinkWrap,
physics: widget.physics,
padding: widget.padding,
onPageChanged: (index) {
if (index >= _data.length - 1) _loadData();
},
);
}
}
Loading