Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

ListView Scrollback: Can Hot Reload Preserve It?

Learn how to prevent ListView from scrolling to top after hot reload in Flutter using PageStorageKey and persistent state strategies.
Developer fixing ListView scroll issue in Flutter using PageStorageKey during hot reload Developer fixing ListView scroll issue in Flutter using PageStorageKey during hot reload
  • 🌐 Flutter’s hot reload rebuilds widgets but keeps state only if you use the right keys or controllers.
  • 💾 PageStorageKey lets scroll position save itself between hot reloads.
  • 👨‍🔧 Using ScrollController gives you manual control over scrolling. But it takes more work to manage.
  • ⚠️ If parent widgets rebuild often or keys are used wrong, scroll position will not stay saved.
  • 🧱 Good app design, like MVVM and Riverpod, makes tracking and bringing back scroll state simpler.

Flutter's hot reload helps you work fast. But it has a small issue: scrollable views like ListView often jump back to the top after a reload. This is a fixable limit of how Flutter rebuilds things. This article explains how Flutter handles widget rebuilds, why ListView loses its place, and what you can do. We will look at PageStorageKey, ScrollController, and good app design to make sure your scrolling stays smooth and in place, even with many reloads.

How Hot Reload Really Works in Flutter

Hot reload helps you build apps faster. It puts new code into a running app without starting it over. But not all widget states stay the same. Some states are saved automatically, and you have to save others yourself.

What Hot Reload Saves and Rebuilds

When you hot reload:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

  • ✅ It saves objects and data inside StatefulWidgets, like global providers and Blocs.
  • ❌ But, it rebuilds the widget tree from updated build methods.
  • ❌ Any data that starts in a build() method or that does not have a stable key gets rebuilt. This means scroll position often resets.

Flutter finds widgets using their keys. It only rebuilds them if their code changes. But without clear keys or controllers to keep state, the scroll position resets.

Hot Reload vs Hot Restart

Feature Hot Reload Hot Restart
Keeps State? ✅ Yes (within StatefulWidgets) ❌ No (everything reset)
Speed ⚡ Fast 🐢 Slower than reload
Widget Tree 🛠️ Rebuilt 🔄 Fully rebuilt
Scroll Position ❌ Lost unless linked to storage ❌ Always reset

This comparison helps you see if you have a bug or just a normal Flutter behavior you missed.

Why ListView Scrolls Back to Top

Let's see why a ListView loses its place after a hot reload.

The Core Problem

  1. Flutter widgets get checked again after a hot reload.
  2. Scrollable widgets, like ListView, use an internal state to keep their position.
  3. If they don't keep their identity — usually with keys — Flutter sees the list as new. Then it resets the scroll to the top (offset 0).

So, it's important to give widgets a stable identity.

Fixing It with PageStorage and PageStorageKey

Flutter’s PageStorage lets you save scroll positions and other small UI details between rebuilds. It is a temporary storage for keys and values, tied to the widget tree. This helps you keep scrolling working well after hot reloads.

What is PageStorage?

PageStorage is a hidden tool that saves UI states related to a page, like scroll positions. This storage stays through hot reloads and even when you change routes in some parts of your app.

Understanding PageStorageKey<T>

PageStorageKey<T> is a class Flutter uses to mark widgets that need to save state. When you add it to a scrollable widget, Flutter knows it's the same widget even after rebuilds. Then, it brings back the widget's UI state.

  • The key must be a stable and unique value for the widget's whole life.
  • If you change the key, the stored data resets, and so does the scroll position.

How to Use PageStorageKey in Your ListView

Using PageStorageKey in ListView is simple. Here is how you add it:

class MyListViewPage extends StatelessWidget {
  final List<String> items;

  MyListViewPage({Key? key, required this.items}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      key: PageStorageKey('my-list-view'), // Crucial for scroll retention!
      itemCount: items.length,
      itemBuilder: (context, index) => ListTile(
        title: Text(items[index]),
      ),
    );
  }
}

Best Practices to Follow

  • Use stable names — plain strings or enums work well.
  • Don't use UniqueKey() or keys made on the fly. These change with every build and break the saving process.
  • Make sure the parent widget is not rebuilt when it doesn't need to be. If it is, the ListView could still lose its position.
  • Use StatefulWidget when you need to to hold state or scroll controllers.

ScrollController vs PageStorageKey

While PageStorageKey saves scroll state on its own, ScrollController gives you more control. But it needs you to set it up by hand.

When to Use ScrollController

Use a ScrollController if:

  • You want fine-tuned control for your own scroll actions.
  • You plan to scroll to certain spots or item numbers automatically.
  • You need to save the scroll position yourself, even after Flutter rebuilds.
class ScrollListView extends StatefulWidget {
  final List<String> items;

  ScrollListView({Key? key, required this.items}) : super(key: key);

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

class _ScrollListViewState extends State<ScrollListView> {
  final ScrollController _controller = ScrollController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _controller,
      itemCount: widget.items.length,
      itemBuilder: (context, index) => ListTile(
        title: Text(widget.items[index]),
      ),
    );
  }
}

Manually Storing and Bringing Back Position

You can save _controller.offset by hand to your state provider or a local file.

ref.read(scrollPositionProvider.notifier).state = _controller.offset;
_controller.jumpTo(ref.read(scrollPositionProvider));

Should You Combine Both?

You can technically use both a PageStorageKey and ScrollController together. But this often causes problems. Flutter might bring back state from one source, then change it with the other.
🛑 It's best to use only one scroll management method for each widget.

Best Practices for Scroll Saving

To build strong Flutter apps, use these scroll-safe rules often:

  • Use PageStorageKey to save scroll state automatically. It is built right into Flutter.
  • For harder tasks, use ScrollController and state management.
  • Don't let widgets rebuild by accident. Use fewer anonymous functions or nested widgets.
  • Put related widgets together. This helps lower the chance of many widgets rebuilding.
  • Lists that change a lot, or lists inside tabs, can also use their own way to bring back scroll state.

Managing Scroll with State Management (e.g., Riverpod)

You can combine saving scroll state with more complex state management tools like Riverpod:

final scrollOffsetProvider = StateProvider<double>((ref) => 0.0);

class MyCustomList extends ConsumerStatefulWidget {
  @override
  _MyCustomListState createState() => _MyCustomListState();
}

class _MyCustomListState extends ConsumerState<MyCustomList> {
  late ScrollController _controller;

  @override
  void initState() {
    super.initState();
    _controller = ScrollController(
      initialScrollOffset: ref.read(scrollOffsetProvider),
    );

    _controller.addListener(() {
      ref.read(scrollOffsetProvider.notifier).state = _controller.offset;
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _controller,
      itemCount: 100,
      itemBuilder: (_, index) => ListTile(
        title: Text('Item $index'),
      ),
    );
  }
}

This gives you full control over how scrolling works across screens, when the app is open, and when pages refresh.

UI Design That Supports Saved State

Good app design makes saving state simpler. Use these designs:

  • MVVM (Model-View-ViewModel): State stays in the ViewModel, and the UI uses it.
  • Clean Architecture: This design helps keep scrolling working across different parts of your app.
  • Repository Layer: This layer helps watch scroll states linked to routes or specific screen rules.

Make a ScrollStateManager for your app. It can put all scroll positions in one place and fill in widgets automatically after hot reloads.

Debugging Scroll Behavior

If your ListView hot reload state is still broken, try these tips:

  • ✅ Use Flutter DevTools to look at the widget tree and check keys.
  • ✅ Add print() statements or use debugPrint() to see how widget methods run.
  • ✅ Run flutter run --track-widget-creation to watch widgets being rebuilt.

Checking PageStorageBucket Use

You can see what is being saved. Just make a global PageStorageBucket like this:

final PageStorageBucket bucket = PageStorageBucket();

void main() {
  runApp(MaterialApp(
    home: PageStorage(
      bucket: bucket,
      child: MyListViewPage(),
    ),
  ));
}

Make sure the widget tree keeps the ListView in the same PageStorage bucket after reloads.

Make Your Development Work Better for ListView

Want to make your hot reload development work better?

  • Always use PageStorageKey or ScrollController on purpose.
  • Don't make objects on the fly, like anonymous widget builds right in the build() function.
  • Use widget tests to confirm scrolling does not reset by accident.
  • Choose app designs where state lives outside the UI. This needs less from the UI for saved logic.

Common Mistakes That Break Scroll Position

Don't make these common mistakes:

  • ❌ Don't forget keys.
  • ❌ Don't use keys like UniqueKey(). They change every build.
  • ❌ Don't change the widget setup too much between hot reloads.
  • ❌ Don't move a scrollable widget up or down in the widget tree without keeping the same key.

When Scroll Reset Is Actually a Good Thing

Sometimes, resetting the scroll makes the app easier to use:

  • When you go to a detail page and come back, it might be better to start at the top.
  • When searching or filtering, it's often better to show users a fresh list.
  • With pull-to-refresh, users expect to go to the top after new content loads.

Just make sure this is on purpose, and not because of bad app design.

Building UI That Works With You

Flutter's hot reload is great for developers. But ListView resets are a big problem if not fixed. Good news: the solution is clear:

  • Use PageStorageKey for no-fuss scroll saving.
  • Use ScrollController for your own or complex scrollers.
  • Use state management and front-end design patterns on purpose. This helps reduce ongoing work.
  • Debug in a clear, small way. Know what builds and rebuilds what.

Save scroll. Build in a solid way. Keep making your Flutter apps with confidence.

What’s Next? Improve Your Flutter State Skills

Look at more Devsolus guides to make your Flutter work even better:


Citations

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading