TatsuUkraine / flutter_sticky_infinite_list

Multi directional infinite list with Sticky headers for Flutter applications
BSD 2-Clause "Simplified" License
341 stars 31 forks source link

Example for Lazy Loading #59

Closed desmeit closed 1 week ago

desmeit commented 1 year ago

Is there an small example for lazy loading with this great plugin?

I want to lazily load and display small chunks of new items each time the user reached the last preloading item. Is that possible?

TatsuUkraine commented 1 year ago

Hi. Could you share a little bit more context? I mean, in what particular part on the list and under what conditions you need to lazy load data?

desmeit commented 1 year ago

I want to add months (header) and days of the month (content).

If the user has entered something on some day, a dot should appear at this date. Maybe the user has already entered data over years. I don't want to preload all years, that makes no sense. Therefore I would like to preload one month and when the user is reaching the first day of this month it should loads the previous month etc.

TatsuUkraine commented 1 year ago

Ok. Need to think.

A variant on top of my head in case you using basic solution with InfiniteList and InfiniteListItem (for months).

You can create your own widget and extend from InfiniteListItem. There you will have ability to override initState, which will be invoked as part of StatefullWidget lifecycle. Which means this method will be invoked only once, when this widget will be rendered by Flutter.

Since InfiniteList builds each InfiniteListItem on demand (basically it works the same as ListView.builder), InfiniteListItem.initState will be invoked only when Flutter decides that it's time to render it during the scroll. By default it's a visible scrollable area + some space behind it.

One thing to consider in this variant. If you want to render scrollable area with initial offset other than 0, flutter will try to build items between 0 position and position that you specified. So if you want to navigate to this page with "preselected date", consider to use it as "center" date.

TatsuUkraine commented 1 year ago

Also, with this variant take into account thatListItem widget (month widget) will be destroyed once you scroll far enough after it was rendered. So when you scroll back to already destroyed list item - this item will be built from the scratch (including initState invocation)

For example. Let's assume you have 01.2023 and 02.2023 rendered when you first time open your page. You scroll forward, Flutter sees that it's time to render 03.2023. It will build it and invoke initState method on it. You continue to scroll and lets say you scrolled up to 06.2023. Now flutter sees that 03.2023 is no longer visible and it will destroy this widget. So once you scroll back and flutter sees that 03.2023 widget should be rendered, it will build it from the scratch (with initState invocation)

TatsuUkraine commented 1 year ago

let me know if I don't explain it clear enough)

desmeit commented 1 year ago

I did some tests and it seems to work with this code:

import 'package:flutter/material.dart';
import 'package:sticky_infinite_list/sticky_infinite_list.dart';

class StickyInfiteChart extends StatefulWidget {
  @override
  State<StickyInfiteChart> createState() => _StickyInfiteChartState();
}

class _StickyInfiteChartState extends State<StickyInfiteChart> {
  late ScrollController controller;

  List<InfiniteListItem> items = [];

  @override
  void initState() {
    super.initState();

    items =
        List<InfiniteListItem>.generate(3, (index) => _buildInfinitItem(index));

    controller = ScrollController()..addListener(_scrollListener);
    //controller = ScrollController();
  }

  @override
  void dispose() {
    controller.removeListener(_scrollListener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
        child: InfiniteList(
            controller: controller,
            direction: InfiniteListDirection.multi,
            negChildCount: items.length,
            //posChildCount: 1,
            scrollDirection: Axis.horizontal,
            builder: (BuildContext context, int index) {
              /// Builder requires [InfiniteList] to be returned
              return _buildInfinitItem(index);
            }));
  }

  InfiniteListItem<int> _buildInfinitItem(index) {
    return InfiniteListItem(
      positionAxis: HeaderPositionAxis.crossAxis,

      /// Header builder
      headerBuilder: (BuildContext context) {
        return Container(
          alignment: Alignment.centerLeft,
          width: 100,
          height: 30,
          child: Text("Header $index"),
          color: Colors.orange,
        );
      },

      /// Content builder
      contentBuilder: (BuildContext context) {
        return Container(
          width: 150,
          height: 300,
          child: Text(
         "Content $index",
          style: TextStyle(
             color: Colors.white,
          ),
          ),
          color: Colors.blueAccent,
          );
      },
    );
  }

  void _scrollListener() {
    //print(controller.position.extentAfter);
    if (controller.position.extentAfter < 500) {
      print("get new items");
      setState(() {
        items.addAll(List<InfiniteListItem>.generate(
            5, (index) => _buildInfinitItem(index)));
      });
    }
  }
}

but I will do some further tests and will let you know. thanks.

desmeit commented 1 year ago

After some tests I think its a bit more complicated.

Just one question: What did you men by "You can create your own widget and extend from InfiniteListItem."?

If I understand your idea correctly, do you mean that I generate my list dynamically and always add new items if I scroll? Or do you mean that the items are generated by ListView build when ListView thinks it needs to be generated? Could you give a minimal example?

TatsuUkraine commented 1 year ago

I meant, items generated by ListView (InfiniteList). So when you first time render it, it will render items within the visible viewport (+ extra offset on sides). During the scroll it will be rendering items while you scroll and destroy those who already outside of boundary .

So using initState and dispose method you will know what items rendered, and what items destroyed