Hey devs! 👋
Want to refresh your ListView with infinite scroll in FlutterFlow when something changes or when the user pulls to refresh? You’re in the right place! In this guide, I’ll walk you through a custom setup that lets you do exactly that—with full code and a super-easy step-by-step.

Let me walk you through a complete solution using custom code. And don’t worry—it’s easier than you think. We’ll wrap the ListView in a reusable widget that supports both infinite scroll and manual refresh, all at the same time.


✅ What Are We Solving?

You have a ListView using infinite scrolling, and you want to refresh it automatically when something changes or manually with pull-to-refresh (swipe down gesture). FlutterFlow’s native ListView alone doesn’t give you both features together, so let’s fix that with a little trick! 😉


🔧 Step-by-Step: Refresh ListView with Infinite Scroll Setup

Step 1: Convert Your ListView into a Component

  • Right-click on your existing ListView.
  • Click “Convert to Component”.
  • Name it something like MyInfiniteListView.

Step 2: Add Custom Widget

  • Go to the Custom Code > Widgets section.
  • Click “+ Add Widget” and give it a name like RebuildComponent.
  • Now paste the full code below. Save it.

👇 The Full Custom Widget Code

dartCopyEdit// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/backend/schema/structs/index.dart';
import '/backend/schema/enums/enums.dart';
import '/actions/actions.dart' as action_blocks;
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart';
import '/custom_code/actions/index.dart';
import '/flutter_flow/custom_functions.dart';
import 'package:flutter/material.dart';

// Begin custom widget code
class RebuildComponent extends StatefulWidget {
  const RebuildComponent({
    super.key,
    this.width,
    this.height,
    required this.listViewWithInfiniteQuery,
    this.shouldScrollToTop = false,
  });

  final double? width;
  final double? height;
  final bool shouldScrollToTop;
  final Widget Function() listViewWithInfiniteQuery;

  @override
  State<RebuildComponent> createState() => _RebuildComponentState();
}

class _RebuildComponentState extends State<RebuildComponent> {
  Key listViewKey = UniqueKey();
  final ScrollController _scrollController = ScrollController();
  bool _wasScrollTriggered = false;

  @override
  void didUpdateWidget(covariant RebuildComponent oldWidget) {
    super.didUpdateWidget(oldWidget);

    // Scroll to top if needed
    if (widget.shouldScrollToTop && !_wasScrollTriggered) {
      _scrollToTop();
      _wasScrollTriggered = true;
    } else if (!widget.shouldScrollToTop) {
      _wasScrollTriggered = false;
    }
  }

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

  Future<void> _handleRefresh() async {
    try {
      setState(() {
        listViewKey = UniqueKey(); // Triggers rebuild
      });
      await Future.delayed(const Duration(milliseconds: 500));
    } catch (e, stack) {
      debugPrint('Refresh failed: $e\n$stack');
    }
  }

  void _scrollToTop() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_scrollController.hasClients) {
        _scrollController.animateTo(
          0,
          duration: const Duration(milliseconds: 500),
          curve: Curves.easeInOut,
        );
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: widget.width,
      height: widget.height,
      child: RefreshIndicator(
        onRefresh: _handleRefresh,
        child: NotificationListener<ScrollNotification>(
          onNotification: (_) => false,
          child: KeyedSubtree(
            key: listViewKey,
            child: PrimaryScrollController(
              controller: _scrollController,
              child: Builder(
                builder: (context) {
                  try {
                    return widget.listViewWithInfiniteQuery();
                  } catch (e, stack) {
                    debugPrint('ListView build failed: $e\n$stack');
                    return const Center(child: Text('Something went wrong'));
                  }
                },
              ),
            ),
          ),
        ),
      ),
    );
  }
}

🧠 How the ListView Refresh Code Works in FlutterFlow (Quick Summary)

Let me quickly break it down for you:

  • RebuildComponent is a custom widget that wraps your ListView.
  • It listens for refresh gestures and triggers a rebuild by giving a new key to the ListView.
  • Optional: It can automatically scroll to top if you toggle the shouldScrollToTop parameter.
  • Perfect for cases when your backend data changes and you want to requery without navigating away.

🧪 Step 3: Use It on Your Page

Now that your custom widget is ready:

  • Go to the page where you want the ListView.
  • Drag in a Custom Widget from the widget panel.
  • Choose RebuildComponent.
  • Pass your ListView component as a parameter to listViewWithInfiniteQuery.
dartCopyEditRebuildComponent(
  listViewWithInfiniteQuery: () => MyInfiniteListView(),
)

🏁 That’s It!

You now have a fully working infinite scroll ListView that refreshes on pull AND rebuilds when needed. 🎉
You can use this setup for notifications, posts, products, or any paginated query in your app.


🔁 Quick Recap

  • Convert ListView into a component ✅
  • Create RebuildComponent custom widget ✅
  • Replace original ListView with RebuildComponent and pass the component ✅
  • Pull to refresh + auto rebuild = 🚀

Got questions? Drop them in the comments.
I’ll try to help or make a short video on this soon! 🎥
Happy FlutterFlow-ing! 💙

Leave a Reply

Your email address will not be published. Required fields are marked *