Bird
Raised Fist0
Fluttermobile~20 mins

Infinite scrolling in Flutter - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Infinite Scroll List
A screen that shows a list of items which loads more items automatically when the user scrolls near the bottom.
Target UI
-------------------------
| Infinite Scroll List   |
-------------------------
| Item 1                |
| Item 2                |
| Item 3                |
| ...                   |
| Loading more...       |
-------------------------
Display a vertical scrollable list of items labeled 'Item 1', 'Item 2', etc.
Initially show 20 items.
When the user scrolls near the bottom, automatically load 20 more items.
Show a loading indicator at the bottom while loading more items.
Use a StatefulWidget to manage the list and loading state.
Starter Code
Flutter
import 'package:flutter/material.dart';

class InfiniteScrollList extends StatefulWidget {
  @override
  State<InfiniteScrollList> createState() => _InfiniteScrollListState();
}

class _InfiniteScrollListState extends State<InfiniteScrollList> {
  final ScrollController _scrollController = ScrollController();
  List<String> _items = [];
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _loadInitialItems();
    _scrollController.addListener(_onScroll);
  }

  void _loadInitialItems() {
    // TODO: Add code to load initial 20 items
  }

  void _onScroll() {
    // TODO: Add code to detect scroll near bottom and load more items
  }

  void _loadMoreItems() async {
    // TODO: Add code to load 20 more items with delay
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Infinite Scroll List')),
      body: ListView.builder(
        controller: _scrollController,
        itemCount: _items.length + (_isLoading ? 1 : 0),
        itemBuilder: (context, index) {
          if (index < _items.length) {
            return ListTile(title: Text(_items[index]));
          } else {
            return const Padding(
              padding: EdgeInsets.all(16.0),
              child: Center(child: CircularProgressIndicator()),
            );
          }
        },
      ),
    );
  }
}
Task 1
Task 2
Task 3
Solution
Flutter
import 'package:flutter/material.dart';

class InfiniteScrollList extends StatefulWidget {
  @override
  State<InfiniteScrollList> createState() => _InfiniteScrollListState();
}

class _InfiniteScrollListState extends State<InfiniteScrollList> {
  final ScrollController _scrollController = ScrollController();
  List<String> _items = [];
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _loadInitialItems();
    _scrollController.addListener(_onScroll);
  }

  void _loadInitialItems() {
    _items = List.generate(20, (index) => 'Item ${index + 1}');
    setState(() {});
  }

  void _onScroll() {
    if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100 && !_isLoading) {
      _loadMoreItems();
    }
  }

  Future<void> _loadMoreItems() async {
    setState(() {
      _isLoading = true;
    });
    await Future.delayed(const Duration(seconds: 2));
    final nextItems = List.generate(20, (index) => 'Item ${_items.length + index + 1}');
    _items.addAll(nextItems);
    setState(() {
      _isLoading = false;
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Infinite Scroll List')),
      body: ListView.builder(
        controller: _scrollController,
        itemCount: _items.length + (_isLoading ? 1 : 0),
        itemBuilder: (context, index) {
          if (index < _items.length) {
            return ListTile(title: Text(_items[index]));
          } else {
            return const Padding(
              padding: EdgeInsets.all(16.0),
              child: Center(child: CircularProgressIndicator()),
            );
          }
        },
      ),
    );
  }
}

This solution uses a StatefulWidget to keep track of the list of items and whether the app is currently loading more items.

We start by loading 20 items labeled 'Item 1' to 'Item 20'. The ScrollController listens for scroll events. When the user scrolls near the bottom (within 100 pixels), and if not already loading, it triggers _loadMoreItems().

The _loadMoreItems() method simulates a network delay of 2 seconds, then adds 20 more items to the list. While loading, a circular progress indicator is shown at the bottom of the list.

This creates a smooth infinite scrolling experience where more items appear as the user scrolls down.

Final Result
Completed Screen
-------------------------
| Infinite Scroll List   |
-------------------------
| Item 1                |
| Item 2                |
| Item 3                |
| ...                   |
| Item 40               |
| Loading more...       |
-------------------------
User scrolls down the list.
When near the bottom, a loading spinner appears.
After 2 seconds, 20 more items appear below.
User can keep scrolling to load more items endlessly.
Stretch Goal
Add a pull-to-refresh feature that reloads the list back to the first 20 items.
💡 Hint
Wrap the ListView.builder with a RefreshIndicator widget and implement the onRefresh callback to reset the list.

Practice

(1/5)
1. What is the main purpose of using infinite scrolling in a Flutter app?
easy
A. To load more content automatically as the user scrolls down
B. To preload all data before showing the screen
C. To prevent any scrolling on the screen
D. To refresh the entire app when the user taps a button

Solution

  1. Step 1: Understand infinite scrolling concept

    Infinite scrolling means loading more data automatically when the user reaches the bottom of the list.
  2. Step 2: Compare options with this concept

    Only To load more content automatically as the user scrolls down describes loading more content as the user scrolls down, which matches infinite scrolling.
  3. Final Answer:

    To load more content automatically as the user scrolls down -> Option A
  4. Quick Check:

    Infinite scrolling = load more on scroll [OK]
Hint: Infinite scrolling means loading more items when scrolling down [OK]
Common Mistakes:
  • Thinking infinite scrolling means preloading all data
  • Confusing infinite scrolling with pull-to-refresh
  • Assuming infinite scrolling disables scrolling
2. Which Flutter widget is commonly used to detect scroll position for infinite scrolling?
easy
A. TextEditingController
B. ScrollController
C. AnimationController
D. PageController

Solution

  1. Step 1: Identify controller for scroll events

    ScrollController is designed to listen to scroll position changes in scrollable widgets.
  2. Step 2: Eliminate unrelated controllers

    TextEditingController is for text input, AnimationController for animations, PageController for page views, so they don't detect scroll position.
  3. Final Answer:

    ScrollController -> Option B
  4. Quick Check:

    ScrollController detects scroll position [OK]
Hint: ScrollController tracks scroll position for infinite scroll [OK]
Common Mistakes:
  • Using TextEditingController for scrolling
  • Confusing PageController with ScrollController
  • Not using any controller to detect scroll
3. Given this Flutter code snippet, what will happen when the user scrolls to the bottom?
ScrollController _controller = ScrollController();

_controller.addListener(() {
  if (_controller.position.pixels == _controller.position.maxScrollExtent) {
    print('Load more items');
  }
});
medium
A. Prints 'Load more items' when user reaches bottom
B. Nothing happens when scrolling to bottom
C. Throws an error because of missing setState
D. Scrolls back to top automatically

Solution

  1. Step 1: Understand the listener logic

    The listener checks if the scroll position equals the max scroll extent, meaning user reached bottom.
  2. Step 2: Check the action inside listener

    When condition is true, it prints 'Load more items' to console.
  3. Final Answer:

    Prints 'Load more items' when user reaches bottom -> Option A
  4. Quick Check:

    Scroll at bottom triggers print [OK]
Hint: Listener triggers action at maxScrollExtent (bottom) [OK]
Common Mistakes:
  • Thinking setState is required for print statements
  • Assuming nothing happens without UI update
  • Confusing pixels with minScrollExtent
4. Identify the error in this infinite scrolling setup:
ScrollController _controller = ScrollController();

@override
void initState() {
  super.initState();
  _controller.addListener(() {
    if (_controller.position.pixels >= _controller.position.maxScrollExtent) {
      fetchMoreData();
    }
  });
}

@override
void dispose() {
  super.dispose();
}
medium
A. initState should not call super.initState()
B. Incorrect comparison operator in listener
C. Missing call to _controller.dispose() in dispose method
D. fetchMoreData() should be called in build method

Solution

  1. Step 1: Check lifecycle methods for controller cleanup

    ScrollController must be disposed in dispose() to avoid memory leaks.
  2. Step 2: Verify dispose method implementation

    dispose() calls super.dispose() but does not call _controller.dispose(), which is required.
  3. Final Answer:

    Missing call to _controller.dispose() in dispose method -> Option C
  4. Quick Check:

    Dispose controllers to prevent leaks [OK]
Hint: Always dispose controllers in dispose() method [OK]
Common Mistakes:
  • Forgetting to dispose ScrollController
  • Calling fetchMoreData() in build causing repeated calls
  • Misunderstanding lifecycle method order
5. You want to implement infinite scrolling with a loading spinner at the bottom. Which approach correctly shows the spinner only while loading new items?
hard
A. Always show CircularProgressIndicator at bottom regardless of loading state
B. Replace entire ListView with spinner when loading
C. Use a Timer to show spinner for fixed 5 seconds after scroll
D. Add a boolean isLoading state; show CircularProgressIndicator in ListView footer when true

Solution

  1. Step 1: Understand conditional UI for loading spinner

    Spinner should appear only when new data is loading, controlled by a boolean state.
  2. Step 2: Evaluate options for showing spinner

    Add a boolean isLoading state; show CircularProgressIndicator in ListView footer when true uses isLoading boolean to conditionally show spinner in ListView footer, which is correct.
  3. Step 3: Why other options are incorrect

    Always show CircularProgressIndicator at bottom regardless of loading state always shows spinner, confusing users. Use a Timer to show spinner for fixed 5 seconds after scroll uses fixed timer unrelated to actual loading. Replace entire ListView with spinner when loading hides list, bad UX.
  4. Final Answer:

    Add a boolean isLoading state; show CircularProgressIndicator in ListView footer when true -> Option D
  5. Quick Check:

    Show spinner only while loading = use boolean state [OK]
Hint: Use boolean state to toggle loading spinner visibility [OK]
Common Mistakes:
  • Showing spinner all the time confusing users
  • Using timers instead of actual loading state
  • Hiding list instead of appending new items
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard