0

when I add an event from a stateless widget by using BlocProvider.of<>, it really adds event and yield state, and BlocBuilder work and change UI,

But, when adding an event from a separate class, it really adds an event to the bloc and onTransition work, but not yield a new state, and BlocBuilder not work to change UI.

the main :

main(){
return runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
 MQTTManager x = MQTTManager();
 return MaterialApp(
   home: BlocProvider(
     lazy: false,
     create:(context)=>MqttblocBloc(MQTTManager())..add(StartConnect()) ,
     child:Home() ,
   ),
 );
}
}

the bloc :

class MqttblocBloc extends Bloc<MqttblocEvent, MqttblocState> {
  MQTTManager manager = MQTTManager() ;
  MqttblocBloc(this.manager) : super(MqttblocInitial());

 @override
 Stream<MqttblocState> mapEventToState(
   MqttblocEvent event,
 ) async* {
   if(event is StartConnect){
    try{
      manager.initializeMQTTClient();
      manager.connect();
      yield MqttblocInitial();
    }catch(e){
      print(e);
    }
   }

   else if(event is ConnectedEvent) {
     try{
       print('inBloc connect....');
       yield ConnectedState(MQTTManager.s);
     }catch(e){
       print(e);
     }
   }


   else if(event is PublishedEvent){
     try{
       manager.publishsw1('on');
       print('inBloc publish........');
       yield PublishState(manager.getText1());
     }catch(e){
       print(e);
     }
   }

   else if(event is DisconnectedEvent) {
     try{
       print('inBloc And Disconnnnn....');
       yield DisconnectState(MQTTManager.u);
     }catch(e){
       print(e);
     }
   }

 }
 @override
 void onTransition(Transition<MqttblocEvent, MqttblocState> transition) {
   super.onTransition(transition);
   print(transition);
 }
}

and here separate class where I listen to server and add events to bloc :

class MQTTManager {

  MqttblocBloc bloc ;

  static var s ;
  static var u ;
  MqttServerClient client;
  String text ;
  String text1 ;
  String text2 ;
  static List<String> conn = [] ;

  void initializeMQTTClient(){
    client = MqttServerClient("broker.shiftr.io","User");
    client.port = 1883;
    
    client.secure = false;
    client.logging(on: true);
    client.onConnected = onConnected;

    final MqttConnectMessage connMess = MqttConnectMessage()
        .authenticateAs('889514b9', 'd5459e3f6b0422cb')
        .withClientIdentifier("User")
        .withWillTopic('willtopic') 
        .withWillMessage('My Will message')
        .startClean() // Non persistent session for testing
        .withWillQos(MqttQos.atLeastOnce);
    print('EXAMPLE::Mosquitto client connecting....');
    client.connectionMessage = connMess;

  }
  // Connect to the host
  void connect() async{
    assert(client != null);
    try {
      print('EXAMPLE::Mosquitto start client connecting....');
      await client.connect();
      Amar(); // <...... here calling this fun to start listen to Server
    } on Exception catch (e) {
      print('EXAMPLE::client exception - $e');
      disconnect();
    }
  }

  void disconnect() {
    print('Disconnected');
    client.disconnect();
  }

  void publishsw1(String message){
    final MqttClientPayloadBuilder builder = MqttClientPayloadBuilder();
    builder.addString(message);
    client.publishMessage('hello/sw1', MqttQos.exactlyOnce, builder.payload);

  }
  void onConnected() {

    print('EXAMPLE::shiftr client connected....');

    client.subscribe("hello/sw1", MqttQos.atLeastOnce);

    client.updates.listen((List<MqttReceivedMessage<MqttMessage>> c) {
      final MqttPublishMessage recMess = c[0].payload;
      final String pt =
      MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
      setText(pt);

    });

  }

   Amar() { //<....... here h listen to server 
     bloc =  MqttblocBloc(this);
     client.subscribe("\$events", MqttQos.atLeastOnce);

    client.updates.listen((List<MqttReceivedMessage<MqttMessage>> c) {
      final MqttPublishMessage recMess = c[0].payload;
      final String pt =
      MqttPublishPayload.bytesToStringAsString(recMess.payload.message);

      var z =  DetectEvent.fromJson(json.decode(pt));
      if(z.type == 'connected'){
        var connected = Connected.fromJson(json.decode(pt));
        print (connected.type);
        bloc.add(ConnectedEvent()); // <... here I add event to Bloc , but it not changeing UI  

      }
      else if(z.type == 'disconnected'){
        var disconnected = Disconnected.fromJson(json.decode(pt));
        print (disconnected.type) ;
        bloc.add(DisconnectedEvent()); // <... here I add event to Bloc , but it not changeing UI  

      }
      else if(z.type == 'published'){
        var published = Published.fromJson(json.decode(pt));
        print(published.type) ;
      }
}
}

and that is a stateless widget and use blocbuider :

class Home extends StatelessWidget {
MQTTManager v = MQTTManager();
  @override
  Widget build(BuildContext context) {
    MqttblocBloc p = BlocProvider.of<MqttblocBloc>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc MQTT'),
        actions: [
          Row(
            children: [
              IconButton(
                icon: Icon(Icons.wb_incandescent,
                ),
                onPressed: (){
                 p.add(PublishedEvent());//<.... here it change UI ,
                },
              ),
            ],
          )
        ],
      ),
      body: Center(
        child: BlocBuilder<MqttblocBloc,MqttblocState>(
         // buildWhen: (previuosState , currentState)=>currentState.runtimeType !=previuosState.runtimeType,

          builder:(context , state){
            if(state is MqttblocInitial){
              return CircularProgressIndicator();
            }
            else if(state is ConnectedState){
              return IconButton(
                icon: Icon(Icons.wifi),
                onPressed: (){},
              );
            }
            else if(state is PublishState){
              return RaisedButton(
                child: Text('${state.x}'),
                onPressed: (){},
              );
            }
            else if(state is DisconnectState){
              return IconButton(
                icon: Icon(Icons.wb_incandescent),
                onPressed: (){
                },
              );
            }
            return CircularProgressIndicator();
          } ,
        ),
      )
    );
  }
}

bloc State :

@immutable
abstract class MqttblocState extends Equatable {
 const MqttblocState();
  @override
  List<Object> get props => [];

}

class MqttblocInitial extends MqttblocState {}

class ConnectedState extends MqttblocState{
  final String x ;
  ConnectedState(this.x);
  @override
  List<Object> get props => [x];
}
class PublishState extends MqttblocState{
  final String x ;
  PublishState(this.x);
  @override
  List<Object> get props => [x];
}

class DisconnectState extends MqttblocState{
  final String x ;
  DisconnectState(this.x);
  @override
  List<Object> get props => [x];
}

and bloc events

@immutable
abstract class MqttblocEvent extends Equatable  {
  MqttblocEvent();
  @override
  List<Object> get props => [];
}

class StartConnect extends MqttblocEvent{}

class ConnectedEvent extends MqttblocEvent{}

class PublishedEvent extends MqttblocEvent{}

class DisconnectedEvent extends MqttblocEvent{}


  • not sure, but could it be that you create a second instance of that bloc? You would have to pass the bloc instance as an argument, in this case – w461 Nov 17 '20 at 21:59
  • Where the second instance of the bloc , just take it in function of Amar() , to able receive events which come from a server to add that events to bloc , and that event actually converted to states and transition work, but not rebuild the UI corresponding to states.... i hope to help plz – ahmed zahran Nov 17 '20 at 22:48

2 Answers2

1

The UI won't rebuild upon sending the same state again. You need to send some other state first. So for any event, your mapping should look like this:

if(event is StartConnect){
    yield MqrrblocInProgress(); // <============== ADDED
    try{
      manager.initializeMQTTClient();
      manager.connect();
      yield MqttblocInitial();
    }catch(e){
      print(e);
    }

Of course, you need to define this state (InProgress), too, as well as define some widget in the UI for this state (eg spinning wheel)

Hamed
  • 5,867
  • 4
  • 32
  • 56
w461
  • 2,168
  • 4
  • 14
  • 40
  • The same problem still exists , i add other state and yield it so far every event and I define widget for that state , I will show u transition : I/flutter (29067): Transition { currentState: Instance of 'MqttblocInitial', event: Instance of 'ConnectedEvent', nextState: Instance of 'MqttInProgress' } I/flutter (29067): inBloc connect.... I/flutter (29067): Transition { currentState: Instance of 'MqttInProgress', event: Instance of 'ConnectedEvent', nextState: Instance of 'ConnectedState' } – ahmed zahran Nov 18 '20 at 17:08
  • Can you explain more, if possible, please? – ahmed zahran Nov 18 '20 at 21:33
  • you initialize your bloc in Amar. I guess, Home, is not a sub-widget of Amar. So it might not find it. Try putting `MqttblocBloc bloc = MqttblocBloc(this);` in `MyApp` and either pass it to the widgets or - as you did - use `MqttblocBloc p = BlocProvider.of(context);` – w461 Nov 19 '20 at 09:42
  • Not work also,I think you have put your hand on the main problem because the Home has context, `MqttblocBloc p = BlocProvider.of(**context**);` the context is a reference of widget tree and within it I can add to Bloc and change UI, but Amar() is a function in a certain class , that class not sub class of widget tree to access to UI and change it , So if I add event to Bloc from Home it really rebuild UI , but if I add event to Bloc from Amar() , the UI not response and never change , With the note that transition work Correctly even if I add event to Bloc from Amar(). – ahmed zahran Nov 19 '20 at 14:46
  • Then you should try passing the bloc as an argument to Amar. Actually Amar looks more like a function to me, if this is the full code. – w461 Nov 19 '20 at 16:39
  • That's how you mean `Amar(MqttblocBloc bloc) ` , then what ? ... Is there another solution related with BlocProvider in MyApp widget? – ahmed zahran Nov 19 '20 at 17:25
  • Hard to tell without the full code, and even then, not sure if I have sufficient knowledge on that. Based on the assumption that you currently have a somewhere - possible loose - hanging function Amar(), I would create an app wide bloc which hosts the function Amar (so defined within that bloc class). Then you access this bloc from anywhere in the widget tree with events etc and as such use Amar() through that bloc. With your current design I assume that you will loose the instance of Amar all the time and re-create a new one – w461 Nov 19 '20 at 17:57
  • Sorry for my late response, the internet in my country is bad , Simply suppose I have a class with a function that listens to specific events. Every event occurs inside the function. I want to update the UI, as the general idea is how to add an event from a class that does not follow a widget tree. I thought about using `GlopalKey`, but how? I thought it was as easy as adding an event from anywhere in the code and simply changing the UI, or I misunderstood the BLOC function. – ahmed zahran Nov 22 '20 at 11:18
  • I guess what you describe is typically a class called bloc. You will need to ensure that the class (the one hosting Amar) keeps alive during the life cycle of the app. To be sure and also because it typically makes sense to stick with common approaches, I would use an app wide bloc to do so. When I needed to return an event to an upstream bloc from the downstream bloc, I added the event in the ui based on a downstream state (as explained in the link I provided to you). – w461 Nov 23 '20 at 11:12
  • i edit the code to implement the full code , showing that class has Amar function , please read it , and where is the link you provided to me ?thanks for your efforts – ahmed zahran Nov 23 '20 at 17:23
  • replace `create:(context)=>MqttblocBloc(MQTTManager())` with `create:(context)=>MqttblocBloc(x)`. https://stackoverflow.com/questions/64011595/flutter-inter-bloc-communication-passing-data-events-between-different-blocs – w461 Nov 23 '20 at 20:09
  • i did it before , not work also .. Ok what about using `get_it`? – ahmed zahran Nov 23 '20 at 20:21
  • not sure, what happens here: `class MqttblocBloc extends Bloc { MQTTManager manager = MQTTManager() ; MqttblocBloc(this.manager) : super(MqttblocInitial());` typically you would declare `final MQTTManager manager;` to declare a parameter – w461 Nov 24 '20 at 07:24
1

here is why, You're yielding the same state and using Equatable without being any different props to compare to so the BlocBuilder is not seeing any change.

You have two solutions ether un-inherit Equatable from class MqttblocEvent:

 abstract class MqttblocEvent {
  MqttblocEvent();
}

Or yield different state in-between like MqrrblocInProgress (Recommended) :

 Stream<MqttblocState> mapEventToState(
   MqttblocEvent event,
 ) async* {
    yield MqrrblocInProgress();
.....
AmrNRD
  • 11
  • 2