PJQuakJag
PJQuakJag

Reputation: 1247

How to place a key in ListView to maintain state on page change

I am trying to build a steam of posts (think twitter or instagram type posts) that a user is able to scroll through. As they are scrolling, they can click one of the posts and navigate to a new page. When they go navigate back from that page, I want them to remain at the same position on the position that they were previously on within the ListView.

PROBLEM

I can not currently keep the stream widget from rebuilding and returning to the scroll position. I know that one of the solutions to this is to include a key; however, I have tried to including the key in the ListView.builder, but it has not worked.

QUESTION Where should I include the key? Am I using the right type of key?

class Stream extends StatefulWidget {
  Stream({Key key, this.user}) : super(key: key);

  final User user;

  @override
  _StreamState createState() => new _StreamState(
      user: user
  );
}

class _StreamState extends State<Stream> {


  _StreamState({this.user});

  final User user;

  Firestore _firestore = Firestore.instance;
  List<DocumentSnapshot> _posts = [];
  bool _loadingPosts = true;
  int _per_page = 30;
  DocumentSnapshot _lastPosts;
  ScrollController _scrollController = ScrollController();
  bool _gettingMorePosts = false;
  bool _morePostsAvailable = true;


  _getPosts() async {
    Query q = _firestore
        .collection('posts')
        .document(user.user_id)
        .collection('posts')
        .orderBy("timePosted", descending: true)
        .limit(_per_page);

    setState(() {
      _loadingPosts = true;
    });
    QuerySnapshot querySnapshot = await q.getDocuments();
    _posts = querySnapshot.documents;

    if (_posts.length == 0) {
      setState(() {
        _loadingPosts = false;
      });
    }

    else {
      _lastPosts = querySnapshot.documents[querySnapshot.documents.length - 1];

      setState(() {
        _loadingPosts = false;
      });
    }
  }

  _getMorePosts() async {

    if (_morePostsAvailable == false) {
      return;
    }

    if (_gettingMorePosts == true) {
      return;
    }

    if (_posts.length == 0) {
      return;
    }

    _gettingMorePosts = true;

    Query q = _firestore
        .collection('posts')
        .document(user.user_id)
        .collection('posts')
        .orderBy("timePosted", descending: true)
        .startAfter([_lastPosts.data['timePosted']]).limit(_per_page);

    QuerySnapshot querySnapshot = await q.getDocuments();

    if (querySnapshot.documents.length == 0) {
      _morePostsAvailable = false;
    }

    if(querySnapshot.documents.length > 0) {
      _lastPosts  = querySnapshot.documents[querySnapshot.documents.length - 1];

    }
    _posts.addAll(querySnapshot.documents);

    setState(() {});

    _gettingMorePosts = false;
  }

  @override
  void initState() {
    super.initState();
    _getPosts();
    _scrollController.addListener(() {
      double maxScroll = _scrollController.position.maxScrollExtent;
      double currentScroll = _scrollController.position.pixels;
      double delta = MediaQuery.of(context).size.height * 0.25;

      if (maxScroll - currentScroll < delta) {

        _getMorePosts();
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[

        new Expanded(
          child: _loadingPosts == true
              ? Container(
            child: Center(
              child: Text(" "),
            ),
          )
              : Container(
            child: Center(
              child: _posts.length == 0
                  ? Center(
                child: Text("Follow friends", style: TextStyle(fontSize: 15),),
              )
                  : ListView.builder(
                key: widget.key,
                  controller: _scrollController,
                  itemCount: _posts.length,
                  itemBuilder: (BuildContext ctx, int index) {
                    return new Widget(
                    //paramenters to build the post widget here
                    );
                  }),
            ),
          ),
        ),
      ],
    );
  }

One thing to note, since I don't want to return all pages (due to Firestore expenses calling so many posts), the build logic is created such that more posts are loaded upon scroll. I realize this may impact it.

Upvotes: 1

Views: 1956

Answers (2)

CopsOnRoad
CopsOnRoad

Reputation: 268244

Short answer:

You need to provide key to your ListView.builder like this:

ListView.builder(
  key: PageStorageKey("any_text_here"),
  // ...
)

Long answer:

You can see that when you come back from screen 2 to screen 1, the item 30 remains on the top.

enter image description here


Sorry it was difficult to reproduce your code due to limited availability to the variables you're using. I created a simple example to demonstrate what you're looking for.

Full code:

void main() => runApp(MaterialApp(home: HomePage()));

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ListView.builder(
        key: PageStorageKey("any_text_here"), // this is the key you need
        itemCount: 50,
        itemBuilder: (_, i) {
          return ListTile(
            title: Text("Item ${i}"),
            onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => DetailPage(index: i))),
          );
        },
      ),
    );
  }
}

class DetailPage extends StatefulWidget {
  final int index;

  const DetailPage({Key key, this.index}) : super(key: key);

  @override
  _DetailPageState createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text(
          "You clicked ${widget.index}",
          style: Theme.of(context).textTheme.headline,
        ),
      ),
    );
  }
}

Upvotes: 5

GJJ2019
GJJ2019

Reputation: 5182

This kind of key works:

key: PageStorageKey('Your Key Name'),

Upvotes: 0

Related Questions