How to manage multiple state on same screen using flutter bloc
Asked Answered
E

4

7

i have a screen with three widget [widgetA, widgetB, widgetC] and i have a bloc[BlocA] which is responsible for the data fetching and displaying on this screen i have three event [eventA, eventB, eventC] which render the widget [widgetA, widgetB, widgetC] and i have three state [stateA, stateB, stateC] which are responsible for managing state of widget [widgetA, widgetB, widgetC] i have attached all code to reproduce and test the case.

I am only able to display one state and their respective widget at a time whereas i want to display all three state and its widget based on their event. any help would be highly appreciated.

only way i tried to achieve the same is by making separate bloc and event class for each widget, but somehow i am not satisfied with this approach.

what would be the best approach to achieve this use case.

TestScreen

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:locus/blocs/test/testbloc.dart';

class TestScreen extends StatelessWidget {
  const TestScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
        create: (context) => TestBloc()..add(const TestEvent1()),
        child: Scaffold(
          appBar: AppBar(
            title: const Text('Test'),
          ),
          body: Stack(
            children: [
              Builder(builder: (context) {
                return Padding(
                  padding: const EdgeInsets.only(top: 0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      ElevatedButton(
                          onPressed: () =>
                              context.read<TestBloc>().add(const TestEvent1()),
                          child: const Text("Event1")),
                      const SizedBox(width: 10),
                      ElevatedButton(
                          onPressed: () => context
                              .read<TestBloc>()
                              .add(const TestEvent2(" event 2")),
                          child: const Text("Event2")),
                      const SizedBox(width: 10),
                      ElevatedButton(
                          onPressed: () => context
                              .read<TestBloc>()
                              .add(const TestEvent3(false)),
                          child: const Text("Event3")),
                    ],
                  ),
                );
              }),
              BlocBuilder<TestBloc, TestState>(
                builder: (context, state) {
                  if (state is TestState1) {
                    return const Center(child: Text("I am state 1"));
                  }
                  return const SizedBox.shrink();
                },
              ),
              Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  BlocBuilder<TestBloc, TestState>(
                    builder: (context, state) {
                      if (state is TestState2) {
                        return Center(
                            child: Text("I am state 2 ${state.message}"));
                      }
                      return const SizedBox.shrink();
                    },
                  ),
                  BlocBuilder<TestBloc, TestState>(
                    builder: (context, state) {
                      if (state is TestState3) {
                        return Center(
                            child: Text("I am state 3 ${state.check}"));
                      }
                      return const SizedBox.shrink();
                    },
                  ),
                ],
              ),
            ],
          ),
        ));
  }
}

TestBloc


import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:bloc/bloc.dart';
part 'test_state.dart';
part 'test_event.dart';

class TestBloc extends Bloc<TestEvent, TestState> {
  TestBloc() : super(TestInitializing()) {
    on<TestEvent1>((event, emit) => test1(event, emit));
    on<TestEvent2>((event, emit) => test2(event, emit));
    on<TestEvent3>((event, emit) => test3(event, emit));
  }

  Future test1(TestEvent1 event, Emitter<TestState> emit) async {
    try {
      emit(const TestState1());
    } catch (_) {}
  }

  Future test2(TestEvent2 event, Emitter<TestState> emit) async {
    try {
      emit(const TestState2(message: "Hello"));
    } catch (_) {}
  }

  Future test3(TestEvent3 event, Emitter<TestState> emit) async {
    try {
      emit(const TestState3(check: true));
    } catch (_) {}
  }
}

TestEvent


@immutable
abstract class TestEvent extends Equatable {
  const TestEvent();
}

class TestEvent1 extends TestEvent {
  const TestEvent1();
  @override
  List<Object> get props => [];
}

class TestEvent2 extends TestEvent {
  final String message;
  const TestEvent2(this.message);
  @override
  List<Object> get props => [message];
}

class TestEvent3 extends TestEvent {
  final bool check;
  const TestEvent3(this.check);
  @override
  List<Object> get props => [check];
}

TestState


@immutable
abstract class TestState extends Equatable {
  const TestState();
}

class TestInitializing extends TestState {
  @override
  List<Object> get props => [];
}

class TestState1 extends TestState {
  const TestState1();
  @override
  List<Object?> get props => [];
}

class TestState2 extends TestState {
  final String message;
  const TestState2({
    required this.message,
  });
  @override
  List<Object> get props => [message];
}

class TestState3 extends TestState {
  final bool check;
  const TestState3({
    required this.check,
  });
  @override
  List<Object> get props => [check];
}

testbloc barrel class

export 'test_bloc.dart';
Edgaredgard answered 11/2, 2022 at 8:6 Comment(0)
W
7

A bloc can only have one state at a time. If you want more states than that you'll have to either maintain a custom internal state mechanism inside TestBloc or create 3 separate TestBlocs and then provide each BlocBuilder with each TestBloc like so:

class TestScreen extends StatelessWidget {
  TestScreen({Key? key}) : super(key: key) {
  }

  final TestBloc bloc1 = TestBloc();
  final TestBloc bloc2 = TestBloc();
  final TestBloc bloc3 = TestBloc();

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
        create: (context) => TestBloc()..add(const TestEvent1()),
        child: Scaffold(
          appBar: AppBar(
            title: const Text('Test'),
          ),
          body: Stack(
            children: [
              Builder(builder: (context) {
                return Padding(
                  padding: const EdgeInsets.only(top: 0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      ElevatedButton(
                          onPressed: () =>
                              bloc1.add(const TestEvent1()),
                          child: const Text("Event1")),
                      const SizedBox(width: 10),
                      ElevatedButton(
                          onPressed: () => bloc2.add(const TestEvent2(" event 2")),
                          child: const Text("Event2")),
                      const SizedBox(width: 10),
                      ElevatedButton(
                          onPressed: () => bloc3.add(const TestEvent3(false)),
                          child: const Text("Event3")),
                    ],
                  ),
                );
              }),
              BlocBuilder<TestBloc, TestState>(
                bloc: bloc1,
                builder: (context, state) {
                  if (state is TestState1) {
                    return const Center(child: Text("I am state 1"));
                  }
                  return const SizedBox.shrink();
                },
              ),
              Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  BlocBuilder<TestBloc, TestState>(
                    bloc: bloc2,
                    builder: (context, state) {
                      if (state is TestState2) {
                        return Center(
                            child: Text("I am state 2 ${state.message}"));
                      }
                      return const SizedBox.shrink();
                    },
                  ),
                  BlocBuilder<TestBloc, TestState>(
                    bloc: bloc3,
                    builder: (context, state) {
                      if (state is TestState3) {
                        return Center(
                            child: Text("I am state 3 ${state.check}"));
                      }
                      return const SizedBox.shrink();
                    },
                  ),
                ],
              ),
            ],
          ),
        ));
  }
}

However making 3 separate blocs (TestBloc1, TestBloc2, TestBloc3) isn't necessaryly a bad way to go in regards to speration of concerns.

Wharfage answered 11/2, 2022 at 9:19 Comment(5)
thanks for your valuable response. your solution will work, to be precise i used the same approach in one of my project, but somehow i don't really like this approach and looking for better approach.Edgaredgard
@Edgaredgard u found any solution ?Nessim
@rahulshalgar yes, I found the solutions.Actually there are couple of ways of achieving multiple state from the same bloc.You can use buildwhen property of bloc builder to decide when to build the widget or you can use copy of bloc to emit different states and managing the state based on bloc you are emitting the state. Like if you have one bloc named testbloc with multiple state then you can define multiple copies of bloc using bloc provider and in bloc builder you can reference the bloc.Catch is that your bloc can emit one state at one time and based on your requirement you can manage state.Edgaredgard
thanks for your ans @CrenshawDk, i have one question if I have two APIs, and the second API depends on the first, mean first need to fetch data from the first API then take the ID from it need to request 2nd API and show both APIs Data on UI how to manage it with bloc?Broadway
I have one question if I have two APIs, and the second API depends on the first, mean first need to fetch data from the first API then take the ID from it need to request 2nd API and show both APIs Data on UI how to manage it with bloc?Broadway
L
1

I'm pretty late to the party but I've implemented a package that does exactly what you want !

Here you go : https://pub.dev/packages/multi_state_bloc

Lonna answered 7/9, 2022 at 12:3 Comment(1)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewSaval
E
1

You can try this code:

buildWhen: (previous, current) => current is TestState1 && previous != current.
Effectuate answered 7/12, 2022 at 8:7 Comment(0)
J
0

you can use build when which will build specific state based on constraints here an Ex of usage

state class

 class GetAllVariantsLoading extends FilterState {}

class GetAllVariantsFailure extends FilterState {
  final String errorMessage;

  GetAllVariantsFailure(this.errorMessage);
}

class GetAllVariantsSuccess extends FilterState {
  final AllVariantsResponse getAllVariantsResponse;
  final List<VariantValuesModel> allValues;

  GetAllVariantsSuccess(this.getAllVariantsResponse, this.allValues);
}

UI

@override

Widget build(BuildContext context) { final bloc = Modular.get();

return BlocBuilder<FilterBloc, FilterState>(
  buildWhen: (previous, current) {
    return current is GetAllVariantsLoading ||
        current is GetAllVariantsFailure ||
        current is GetAllVariantsSuccess;
  },
  bloc: bloc,
  builder: (context, state) {
    if (state is GetAllVariantsLoading) {
      return _buildLoading();
    } else if (state is GetAllVariantsFailure) {
      return _buildFailure(state);
    } else if (state is GetAllVariantsSuccess) {
      return  class GetAllVariantsLoading extends FilterState {}

class GetAllVariantsFailure extends FilterState {
  final String errorMessage;

  GetAllVariantsFailure(this.errorMessage);
}

class GetAllVariantsSuccess extends FilterState {
  final AllVariantsResponse getAllVariantsResponse;
  final List<VariantValuesModel> allValues;

  GetAllVariantsSuccess(this.getAllVariantsResponse, this.allValues);
}_buildSuccess(state, bloc);
    }  else {
      return  PleaseTryAgainMessage(
        title: state.toString(),
        callback:  () => bloc.add(GetAllVariantsEvent()),
      );
    }
  },
);

}

Jolty answered 18/9 at 7:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.