Reputation: 521
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
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
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
Reputation: 64
You can try this code:
buildWhen: (previous, current) => current is TestState1 && previous != current.
Upvotes: 2
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
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