1
votes

I have a simple infinite scroll which loads items from an API. My expectation after load more items, the list if updated with new items and my view port will not be resetted.

Here is my page

class ArticleListPage extends StatelessWidget {
  final _scrollController = ScrollController();
  final Color color = Colors.black;
  ArticleListBloc _articleListBloc;

  @override
  Widget build(BuildContext context) {
    _articleListBloc = BlocProvider.of<ArticleListBloc>(context);
    return Scaffold(
      backgroundColor: color,
      body: BlocBuilder<ArticleListBloc, ArticleListState>(
        builder: (context, ArticleListState state) {
          if (state.searching) {
            return Center(
              child: CircularProgressIndicator(
                backgroundColor: Colors.white,
              ),
            );
          } else {
            return NotificationListener<ScrollNotification>(
              onNotification: _handleScrollNotification,
              child: ListView.builder(
                shrinkWrap: true,
                itemCount: calculateListItemCount(state),
                controller: _scrollController,
                physics: AlwaysScrollableScrollPhysics(),
                itemBuilder: (context, index) {
                  return index >= state.listItems.length
                      ? Center(
                          child: CircularProgressIndicator(),
                        )
                      : ArticleListViewItem(article: state.listItems[index]);
                },
              ),
            );
          }
        },
      ),
    );
  }

  bool _handleScrollNotification(ScrollNotification notification) {
    if (notification is ScrollEndNotification &&
        _scrollController.position.extentAfter == 0) {
      _articleListBloc.add(GetArticles());
    }
    return false;
  }

  int calculateListItemCount(ArticleListState state) {
    if (state.hasReachedEndOfResults) {
      return state.listItems.length;
    } else {
      return state.listItems.length + 1;
    }
  }
}

And my bloc

class ArticleListBloc extends Bloc<ArticleListEvent, ArticleListState> {
  final ArticleDataSource dataSource;

  ArticleListBloc(this.dataSource) : super(ArticleListState.initial());

  @override
  Stream<ArticleListState> mapEventToState(ArticleListEvent event) async* {
    String searchKeyword = state.searchKeyword;
    int pageNumber = state.pageNumber;
    bool hasReachEndResult = state.hasReachedEndOfResults;
    BuiltList<Article> currentArticles = state.listItems;
    if (event is SetKeyword) {
      searchKeyword = event.keyword;
      pageNumber = 0;
      currentArticles = new BuiltList<Article>();
      hasReachEndResult = false;
    }
    if (!hasReachEndResult) {
      yield state.rebuild((b) => b..searching = true);
      final articles = await dataSource.getArticles(pageNumber, searchKeyword);
      if (articles.isEmpty) {
        hasReachEndResult = true;
      } else {
        hasReachEndResult = false;
      }
      yield ArticleListState.success(currentArticles + articles, pageNumber + 1,
          searchKeyword, hasReachEndResult);
    }
  }
}

Somehow after searching (load more), I can see the first item is redrawn again and I am forced to scroll down again despite the items are correctly added into the list.

Do I need some kind of skip variable to go to the first newly added item ? What else am I missing here ?

1

1 Answers

0
votes

The problem was with the first if condition in the build widget function. It should be

         if (state.listItems.isEmpty) {
            return Center(
              child: CircularProgressIndicator(
                backgroundColor: Colors.white,
              ),
            );
          }

This way the CircularProgressIndicator would only be drawn during the first search, while the subsequent searches and its progress already handled by indicator in

                itemBuilder: (context, index) {
                  return index >= state.listItems.length
                      ? Center(
                          child: CircularProgressIndicator(),
                        )
                      : ArticleListViewItem(article: state.listItems[index]);
                },