Emre Turan
Emre Turan

Reputation: 113

How to correctly set the initial page size while implementing a lazy loading listview in Flutter?

I'm trying to implement a lazy loading listview in my Flutter app. Basically when user scrolls to bottom, we fetch more data from the server. Let's say there are totally 100 items and I fetch 10 of them at the beginning.

The problem is, some devices such as tablets are large enough to display all the 10 items at once. So Flutter makes the listview unscrollable. Therefore we are not able to reach the bottom and fetching is not triggered.

What is the best approach to trigger fetching automatically on such use cases?

void _scrollListener() {
    // Check if we are at the bottom of the list and loading isn't in progress
    if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !_isLoading) {
      _loadMoreItems();
    }
  }

  Future<void> _loadMoreItems() async {
    setState(() {
      _isLoading = true;
    });

    // Simulate a network request (wait 2 seconds)
    await Future.delayed(Duration(seconds: 2));

    // Fetch more items
    final newItems = List.generate(10, (index) => _items.length + index);

    setState(() {
      _isLoading = false;
      _items.addAll(newItems);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Lazy Loading ListView')),
      body: ListView.builder(
        controller: _scrollController,
        itemCount: _items.length + 1, // +1 for the loading indicator
        itemBuilder: (context, index) {
          if (index == _items.length) {
            // Show loading indicator at the bottom when more items are being fetched
            return _isLoading
                ? Center(child: CircularProgressIndicator())
                : SizedBox.shrink();
          }
          return ListTile(title: Text('Item ${_items[index]}'));
        },
      ),
    );
  }

This screen is large enough to display all 10 items so fetching does not trigger

Upvotes: 1

Views: 60

Answers (3)

Zhentao
Zhentao

Reputation: 801

I would recommend EasyRefresh to you

  EasyRefresh(
    onRefresh: () async {
      ....
    },
    onLoad: () async {
      ....
    },
    child: ListView(),
  );

Upvotes: 0

Handelika
Handelika

Reputation: 412

You can use layout builder and depends on screen size. I used like this. You can make configuration for yours, like get 10 items for small screen, 20 for big screen sizes.

  Widget _buildScreenLayout(BuildContext context) => Container(
        height: context.height,
        margin: const EdgeInsets.all(10),
        child: LayoutBuilder(
          builder: (context, constraints) {
            final isSizeSmall = MediaQuery.sizeOf(context).width < 600;
            if (isSizeSmall) {
              return _buildLayout();
            } else {
              return _buildBigScreenLayout();
            }
          },
        ),
      );

Upvotes: 2

MohitJadav
MohitJadav

Reputation: 954

i have updated code please try this if it works.

import 'package:flutter/material.dart';

class LazyLoadingList extends StatefulWidget {
  const LazyLoadingList({super.key});

  @override
  State<LazyLoadingList> createState() => _LazyLoadingListState();
}

class _LazyLoadingListState extends State<LazyLoadingList> {
  List<int> items = List.generate(20, (index) => index); // Initial list
  final ScrollController _scrollController = ScrollController();
  bool isLoading = false;
  int totalItems = 100; // Simulated total items available

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

  void _onScroll() {
    if (_scrollController.position.pixels >=
            _scrollController.position.maxScrollExtent - 200 &&
        !isLoading &&
        items.length < totalItems) {
      _loadMoreItems();
    }
  }

  Future<void> _loadMoreItems() async {
    if (!mounted) return;

    setState(() {
      isLoading = true;
    });

    // Simulate network delay
    await Future.delayed(const Duration(seconds: 1));

    if (!mounted) return;

    setState(() {
      final newItems = List.generate(
        20,
        (index) => items.length + index,
      );
      items.addAll(newItems);
      isLoading = false;
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Lazy Loading List'),
      ),
      body: ListView.builder(
        controller: _scrollController,
        itemCount: items.length + (isLoading ? 1 : 0),
        itemBuilder: (context, index) {
          if (index == items.length && isLoading) {
            return const Center(
              child: Padding(
                padding: EdgeInsets.all(8.0),
                child: CircularProgressIndicator(),
              ),
            );
          }
          return ListTile(
            title: Text('Item ${items[index]}'),
          );
        },
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: LazyLoadingList()));
}

Upvotes: 2

Related Questions