I am trying to implement pagination in my app but it's not working very well. So far, the code works to get the first 10 posts and their users but after that, it goes downhill. When a user reaches the last post, the getData()
function is called again to add ten more. Although I only call it once, a loop is somehow created, which results in all of the data being added at once, defeating the point of pagination. What's even more strange is that, as I see the range printing many times, I can see that the range skips by tens of numbers (example: Range: [1021, 1030] => Range: [1051, 1060]). Another issue is that when I open the app switcher, the app goes in the background and the data
list is cleared which makes postsList
and usersList
empty, messing up the pagination.
Here is the code that I am using:
class FloatingTabBarView extends StatefulWidget {
const FloatingTabBarView({Key? key}) : super(key: key);
@override
State<FloatingTabBarView> createState() => _FloatingTabBarViewState();
}
class _FloatingTabBarViewState extends State<FloatingTabBarView> with AutomaticKeepAliveClientMixin<FloatingTabBarView> {
@override
bool get wantKeepAlive => true;
List<Post> currentUserPosts = [];
Profile currentProfile = Profile(
ProfileInfoObject(
Supabase.auth.currentUser!.userMetadata!['photoURL'].toString(),
Supabase.auth.currentUser!.userMetadata!['displayName'].toString(),
),
[],
);
static int lastRange = 0;
static var data = [[], []];
static var controller = StreamController<List>.broadcast();
static void getData() async {
List<UserSearchResult> usersList =
data.first.map<UserSearchResult>((e) => e).toList();
List<Post> postsList = data.last.map<Post>((e) => e).toList();
print('Getting/Updating 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];
}
}
print('Range: ${range()}');
final postsDocs = await Supabase.db
.from('posts')
.select()
.order('date', ascending: false)
.range(range().first, range().last);
List<Post> newPostsList =
postsDocs.map<Post>((postDoc) => Post.fromJson(postDoc)).toList();
for (var post in newPostsList) {
postsList.add(post);
if (usersList.where((u) => u.uid == post.uid).isEmpty) {
try {
final userDoc =
await Supabase.db.from('profiles').select().eq('uid', post.uid).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);
} catch (e) {
print('Failed getting user: $e');
}
} else {
final posts = usersList.where((u) => u.uid == post.uid).first.profile.posts;
if (posts.where((p) => p.postId == post.postId).isEmpty) {
posts.add(post);
}
}
}
controller.add([usersList, postsList]);
}
@override
void initState() {
PermissionsService.checkPermissions(context);
getData();
super.initState();
}
@override
void dispose() {
lastRange = 0;
super.dispose();
}
Widget floatingTabBarPageView() {
List<TabItem> tabList(List<UserSearchResult> users, List<Post> posts) {
currentProfile = Profile(
ProfileInfoObject(
Supabase.auth.currentUser!.userMetadata!['photoURL'].toString(),
Supabase.auth.currentUser!.userMetadata!['displayName'].toString(),
),
posts
.where((post) => post.uid == Supabase.auth.currentUser!.id)
.toList(),
);
List blockedUsers =
Supabase.auth.currentUser!.userMetadata!['blockedUsers'] ?? [];
UserSearchResult currentUser = UserSearchResult(
currentProfile,
Supabase.auth.currentUser!.id,
blockedUsers,
);
List<TabItem> list = [
TabItem(
icon: const Icon(Icons.home_outlined, size: 35),
selectedIcon: const Icon(Icons.home, size: 35),
label: "Home",
tabWidget: HomeScreen(usersMap: users, postsList: posts),
),
TabItem(
icon: const Icon(Icons.travel_explore_outlined, size: 35),
selectedIcon: const Icon(Icons.travel_explore, size: 35),
label: "Explore",
tabWidget: ExploreScreen(usersMap: users, postsList: posts),
),
if (!kIsWeb)
if (Platform.isIOS || Platform.isAndroid)
const TabItem(
icon: Icon(CupertinoIcons.add_circled, size: 35),
selectedIcon: Icon(CupertinoIcons.add_circled_solid, size: 35),
label: "Post",
tabWidget: CreatePostScreen(),
),
TabItem(
icon: const Icon(CupertinoIcons.person_alt_circle, size: 35),
selectedIcon:
const Icon(CupertinoIcons.person_alt_circle_fill, size: 35),
label: "Account",
tabWidget: ProfileScreen(
user: currentUser,
),
),
];
return list;
}
return StreamBuilder<List>(
stream: controller.stream,
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
List<UserSearchResult> users = [];
if (snapshot.data?[0] != null) {
users = snapshot.data![0];
}
List<Post> posts = [];
if (snapshot.data?[1] != null) {
posts = snapshot.data![1];
}
print('Snapshots: $users, $posts');
if (!snapshot.hasData) {
return const Scaffold(
body: Center(child: GlowingIcon()),
);
} else {
return FloatingTabBar(
activeColor: const Color(0xFF7157A0),
inactiveColor: const Color(0xFFadc0ff),
tabItemList: tabList(users, posts),
isFloating: true,
titleTapNavigationRouteWidget: HomeScreen(
usersMap: users,
postsList: posts,
),
title: 'GLOBE',
showTabLabelsForFloating: true,
);
}
});
}
@override
Widget build(BuildContext context) {
return floatingTabBarPageView();
}
}