I am trying to implement pagination in my Flutter app but this is the first time I have done it. My idea was to create a Stream
of data that updates each time the user reaches the bottom of a list. I have failed to get this working. My current code has the logic for getting the new data and adding it to the existing data, but right now it’s not even returning the first range of data so the snapshots are empty. As for the pagination functionality, I tried to use a ChangeNotifier
to notify the Stream
to update, but I don’t know if that is working. Please take a look at the code below and let me know what should be changed.
The DataProvider
class:
class DataProvider extends ChangeNotifier{
DataProvider() : super();
static var changeController = ChangeNotifier();
static void reload() {
changeController.notifyListeners();
}
static int lastRange = 0;
static List data = [];
static Stream<List> paginatedUsersAndPosts() async* {
List<UserSearchResult> usersList = data.first;
List<Post> postsList = data.last;
print('Notifier');
changeController.addListener(() async {
print('Change notified, getting data');
List<int> range() {
if (lastRange == 0) {
lastRange = 10;
return [0, 10];
} else {
// Example 0, 10 => 11, 20
int newMin = lastRange + 1;
int newMax = lastRange + 10;
lastRange = newMax;
return [newMin, newMax];
}
}
List<Map<String, dynamic>> postsDocs = await Supabase.db
.from('posts')
.select()
.order('date', ascending: false)
.range(range().first, range().last);
List<Post> newPostsList =
postsDocs.map((postDoc) => Post.fromJson(postDoc)).toList();
newPostsList.forEach((post) async {
postsList.add(post);
if (usersList.where((u) => u.uid == post.uid).isNotEmpty) {
Map<String, dynamic> userDoc =
await Supabase.db.from('profiles').select().single();
ProfileInfoObject profileInfo = ProfileInfoObject.fromJson(userDoc);
print('New profile: $profileInfo');
Profile profile = Profile(profileInfo, []);
profile.posts.add(post);
List blockedUsers = userDoc['blockedUsers'] as List;
UserSearchResult user = (UserSearchResult(
profile, userDoc['uid'].toString(), blockedUsers));
usersList.add(user);
}
});
});
yield [usersList, postsList];
}
}
The main widget that uses the stream:
class FloatingTabBarView extends StatefulWidget {
const FloatingTabBarView({Key? key}) : super(key: key);
@override
State<FloatingTabBarView> createState() => _FloatingTabBarViewState();
}
class _FloatingTabBarViewState extends State<FloatingTabBarView> {
@override
void initState() {
PermissionsService.checkPermissions(context);
DataProvider.reload();
super.initState();
}
Stream<List> stream = DataProvider.paginatedUsersAndPosts();
return StreamBuilder<List>(
stream: stream,
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
...
});
}
@override
Widget build(BuildContext context) {
return floatingTabBarPageView();
}
}
2
Answers
The issue was the way I was creating the stream. Since I made the
Stream
a function, there was no way for me to call that function to reload. Instead, I moved the code to astatic void
that I can call from any page, created aStreamController
, and usedcontroller.add(data)
at the end of thevoid
which allows me to update the stream with the new data.Please take a look at pull_to_refresh package. This allows to implement pull to refresh and incrementally load data. Use the
RefreshController
to update when the data is refreshed or loaded.RefreshController
ListView
withSmartRefresher
onLoading
callback to fetch data incrementallyRefreshController
on completion or error.The package has a good usage example, do check it out.