Bloc: Widget is not rebuild after state changed

Created on 22 Jun 2020  路  2Comments  路  Source: felangel/bloc

Hello, im facing an issue, I read a lot but still did not find the solution.

I have a widget, which when I press it does a state change, in which case the widget rebuilds correctly.
But when I need to make an event change from somewhere else in the app, I see the event change in the log, but the widget doesn't rebuild.
This change of state is made by a class that listens to data from a bluetooth controller, so the change could occur at any time.

This is my widget
`
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:movekomapp/Utils/MyColors.dart';
import 'package:movekomapp/blocs/agua_blocs/bomba_agua_bloc.dart';
import 'package:movekomapp/responsive_ui/mi_positioned.dart';
import 'package:movekomapp/widgets/MyTextStyle.dart';

class BombaAgua extends StatelessWidget {
int widgetType;
String title;
BombaAgua(this.widgetType);
BombaAguaBloc bomabaAguaBloc;

@override
Widget build(BuildContext context) {
title = "Water Bomb";
bomabaAguaBloc = BlocProvider.of(context);
return WaterBomb_principal();
}

Widget WaterBomb_principal() {
String txtState;
return
BlocBuilder(
builder: (context, state) {
if (state.isEnabled) {
txtState = "ON";
} else {
txtState = "OFF";
}
return GestureDetector(
onTap: (){
if (state.isEnabled) {
bomabaAguaBloc.add(DisableBomba());
} else {
bomabaAguaBloc.add(EnableBomba());
}
},
child: Container(
margin: EdgeInsets.all(SC.all(7)),
width: 130,
height: 130,
decoration: new BoxDecoration(
color: MyColors.baseColor
),
child: Stack(
children: [
MyPositioned.fill(/// OFF / ON
left: 10,
bottom: 4,
child: Align(
alignment: Alignment.bottomLeft,
child: RichText(
text: TextSpan(
children: [
TextSpan(
style: MyTextStyle.estilo(
14, colorIcon),
text: txtState),
]
)
),
),
),
])
),
);
}
);
}
}`

This is the bloc

`import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:movekomapp/MapeoIDs/IdComponents.dart';
import 'package:movekomapp/bluetooth/BluetoothRepository.dart';

abstract class BombaAguaEvent extends Equatable {
BombaAguaEvent([List props = const []]) : super(props);
}

class EnableBomba extends BombaAguaEvent { /// Habilita la bateria
bool isEnabled = true;

@override
List get props => [isEnabled];
}

class DisableBomba extends BombaAguaEvent { /// Deshabilita la bateria
bool isEnabled = false;

@override
List get props => [isEnabled];
}
/// Fin declaracion de eventos

class BombaAguaState extends Equatable {
final bool isEnabled;
double valueAmp;

BombaAguaState({
@required this.isEnabled,
@required this.valueAmp,

}) : super([isEnabled,valueAmp]);

/// Valores iniciales
factory BombaAguaState.initial() {
return BombaAguaState(
isEnabled: true,
valueAmp: 2.65,
);
}

factory BombaAguaState.disabled() {
return BombaAguaState(
isEnabled: false,
valueAmp: 0.0,
);
}

BombaAguaState copyWith({
bool isEnabled,
double valueAmp,
}) {
return BombaAguaState(
isEnabled: isEnabled ?? this.isEnabled,
valueAmp: valueAmp ?? this.valueAmp,
);
}
@override
List get props => [isEnabled,valueAmp];
}
/// FIN declaracion de STATE

class BombaAguaBloc extends Bloc {
var ID = IdComponent.BOMBA_AGUA;
@override
// TODO: implement initialState
BombaAguaState get initialState => BombaAguaState.initial();

@override
Stream mapEventToState(BombaAguaEvent event) async* {
if (event is EnableBomba) {
yield* _setValuesAndSendBT(true);
// yield* _setInitValuesAndSendBT(event);
} else if (event is DisableBomba) {
yield* _setValuesAndSendBT(false);
}
}

Stream _setValuesAndSendBT(bool status) async* {
print("_setInitValuesAndSendBT");
List elems = new List();
elems = generateData(status);
try {
BluetoothRepository.sendData(elems);
if (status)
yield BombaAguaState.initial();
else
yield BombaAguaState.disabled();
} catch(e) {
print(e);
}
}
}`

And this is the place where i need to dispatch an event, in log im seeing the state changes but the widget is not rebuilding, how can i notify the widget that need to be rebuild.
pd: i canot use context in this class because it is out fo the tree of widgets.

`import 'package:movekomapp/blocs/agua_blocs/bomba_agua_bloc.dart';

class BackgroundDataProcessor {
BackgroundDataProcessor() {}

void executeAction(){
print("executeAction");
BombaAguaBloc bomabaAguaBloc = BombaAguaBloc();
bomabaAguaBloc.add(DisableBomba());
bomabaAguaBloc.close();

}

}`

question

All 2 comments

Hi @MeliGualaX 馃憢
Thanks for opening an issue!

Several suggestions for things to improve are:

  1. Avoid maintaining state inside of a StatelessWidget's build. In your case txtState is a mutable variable being recreated each time the widget is rebuilt. You should probably remove that variable altogether and just use the bloc's state inside of the BlocBuilder directly.

  2. Your event and state classes should all be immutable meaning all member variables should be final.

  3. You need to inject the same bloc instance into the BackgrounDataProcessor. The current approach won't work because you're creating and adding events to a totally different instance of BombaAguaBloc and the UI is reacting to state changes in a separate instance. You should inject the same instance via the constructor of BackgroundDataProcess instead of creating one internally.

Hope that helps 馃憤

Closing for now but feel free to comment with additional questions and I'm happy to continue the conversation. Also please check out the core concepts if you haven't already 馃憤

Hi @MeliGualaX 馃憢

Besides things pointed out by @felangel I will also suggest to move business logic out of UI and into bloc:

onTap: (){
if (state.isEnabled) {
bomabaAguaBloc.add(DisableBomba());
} else {
bomabaAguaBloc.add(EnableBomba());
}

You could just add a single event here and process it inside bloc where you already have access to isEnabled.

Was this page helpful?
0 / 5 - 0 ratings