Search code examples
flutterdartpaginationflutter-change-notifier

How to notify a stream to update in Flutter


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();
  }
}

Solution

  • 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 a static void that I can call from any page, created a StreamController, and used controller.add(data) at the end of the void which allows me to update the stream with the new data.