QuirijnGB / lazy-load-scrollview

A wrapper for a Flutter ScrollView which enables lazy loading
BSD 2-Clause "Simplified" License
109 stars 26 forks source link

onEndOfPage called more than once #1

Closed GregorySech closed 5 years ago

GregorySech commented 5 years ago

Hi, I've used this plugin inside of a StreamBuilder without issues however when I try to use it inside a custom StatefulWidget the onEndOfPage callback gets called more than once.

Here the example app,

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:lazy_load_scrollview/lazy_load_scrollview.dart';

main() {
  runApp(MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: Text('LazyLoading'),
      ),
      body: Home(),
    ),
  ));
}

class Home extends StatefulWidget {
  final int pageSize;

  Home({this.pageSize = 10});

  @override
  State<StatefulWidget> createState() => HomeState();
}

class HomeState extends State<Home> {
  List<Map> documents;
  bool loading;
  int currentPage;
  Client client;

  @override
  void initState() {
    super.initState();
    documents = [];
    loading = false;
    currentPage = 0;
    client = Client();
    () async {
      await _loadMore();
    }();
  }

// 'http://monster6.disco.unimib.it/API/documents/search/?s=informatica&paging=${widget.pageSize}&offset=${currentPage > 0 ? currentPage * widget.pageSize : ''}'
  Future<void> _loadMore() async {
    print('loading more!');
    setState(() {
      loading = true;
    });
    final response = await client.get(
      Uri.http(
        'monster6.disco.unimib.it',
        '/API/documents/search',
        {
          's': 'informatica',
          'paging': '${widget.pageSize}',
          'offset':
              currentPage > 0 ? '${currentPage * widget.pageSize + 1}' : null
        },
      ),
    );
    print(jsonDecode(response.body));
    final results = List<Map>.from(
        jsonDecode(response.body)['documentsAleph']['documents']);
    if (mounted) {
      setState(() {
        loading = false;
        documents.addAll(results);
        currentPage++;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned.fill(
          child: LazyLoadScrollView(
            child: ListView.builder(
              itemCount: documents.length,
              itemBuilder: (context, index) {
                return AspectRatio(
                  aspectRatio: 2,
                  child: Card(
                    child: Text(documents[index]['title'] ?? 'null'),
                  ),
                );
              },
            ),
            onEndOfPage: () {
              _loadMore();
            },
            scrollOffset: 600,
          ),
        ),
        Positioned(
          top: 0,
          left: 0,
          right: 0,
          child: loading ? LinearProgressIndicator() : Container(),
        ),
      ],
    );
  }
}
QuirijnGB commented 5 years ago

Hi! Thanks for reaching out. I've had a look, and it seems that you calling

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

is causing to update the widget. If you remove that it seems to work. Now that probably doesn't help because you want to display some loading indicator.

I'll need a way to get notified that new data has finished load so I'm thinking to add an extra parameter to the LazyLoadScrollView, maybe isLoading to check whether the list has been populated with new data. Thoughts?

GregorySech commented 5 years ago

Sorry was a little busy.
In the end I just provided an empty callback if I was loading. It does feel like a workaround so maybe having a parameter that "ignores" the callback on build might be a good idea. If the callback returned a future maybe you could set the loadMoreStatus as stable after it completes instead of relying on didUpdateWidget

GregorySech commented 5 years ago

Another idea might be using the LazyLoadScrollView's child's (Value)Key to understand if that has been updated and the LazyLoadScrollView is stable again.
Something like this:


class LazyLoadScrollViewState extends State<LazyLoadScrollView> {
  LoadingStatus loadMoreStatus = LoadingStatus.STABLE;

  @override
  void didUpdateWidget(LazyLoadScrollView oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.child.key == null || oldWidget.child.key != widget.child.key) {
      loadMoreStatus = LoadingStatus.STABLE;
    }
  }
...

and on the "client" side it would be kind of like this:

LazyLoadScrollView(
  child: ListView.builder(
    key: ValueKey(documents.length),
    itemCount: ...
QuirijnGB commented 5 years ago

Heya! Thanks for the feedback. I feel like the key idea would be a bit brittle, and harder to debug. Using an explicit parameter, is probably clearer to the user and easier to document too. I'll see if i can make a PR for it tonight

QuirijnGB commented 5 years ago

@GregorySech I added a new parameter to LazyLoadScrollView named isLoading. If you set a boolean to that it should make it work as you expect. Update your pubspec.yaml to use version 0.0.3

QuirijnGB commented 5 years ago

Thanks for raising this!