avinash
avinash

Reputation: 521

How to manage multiple state on same screen using flutter bloc

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';

Upvotes: 7

Views: 18451

Answers (5)

Mostafa Alazhariy
Mostafa Alazhariy

Reputation: 369

You can use buildWhen parameter in BlocBuilder to control it to rebuild the widget only if a given condition is met.

ex: buildWhen: (previous, current) => current is TestState2 && previous != current,.

The first param in buildWhen presents the previous state, and the one other presents the current emitted state.

i.e:

        BlocBuilder<TestBloc, TestState>(
          builder: (context, state) {
            if (state is TestState2) {
              return Center(
                  child: Text("I am state 2 ${state.message}"));
            }
            return const SizedBox.shrink();
          },
          buildWhen: (previous, current) => current is TestState2 && previous != current, // Add this line
        ),
        BlocBuilder<TestBloc, TestState>(
          builder: (context, state) {
            if (state is TestState3) {
              return Center(
                  child: Text("I am state 3 ${state.check}"));
            }
            return const SizedBox.shrink();
          },
          buildWhen: (p, c) => c is TestState3 && p != c, // Add this line
        ),

Upvotes: 0

Ahmed Farouk
Ahmed Farouk

Reputation: 1

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()),
      );
    }
  },
);

}

Upvotes: 0

DuocNP
DuocNP

Reputation: 64

You can try this code:

buildWhen: (previous, current) => current is TestState1 && previous != current.

Upvotes: 2

Lucas Benghida
Lucas Benghida

Reputation: 11

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

Upvotes: 1

CrenshawDK
CrenshawDK

Reputation: 367

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.

Upvotes: 7

Related Questions