I am building a Flutter Flame game and am trying to have a single action add events to two different blocs. For example, when the character picks up an item I want it to go into an InventoryBloc
and increment a counter in GameStatsBloc
(for total lifetime items collected).
The documentation examples show things like (copied from here):
class Player extends PositionComponent
with FlameBlocListenable<PlayerInventoryBloc, PlayerInventoryState> {
@override
void onNewState(state) {
updateGear(state);
}
}
but explicitly states that you can't use multiple blocs that way. Similarly, if I try to use multiple FlameBlocListener
s they don't provide access to the relevant blocs to add events (they are listeners after all).
Is there a clean way to have a single component interact with multiple blocs? The only way I can think of doing it is making a dummy parent component that uses FlameBlocListenable<InventoryBloc, InventoryState>
that passes the bloc to a child that uses FlameBlocListenable<GameStatsBloc, GameStatsState>
, e.g.:
class InventoryProviderComponent extends Component with FlameBlocListenable<InventoryBloc, InventoryState> {
Future<void> onLoad() async {
add(Player(inventoryBloc: bloc));
}
}
class Player extends PositionComponent with FlameBlocListenable<GameStatsBloc, GameStatsState> {
Player({required this.inventoryBloc});
final InventoryBloc inventoryBloc;
...
// Can now interact with bloc and inventoryBloc as needed to add events
}
This feels pretty awkward to me and would become increasingly cumbersome if more blocs were needed (though that might indicate other issues with the structure of the code).
Your problem reminds me at Mediator (design pattern)
I recommend you to create an intermediary class, like GameBlocMediator, to handle interactions with BLoCs. Also, since you don't have access to the BuildContext in onLoad, use the GetIt package for dependency injection.
class GameBlocMediator {
final InventoryBloc inventoryBloc;
final GameStatsBloc gameStatsBloc;
GameBlocMediator({
required this.inventoryBloc,
required this.gameStatsBloc,
});
void onItemPickedUp(Item item) {
inventoryBloc.add(AddItemEvent(item));
gameStatsBloc.add(IncrementCounterEvent());
}
}
Create a file like services_locator.dart
final GetIt getIt = GetIt.instance;
void setupServiceLocator() {
getIt.registerLazySingleton<InventoryBloc>(() => InventoryBloc());
getIt.registerLazySingleton<GameStatsBloc>(() => GameStatsBloc());
getIt.registerLazySingleton<GameBlocMediator>(() => GameBlocMediator(
inventoryBloc: getIt<InventoryBloc>(),
gameStatsBloc: getIt<GameStatsBloc>(),
));
}
Initialize GetIt in main.dart
void main() {
setupServiceLocator();
...
runApp(...);
}
You can inject your Mediator in your Player Component
class Player extends PositionComponent {
final GameBlocMediator mediator = getIt<GameBlocMediator>();
void pickUpItem(Item item) {
mediator.onItemPickedUp(item);
}
}