Skip to content

Infinite Lazy List#

Example's page

This page describes an example listed in ui category.

Usage of AUI_DECLARATIVE_FOR to make an infinite lazy list.

This example demonstrates making an optimal infinite list. That is, when you infinitely scroll down to load new items, the older items are unloaded from memory.

The "loading" action is performed on a worker thread and a delay is simulated by AThread::sleep.

As a bonus, we've added a spinner to indicate that loading is in progress.

In this example, we've used functional style and decomposition technique instead of making custom view classes. The idea is hide the implementation specifics of myLazyList that makes a basic AView in return. The overall state of produced lazy list object is controlled by list model.

myLazyList is limited to specific type of model by intention. AUI does not provide some kind of generalization on its own. In fact, we're demonstrating the whole concept in less than 100 lines of code. You certainly can make a generalization that suits your project's needs.

Source Code#

Repository

CMakeLists.txt#

1
2
3
aui_executable(aui.example.infinite_lazy_list)

aui_link(aui.example.infinite_lazy_list PRIVATE aui::core aui::views)

src/main.cpp#

#include <range/v3/all.hpp>

#include <AUI/Platform/Entry.h>
#include "AUI/Platform/AWindow.h"
#include "AUI/Util/UIBuildingHelpers.h"
#include "AUI/View/AScrollArea.h"
#include "AUI/View/ASpinnerV2.h"
#include "AUI/View/AForEachUI.h"
#include "AUI/Model/AListModel.h"
#include "AUI/Thread/AAsyncHolder.h"

using namespace declarative;
using namespace ass;
using namespace std::chrono_literals;

struct Item {
    AProperty<AString> value;
};

struct State {
    AProperty<AVector<_<Item>>> items;
    AProperty<bool> needMore = false;
    AAsyncHolder asyncTasks;
};

_<AView> myLazyList(_<State> state) {
    // note that we observe for transition to true here, not the current state of property
    // see PropertyTest_Observing_changes for more info
    AObject::connect(state->needMore.changed, AObject::GENERIC_OBSERVER, [state](bool newState){
        if (!newState) { // we're interested in transitions to true state only.
            return;
        }
        auto loadFrom = state->items->size(); // base index to load from.
        state->asyncTasks << AUI_THREADPOOL {
            // perform "loading" task on a worker thread.

            AThread::sleep(500ms); // imitate hard work here

            // aka "loaded" from backend storage of some kind
            auto loadedItems = AVector<_<Item>>::generate(20, [&](size_t i) {
                return aui::ptr::manage_shared(new Item { .value = "Item {}"_format(loadFrom + i) });
            });

            AUI_UI_THREAD { // back to main thread.
                state->items.writeScope()->insertAll(loadedItems);
                state->needMore = false;
            };
        };
    });

    return Vertical {
        AUI_DECLARATIVE_FOR(i, *state->items, AVerticalLayout) { return Label{} & i->value; },
        Centered {
          _new<ASpinnerV2>() AUI_LET {
                  AObject::connect(it->redrawn, AObject::GENERIC_OBSERVER, [state] {
                      // when a spinner appears, we indicate that we need more items.
                      state->needMore = true;
                  });
              },
        },
    };
}

AUI_ENTRY {
    auto window = _new<AWindow>("Infinite Lazy List", 200_dp, 300_dp);
    window->setContents(Stacked { AScrollArea::Builder().withContents(myLazyList(_new<State>())) });
    window->show();

    return 0;
}