FutureBuilder : Isolating futurebuilder from rebuilds

Dr_Skele·2023년 1월 26일
0

Flutter

목록 보기
8/19

What I need is a listview which can show list of data with RefreshIndicator for refreshing data list and OnReachEnd event for loading another list of data when scroll hits the bottom.
Usually, FutureBuilder is used to load async datas.
Using FutureBuilder for loading a list of data is OK, but if that list is going to change often and also the view that shows them? It just doesn't work.
Problem is that if I use FutureBuilder to get my data, when the widget rebuilds due to Refresh event or OnReachEnd event, the FutureBuilder will also rebuild and load new data everytime.

The solution to the problem is rather easy. The FutureBuilder just needs to be isolated from the rebuilding widgets.
It can be done by wrapping the scroll with FutureBuilder and seperate them so that only child scroll widgets rebuilds on event calls.

Like the above image, the FutureBuilder is wrapping the Scroll and RefreshController.
ChangeNotifierProvider is used for injecting initial data from FutureBuilder to childs.
Datas could be passed as parameters, but that way, it's much hard to access and change datas from child widgets. Also, I prefer passed parameters to be final on the child widget side.
So, the ChangeNotifier is used to hold the data across the widgets.

There is a way to remove the ChangeNotifer and handle all the data loads and events from the parent widget, but that way, I have to implement all the events and widgets everytime I use somewhere else even if I use similar datas.
However, If I do it like the image above, I only have to pass some parameters to the FutureBuilder for loading datas without implementing anything else.

Now It's time to look at the code.


class BoardListView extends StatefulWidget {
  const BoardListView({
    Key? key,
    this.title,
  }) : super(key: key);

  final String? title;

  @override
  State<BoardListView> createState() => _BoardListViewState();
}

class _BoardListViewState extends State<BoardListView> {
  late final BoardListViewModel viewModel;

  @override
  void initState() {
    super.initState();
    viewModel = BoardListViewModel(context);
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: Board.getArticles(title: widget.title),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          viewModel.articleList = snapshot.data as List<ArticleData>;
          return ChangeNotifierProvider.value(
            value: viewModel,
            child: BoardList(
              title: widget.title,
            ),
          );
        } else {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    );
  }
}

FutureBuilder part acts as an initializer.
Loaded data is given to the 'viewmodel' which extends from ChangeNotifier.

class BoardList extends StatefulWidget {
  const BoardList({
    Key? key,
    this.title,
  }) : super(key: key);

  final String? title;

  @override
  State<BoardList> createState() => _BoardListState();
}

class _BoardListState extends State<BoardList> {
  late final ScrollController scrollController;
  late BoardListViewModel viewModel;

  Future onReachEnd() async {
    var result = await BoardController.getArticles(
      title: widget.title,
    );

    if (result.isNotEmpty) {
      setState(() {
        viewModel.addAll(result);
      });
    }
  }

  Future onRefresh() async {
    var result = await BoardController.getArticles(
        categoryId: widget.categoryId, title: widget.title);
    setState(() {
      viewModel.articleList = result;
    });
  }

  @override
  void initState() {
    super.initState();
    scrollController = ScrollController();
  }

  @override
  void dispose() {
    scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    viewModel = Provider.of<BoardListViewModel>(context);
    print(viewModel.articleList.length);

	//custom made LasyScroll.
    //It calls onReachEnd when scoll hits the bottom.
    return LazyScroll(
      controller: scrollController,
      onReachEnd: onReachEnd,
      child: RefreshIndicator(
        onRefresh: onRefresh,
        child: ListView.builder(
          controller: scrollController,
          physics: const BouncingScrollPhysics(
              parent: AlwaysScrollableScrollPhysics()),
          itemBuilder: (context, index) {
            return BoardListTile(articleData: viewModel.articleList[index]);
          },
          itemCount: viewModel.articleList.length,
        ),
      ),
    );
  }
}

All other parts of the scoll.
This part is rebuilt when event triggers without rebuilding parent FutureBuilder.

profile
Tireless And Restless Debugging In Source : TARDIS

0개의 댓글