Reputation: 460
I'm building a tic-tak-toe app and I decided to learn BLoC for Flutter along. I hava a problem with the BlocBuilder
widget.
As I think about it. Every time Cubit/Bloc that the bloc builder widget listens to emits new state the bloc builder goes through this routine:
Call buildWhen
callback passing previous state as the previous
parameter and the newly emitted state as the current
parameter.
If the buildWhen
callback returned true then rebuild.
During rebuilding call the builder
callback passing given context as context
parameter and the newly emitted state as state
parameter. This callback returns the widget that we return.
So the conclusion is that the current
parameter of the buildWhen
call is always equal to the state
parameter of the builder
call. But in practice it's different:
BlocBuilder<GameCubit, GameState>(
buildWhen: (previous, current) => current is SetSlotSignGameState && (current as SetSlotSignGameState).slotPosition == widget.pos,
builder: (context, state) {
var sign = (state as SetSlotSignGameState).sign;
// Widget creation goes here...
},
);
In the builder
callback, it throws:
The following _CastError was thrown building BlocBuilder<GameCubit, GameState>(dirty, state: _BlocBuilderBaseState<GameCubit, GameState>#dc100): type 'GameState' is not a subtype of type 'SetSlotSignGameState' in type cast The relevant error-causing widget was: BlocBuilder<GameCubit, GameState>
The method where I emit the states that is in the GameCubit
class:
// [pos] is the position of the slot clicked
void setSlotSign(Vec2<int> pos) {
// Some code
emit(SetSlotSignGameState(/* Parameter representing the sign that is being placed in the slot*/, pos));
// Some code
emit(TurnChangeGameState());
}
Briefly about types of states. SetSlotSignGameState
is emitted when a user taps on a slot in the tic-tac-toe grid and the slot is empty. So this state means that we need to change sign in some slot. TurnChangeGameState
is emitted when we need to give the turn to the next player.
Temporary solution. For now I fixed it by saving the state from buildWhen
callback in a private field of the widget's state and then using it from the builder. BlocListener
also has this problem but there I can just move the check from listenWhen
callback into listen
callback. The disadvantage of this solution is that it's very inelegant and inconvenient.
Upvotes: 4
Views: 3744
Reputation: 840
buildWhen
is bypassed (not even called) on initial state OR when Flutter requests a rebuild.
I have created a small "test" to emphasize that:
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(BlocTestApp());
}
class BlocTestApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider<TestCubit>(
// Create the TestCubit and call test() event right away
create: (context) => TestCubit()..test(),
child: BlocBuilder<TestCubit, String>(
buildWhen: (previous, current) {
print("Call buildWhen(previous: $previous, current: $current)");
return false;
},
builder: (context, state) {
print("Build $state");
return Text(state);
},
),
),
);
}
}
class TestCubit extends Cubit<String> {
TestCubit() : super("Initial State");
void test() {
Future.delayed(Duration(seconds: 2), () {
emit("Test State");
});
}
}
OUTPUT:
I/flutter (13854): Build Initial State
I/flutter (13854): Call buildWhen(previous: Initial State, current: Test State)
As can be seen from output the initial state is built right away without calling buildWhen
. Only when the state changes buildWhen
is examined.
This behavior is also outlined here by the creator of Flutter Bloc library (@felangel):
This is expected behavior because there are two reasons for a BlocBuilder to rebuild:
- The bloc state changed
- Flutter marked the widget as needing to be rebuilt.
buildWhen will prevent builds triggered by 1 but not by 2. In this case, when the language changes, the whole widget tree is likely being rebuilt which is why the BlocBuilder is rebuilt despite buildWhen.
In your situation, based on the little code you revealed, is better to store the entire Tic-Tac-Toe configuration in the state and use BLOC events to alter it. In this way you do not need that buildWhen
condition.
OR make the check inside the builder
function if the logic let you do that (this is the most common used solutions with BLOC).
To respond to you question (if not clear so far :D): Sadly, you can not rely on buildWhen
to filter the state types sent to builder
function.
Upvotes: 6
Reputation: 156
Could you please check if SetSlotSignGameState extends the abstract class GameState
Upvotes: 0