Initially fetching data from a repository with flutter cubit

296 views Asked by At

I need to fetch data initially when my app starts (flutter framework, cubit as state management). I thought about different approaches, and would like your feedback on which design is good:

a) In the Start Screen: Calling a method "collectDataFromRepository" from the cubit, when BlocProvider is installed.

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => ManageCostCubit(fakeLocalRepository)..getAllCost(),
      child: MaterialApp(
        routes: {

b) In a second screen: Inside initState, by calling a method in the cubit class and consuming the return value? Imho receiving a return value from a cubit method seems to be contrary to the architecture. The architecture suggests receiving state objects which hold the data.

  @override
  void initState() {
   
    super.initState(); 

    myList = context.read<ManageCostCubit>().getAllCost();
  }

c) In a second screen: Inside initState, by calling void method in the cubit class and accessing the state object? But "state" doesn't seem to be available there, i get red underlines with this:

    if (state is Success) {
      costList = state.costs;
    }

However, I never saw a initState method in examples that were crowded with several state-depending codes. Because I can not be sure that accessing remote data is successful.

d) Ignoring Cubit at all and accessing the repository directly from the UI, also in the initState-Method in the second Screen. Actually accessing the repo directly would be the simplest, but since I am facing different connection situations, I think using Cubit is right.

2

There are 2 answers

6
Cabdirashiid On BEST ANSWER

Technically it would be ideal to fetch data closest as possible to where it's being used, Unless it's big data, then it would be a good UX to fetch it where the user most likely won't notice. Allocating memory to data that might not be used is not a good design.

For example: An app has a settings page with multiple nested pages with huge data. If the settings is not the main function of the app, there's no need to fetch all the data every time, Rather load initial data, then once the page is visited, load next pages' data while the user is viewing and probably navigating. It might save time & memory. Even if it's milliseconds, it may contribute to an overall good experience and performance.

Your options a) & b) give the same results. If getAllCost() function is being used here to simply fetch the data & is not needed to be maintained, See FutureBuilder instead.

However, the proper way to use flutter_bloc is to emit states with the value. Have a function that fetches your data and while waiting for results, emit a loading state then if succeeded or failed emit other states, each for success and failed.

Example:

 fetchCosts() async {
    emit(Loading());
    try {
      List costs = await getAllCost();
      emit(Success(costs: costs));
    } catch (error) {
      emit(Failed(error: error));
    }
  }

c) You need a listener such as BlocBuilder to listen to sate changes. If you need to read the state at initState once, You may use BlocProvider like so:

final state = BlocProvider.of<ManageCostCubit>(context).state;

if (state is Success) {
  costList = state.costs;
}

d) If both methods achieve the same results, The method with the scalability, maintainability and lesser code would contribute to a better developer experience.

0
Rasputin221 On

Actually I found what i was looking for. The solution is to start fetching in the initState:

context.read<ManageCostCubit>().getAllCost();

And then checking if it has worked successfully in the UI, depending on the state. I never thougt it that way. I always wanted to be sure to have the right state in initState.

body: BlocBuilder<ManageCostCubit, ManageCostState>(
          builder: (context, state) {
            if (state is ManageCostInitial || state is ManageCostLoading) {
              return CircularProgressIndicator();
            }

            if (state is ManageCostError) {
              return Text('Failed to loads costs: ${state.message}');
            }