vedartm / paginate_firestore

A flutter package to simplify pagination with firestore data 🗃
https://pub.dev/packages/paginate_firestore
MIT License
113 stars 137 forks source link

Scroll positions issue inside TabBarViews #91

Closed azazadev closed 3 years ago

azazadev commented 3 years ago

Hi,

Issue : the scroll position is reset when Change Tabs,

How to reproduce :

this is a very simple example to reproduce, as I know the PaginateFirestore return a CustomScrollView in case of ListView, based on documentation we need to add PageStorageKey as key and the scroll offset will be saved, but this WA not work

NOT WORK WITH PaginateFirestore as CustomScrollView

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:paginate_firestore/paginate_firestore.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firestore pagination library',
      theme: ThemeData(
        brightness: Brightness.dark,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final List<String> _tabs = <String>[
      "Featured",
      "Popular",
      "Latest",
    ];
    return Material(
      child: Scaffold(
        body: DefaultTabController(
          length: _tabs.length,
          child: Scaffold(
            appBar: AppBar(
              bottom: TabBar(
                tabs: _tabs.map((String name) => Tab(text: name)).toList(),
              ),
              title: Text('Tabs Demo'),
            ),
            body: TabBarView(
              children: _tabs.map((String name) {
                return SafeArea(
                  top: false,
                  bottom: false,
                  child: Builder(
                    builder: (BuildContext context) {
                      return NotificationListener<ScrollNotification>(
                        onNotification: (scrollNotification) {
                          return true;
                        },
                        child: PaginateFirestore(
                          key: PageStorageKey<String>(name),
                          // Use SliverAppBar in header to make it sticky
                          header: SliverToBoxAdapter(child: Text('HEADER')),
                          footer: SliverToBoxAdapter(child: Text('FOOTER')),
                          // item builder type is compulsory.
                          itemBuilderType: PaginateBuilderType
                              .listView, //Change types accordingly
                          itemBuilder: (index, context, documentSnapshot) {
                            final data = documentSnapshot.data() as Map;
                            return ListTile(
                              leading: CircleAvatar(child: Icon(Icons.person)),
                              title: data == null
                                  ? Text('Error in data')
                                  : Text(data['name']),
                              subtitle: Text(documentSnapshot.id),
                            );
                          },
                          // orderBy is compulsory to enable pagination
                          query: FirebaseFirestore.instance
                              .collection('users')
                              .orderBy('name'),
                          // to fetch real-time data
                          isLive: true,
                        ),
                      );
                    },
                  ),
                );
              }).toList(),
            ),
          ),
        ),
      ),
    );
  }
}

WORK WITH CustomScrollView

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firestore pagination library',
      theme: ThemeData(
        primarySwatch: Colors.yellow,
        visualDensity: VisualDensity.adaptivePlatformDensity,
        brightness: Brightness.dark,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final List<String> _tabs = <String>[
      "Featured",
      "Popular",
      "Latest",
    ];
    return Material(
      child: Scaffold(
        body: DefaultTabController(
          length: _tabs.length,
          child: Scaffold(
            appBar: AppBar(
              bottom: TabBar(
                tabs: _tabs.map((String name) => Tab(text: name)).toList(),
              ),
              title: Text('Tabs Demo'),
            ),
            body: TabBarView(
              children: _tabs.map((String name) {
                return SafeArea(
                  top: false,
                  bottom: false,
                  child: Builder(
                    builder: (BuildContext context) {
                      return NotificationListener<ScrollNotification>(
                        onNotification: (scrollNotification) {
                          return true;
                        },
                        child: CustomScrollView(
                          key: PageStorageKey<String>(name),
                          slivers: <Widget>[
                            SliverPadding(
                              padding: const EdgeInsets.all(8.0),
                              sliver: SliverList(
                                delegate: SliverChildBuilderDelegate(
                                  (BuildContext context, int index) {
                                    return Column(
                                      children: <Widget>[
                                        Container(
                                          height: 150,
                                          width: double.infinity,
                                          color: Colors.blueGrey,
                                          child: Column(
                                            mainAxisAlignment:
                                                MainAxisAlignment.center,
                                            children: <Widget>[
                                              Text('$name $index')
                                            ],
                                          ),
                                        ),
                                        SizedBox(
                                          height: 8,
                                        )
                                      ],
                                    );
                                  },
                                  childCount: 30,
                                ),
                              ),
                            ),
                          ],
                        ),
                      );
                    },
                  ),
                );
              }).toList(),
            ),
          ),
        ),
      ),
    );
  }
}

@vedartm @all Any idea please ?

azazadev commented 3 years ago

as a WA I wrap the PaginateFirestore with widget implementing AutomaticKeepAliveClientMixin to maintain the state, hope this help someone facing the same issue

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:paginate_firestore/paginate_firestore.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firestore pagination library',
      theme: ThemeData(
        brightness: Brightness.dark,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final List<String> _tabs = <String>[
      "Featured",
      "Popular",
      "Latest",
    ];
    return Material(
      child: Scaffold(
        body: DefaultTabController(
          length: _tabs.length,
          child: Scaffold(
            appBar: AppBar(
              bottom: TabBar(
                tabs: _tabs.map((String name) => Tab(text: name)).toList(),
              ),
              title: Text('Tabs Demo'),
            ),
            body: TabBarView(
              children: _tabs.map((String name) {
                return SafeArea(
                  top: false,
                  bottom: false,
                  child: Builder(
                    builder: (BuildContext context) {
                      return NotificationListener<ScrollNotification>(
                        onNotification: (scrollNotification) {
                          return true;
                        },
                        child: PaginateView(
                          child: PaginateFirestore(
                            key: PageStorageKey<String>(name),
                            // Use SliverAppBar in header to make it sticky
                            header: SliverToBoxAdapter(child: Text('HEADER')),
                            footer: SliverToBoxAdapter(child: Text('FOOTER')),
                            // item builder type is compulsory.
                            itemBuilderType: PaginateBuilderType
                                .listView, //Change types accordingly
                            itemBuilder: (index, context, documentSnapshot) {
                              final data = documentSnapshot.data() as Map;
                              return ListTile(
                                leading:
                                    CircleAvatar(child: Icon(Icons.person)),
                                title: data == null
                                    ? Text('Error in data')
                                    : Text(data['name']),
                                subtitle: Text(documentSnapshot.id),
                              );
                            },
                            // orderBy is compulsory to enable pagination
                            query: FirebaseFirestore.instance
                                .collection('users')
                                .orderBy('name'),
                            // to fetch real-time data
                            isLive: true,
                          ),
                        ),
                      );
                    },
                  ),
                );
              }).toList(),
            ),
          ),
        ),
      ),
    );
  }
}

class PaginateView extends StatefulWidget {
  final Widget child;
  const PaginateView({
    Key key,
    this.child,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _PaginateViewState();
  }
}

class _PaginateViewState extends State<PaginateView>
    with AutomaticKeepAliveClientMixin {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }

  @override
  bool get wantKeepAlive => true;
}

@vedartm you can close ticket if this WA seems correct for you

vedartm commented 3 years ago

@azazadev Thanks for suggesting this solution. I will close this now.