Flutter ListView.builder - How to Jump to Certain Index Programmatically Flutter ListView.builder - How to Jump to Certain Index Programmatically dart dart

Flutter ListView.builder - How to Jump to Certain Index Programmatically


General Solution:

To store anything which can be represented as a number/string/list of strings, Flutter provides a powerful easy-to-use plugin which stores the values needed to be stored along with a key. So the next time you need you'll need to retrieve or even update that value all that you'll need is that key.

To get started, add the shared_preferences plugin to the pubspec.yaml file,

dependencies:  flutter:    sdk: flutter  shared_preferences: "<newest version>"

Run flutter pub get from the terminal or if your using IntelliJ just click on Packages get(You'll find it somewhere around the top-right corner of your screen while viewing the pubspec.yaml file)

Once the above command is successfully executed, import the below file in your main.dart or concerned file.

  import 'package:shared_preferences/shared_preferences.dart';

Now just attach a ScrollController to your ListView.builder() widget and make sure that the final/last offset is stored along with a specific key using shared_preferences whenever the user leaves the app in any way and is set when the initState of your concerned widget is called.

In order to know to detect changes in the state of our app and to act with accordance to it, we'll be inheriting WidgetsBindingObserver to our class.

Steps to follow:

  1. Extend the WidgetsBindingObserver class along with the State class of your StatefulWidget.

  2. Define a async function resumeController() as a function member of the above class.

  Future<void> resumeController() async{    _sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){      if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));      else _sharedPreferences.setDouble("scroll-offset-0", 0);      setState((){});      return _sharedPreferences;    });
  1. Declare two variables one to store and pass the scrollcontroller and the other to store and use the instance of SharedPreferences.
  ScrollController _scrollController;  SharedPreferences _sharedPreferences;
  1. Call resumeController() and pass your class to the addObserver method of the instance object in WidgetsBinding class.
  resumeController();  WidgetsBinding.instance.addObserver(this);
  1. Simply paste this code in the class definition (outside other member functions)
 @override  void dispose() {    WidgetsBinding.instance.removeObserver(this);    _scrollController.dispose();    super.dispose();  }  @override  void didChangeAppLifecycleState(AppLifecycleState state) {    if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)       _sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);    super.didChangeAppLifecycleState(state);  }
  1. Pass the ScrollController() to the concerned Scrollable.

Working Example:

class MyWidget extends StatefulWidget {  @override  _MyWidgetState createState() => _MyWidgetState();}class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver{  //[...]  ScrollController _scrollController;  SharedPreferences _sharedPreferences;  Future<void> resumeController() async{    _sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){      if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));      else _sharedPreferences.setDouble("scroll-offset-0", 0);      setState((){});      return _sharedPreferences;    });  }  @override  void initState() {    resumeController();    WidgetsBinding.instance.addObserver(this);    super.initState();  }  @override  void dispose() {    WidgetsBinding.instance.removeObserver(this);    _scrollController.dispose();    super.dispose();  }  @override  void didChangeAppLifecycleState(AppLifecycleState state) {    if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)       _sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);    super.didChangeAppLifecycleState(state);  }  @override  Widget build(BuildContext context) {    return MaterialApp(      debugShowCheckedModeBanner: false,      home: Scaffold(        appBar: AppBar(          title: Text("Smart Scroll View"),        ),        body: ListView.builder(            itemCount: 50,            controller: _scrollController,            itemBuilder: (c,i)=>                Padding(                  padding: EdgeInsets.symmetric(horizontal: 24,vertical: 16),                  child: Text((i+1).toString()),                ),        ),      ),    );  }}


You can use https://pub.dev/packages/scrollable_positioned_list. You can pass the initial index to the widget.

ScrollablePositionedList.builder( initialScrollIndex: 12, //you can pass the desired index here// itemCount: 500, itemBuilder: (context, index) => Text('Item $index'), itemScrollController: itemScrollController, itemPositionsListener: itemPositionsListener,);


Solution without knowing the size of your widgets

the Solution I found without knowing the size of your widget is displaying a reverse 'sublist' from the index to the end, then scroll to the top of your 'sublist' and reset the entire list. As it is a reverse list the item will be add at the top of the list and you will stay at your position (the index).

the problem is that you can't use a listView.builder because you will need to change the size of the list

example

class _ListViewIndexState extends State<ListViewIndex> {  ScrollController _scrollController;  List<Widget> _displayedList;  @override  void initState() {    super.initState();    _scrollController = ScrollController();    _displayedList = widget.items.sublist(0, widget.items.length - widget.index);    if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {      SchedulerBinding.instance.addPostFrameCallback((_) {//here the sublist is already build        completeList();      });    }  }  completeList() {//to go to the last item(in first position)     _scrollController.jumpTo(_scrollController.position.maxScrollExtent);//reset the list to the full list    setState(() {      _displayedList = widget.items;    });  }  @override  Widget build(BuildContext context) {    return Stack(      children: <Widget>[        ListView(          controller: _scrollController,          reverse: true,          children: _displayedList,        ),      ]    );  }}