Flutter bloc – do not call the api every time (use the same State in different views)

Issue

I’m using flutter bloc pattern in my flutter app. I have bottom navigation bar with several tabs in one page. Two of them are using the same api call (the same State). When the user taps on 1 of them I call the api to get the data, but if the user taps on the other tab I want to get the data without calling the api again. How I can do that?

In my main page (dashboard) I have BlocBuilder to change the tabs and I create the Dashboard cubit in it

class DashboardPage extends StatelessWidget {

 @override
 Widget build(BuildContext context) {
   return BlocBuilder<TabsBloc, AppTab>(
     builder: (BuildContext context, AppTab activeTab) {
       return Scaffold(
         appBar: AppBar(
           title: Text(DashboardHelpers.getTabLabel(activeTab)),
         ),
         body: RepositoryProvider(
           create: (BuildContext context) => DashboardRepository(),
           child: BlocProvider<DashboardCubit>(
             create: (BuildContext context) => DashboardCubit(
               dashboardRepository: context.read<DashboardRepository>(),
               authBloc: context.read<AuthBloc>(),
             ),
             child: DashboardHelpers.getTabContent(activeTab),
           ),
         ),
         bottomNavigationBar: TabSelector(
             activeTab: activeTab,
             onTabSelected: (tab) =>
                 BlocProvider.of<TabsBloc>(context).add(TabUpdated(tab))),
       );
     },
   );
 }
}

The tabs are View that are loaded as child. One of the views is View1. When I get the data I loaded in ContentView1

class View1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    BlocProvider.of<DashboardCubit>(context)..getDashboardDevices();
    return BlocConsumer<DashboardCubit, DashboardState>(
      listener: (BuildContext context, DashboardState state) {
        if (state is DashboardError) {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: Text(state.message),
            ),
          );
        }
      },
      builder: (BuildContext context, DashboardState state) {
        if (state is DevicesLoaded) {
          return ContentView1(data: state.data);
        } else if (state is DashboardLoading) {
          return LoadingWidget();
        } else if (state is DashboardError) {
          return Container(
            child: Center(
              child: Text(state.message),
            ),
          );
        } else {
          return Container();
        }
      },
    );
  }
}

and the View2 is almost the same. The data is absolutely the same and it is loaded in ContentView2 but it is a completly different widget than ContentView1

class View2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    BlocProvider.of<DashboardCubit>(context)..getDashboardDevices();
    return BlocConsumer<DashboardCubit, DashboardState>(
      listener: (BuildContext context, DashboardState state) {
        if (state is DashboardError) {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: Text(state.message),
            ),
          );
        }
      },
      builder: (BuildContext context, DashboardState state) {
        if (state is DevicesLoaded) {
          return ContentView2(data: state.data);
        } else if (state is DashboardLoading) {
          return LoadingWidget();
        } else if (state is DashboardError) {
          return Container(
            child: Center(
              child: Text(state.message),
            ),
          );
        } else {
          return Container();
        }
      },
    );
  }
}

The problem is that these two VIEWs are showing different data that comes from the same API endpoint.
How can I load the already gotten data when the user tabs from View1 to View2 without calling the API again.

Thanks!

Solution

You should be calling getDashboardDevices() only once, for that you could create a DashboardInitialState, when user clicks one of tabs, if the state is DashboardInitialState you run getDashboardDevices() and not always when the view is building. This way you will load data only once when one of the views is built and both of them will use the same data on loaded state.

There is View1 as example, try that with both views:

class View1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocConsumer<DashboardCubit, DashboardState>(
      listener: (BuildContext context, DashboardState state) {
        if (state is DashboardError) {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: Text(state.message),
            ),
          );
        }
      },
      builder: (BuildContext context, DashboardState state) {
        if(state is DashboardInitialState) {
          BlocProvider.of<DashboardCubit>(context)..getDashboardDevices();
          return LoadingWidget();
        } else if (state is DevicesLoaded) {
          return ContentView1(data: state.data);
        } else if (state is DashboardLoading) {
          return LoadingWidget();
        } else if (state is DashboardError) {
          return Container(
            child: Center(
              child: Text(state.message),
            ),
          );
        } else {
          return Container();
        }
      },
    );
  }
}

Answered By – NelsonThiago

Answer Checked By – Marie Seifert (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *