Waleed Alrashed
Waleed Alrashed

Reputation: 997

Bloc widgets reftech data (rebuild) when navigating to another pageView or any View altogether

Context: I have an App that has a PageView to navigate between five screens using BottomNavigationBar, One page is making use of the Bloc Pattern (almost all of the pages will use Bloc in future releases) these Blocs are fetching data from backend service in filling the UI with the fetched data.

Problem: When navigating between pages at any level of the widget tree. the Bloc widget 'reset' and refetch the data from the repository.

As shown in the picture below:

Screenshot that shows the problem

Widget tree as follows:

Tried-Solutions: I added the AutomaticKeepAliveClientMixin to the page class and to other pages but it didn't do the job.

Here is the code for the build method for the page shown in the above screenshot

   @override
  Widget build(BuildContext context) {

    super.build(context);

    return DefaultTabController(
      length: 4,
      child: Scaffold(
        backgroundColor: Colors.white,
        appBar: _builAppBarWithTabs(),
        body: Center(
          child: ConstrainedBox(
            constraints: BoxConstraints(maxWidth: 1200),
            child: ListView(
              scrollDirection: Axis.vertical,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.only(bottom: 150),
                  child: Container(
                    height: 800,
                    child: CustomScrollView(
                      scrollDirection: Axis.vertical,
                      slivers: <Widget>[
                        SliverToBoxAdapter(
                          child: MasonryGrid(
                            column: getResponsiveColumnNumber(context, 1, 2, 6),
                            children: <Widget>[
                              // First Bloc
                              BlocProvider(
                                create: (context) {
                                  BrandBloc(repository: _brandRepository);
                                },
                                child: Container(
                                  width: 200,
                                  alignment: Alignment.center,
                                  height: 80,
                                  child: BrandScreen(
                                    brandBloc: context.bloc(),
                                  ),
                                ),
                              ),
                              CategoryScreen(
                                // Second Bloc
                                categoryBloc: CategoryBloc(
                                  repository: context.repository(),
                                ),
                              ),
                              Container(
                                width: 200,
                                alignment: Alignment.center,
                                height: 330,
                                child: _buildFeaturedItemsList(),
                              ),
                              Placeholder(
                                strokeWidth: 0,
                                color: Colors.white,
                              )
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),


              ],
            ),
          ),
        ),
      ),
    );
  }

here is the code for class timeline.dart

here is the code for class home.dart that contains the BottomNavigationBar class

Question: How to stop the blocs from fetching the data each time the widget rebuilds?

Upvotes: 8

Views: 1433

Answers (3)

34m0
34m0

Reputation: 5955

I was a stuck on a very similar problem here. In my case, I found the BlocProviders or Cubits needed to be managed outside the widget tree, and then using BlocProvider.value to insert them into it.

List<Widget> _pages = List.empty(growable: true);
List<Cubit> _cubits = List.empty(growable: true);

@override
void dispose() {
    _cubits.forEach((cubit) {
      cubit.close();
    });
    super.dispose();
}

@override
void initState() {
    super.initState();    
    addPage(DateTime.now());
}

void addPage(DateTime day) {    
    MyCubit cubit = MyCubit(day: day)..load();
    _cubits.add(cubit);
    pages.add(BlocProvider<MyCubit>.value(
      value: cubit,
      child: MyPage(),
    ));
}

@override
  Widget build(BuildContext context) {
    return Scaffold(        
        body: PageView(
          controller: _controller,          
          children: _pages,
        ));
}

All credit and gratitude to this github issue.

Upvotes: 1

Waleed Alrashed
Waleed Alrashed

Reputation: 997

I finally got my head around the problem As it turned out, it was not about the bloc as much it was a problem related to PageView.

I wrapped the PageView with PageStorage widget as follows:

final PageStorageBucket bucket = PageStorageBucket(); //PageStorageBucket
  static List<Widget> pages = [
    Timeline(pageController: pageController, key: PageStorageKey('timeline')),
    Search(key: PageStorageKey('search')),
    LikeScreen(key: PageStorageKey('like')),
    CartScreen(key: PageStorageKey('cart')),
    storageService.getFromDisk('api_token') != null
        ? ProfileScreen(key: PageStorageKey('profile'))
        : AuthChooserScreen(key: PageStorageKey('auth')),
  ];
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return SafeArea(
      top: true,
      bottom: true,
      child: Scaffold(
        backgroundColor: Colors.white,
        key: _scaffoldKey,
        //Wrapped  PageView with PageStorage
        body: PageStorage(
          bucket: bucket,
          child: PageView(
            children: pages,
            controller: pageController,
            onPageChanged: onPageChanged,
            physics: NeverScrollableScrollPhysics(),
          ),
        ),
        bottomNavigationBar: _buildBottomNavigationBar(),
      ),
    );
  }

Every page is assigned a PageStorageKey which is used to save and restore values that can outlive the widget

Thanks to this great medium article which explains it perfectly: Keeping State with the Bottom Navigation Bar in Flutter

Upvotes: 8

Aziz
Aziz

Reputation: 309

You are using BlocProvider widget in a wrong way Instead, you should extract the BlocProvider to the upper level which is not rebuilt while navigating between pages "maby to MaterialApp widget if you are using this bloc across your app". Then use BlocBuilder or BlocConsumer to provide existing Bloc instance to the child widget and auto rebuild when bloc state change. BlocBuilder official doc: https://bloclibrary.dev/#/flutterbloccoreconcepts?id=blocbuilder

Upvotes: 4

Related Questions