Bloc: UI not getting updated on changes in the state

Created on 18 Oct 2020  路  5Comments  路  Source: felangel/bloc

Describe the bug
I am trying to make something similar to flutter_wtih_streams. I have made my own stream connected to a gRPC server and I want to be able to show the changes in data in realtime on the UI, the problem is that even though when I print the values in console, it shows the change but the same is not reflected in the UI.

Code sample
main.dart

import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:ventilator_software_flutter/monitor_bloc/monitor_bloc.dart';
import 'package:ventilator_software_flutter/grpc_client/client.dart';
import 'package:ventilator_software_flutter/models/DataModel.dart';

import 'package:ventilator_software_flutter/screens/chart.dart';

void main() {
  EquatableConfig.stringify = kDebugMode;
  runApp(VentMonitorApp());
}

class VentMonitorApp extends MaterialApp {
  VentMonitorApp({Key key})
      : super(
          key: key,
          home: BlocProvider(
            create: (_) => MonitorBloc(Client('10.0.2.2', 8080)),
            child: MainPage(),
          ),
        );
}

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Vent Monitor'),
      ),
      body: BlocBuilder<MonitorBloc, MonitorState>(
        builder: (context, state) {
          if (state is MonitorDataPresent) {
            // var toPlot = convertPoints(state.ventDataPoints);
            // return SeriesTimeChart(toPlot);
            return Center(
              child: Text("${state.ventDataPoints.length}"),
            );
          }
          return const Center(
            child: Text('The ventilator is offline Press button to start receiving data'),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.bloc<MonitorBloc>().add(MonitorStarted()),
        tooltip: 'Start',
        child: const Icon(Icons.timer),
      ),
    );
  }
}

The stream

Stream<VentData> runMonitor() async* {
        final monitorRequest = MonitorRequest()..id = this.ventilatorId;
        print("Executing stream listening");
        await for (var dataPoint in this.stub.monitor(monitorRequest)) {
            print("executing this thing");
            ParamConfig data = dataPoint.status;
            yield new VentData(
                tidalVolume: data.tidalVolume,
                respiratoryRate: data.respiratoryRate,
                modeOfVentilation: data.modeOfVentilation.toString(),
                fio2: data.fio2,
                inspiratoryTime: data.inspiratoryTime,
                peep: data.peep,
                timestamp: dataPoint.timestamp
            );
        }
    }

The monitor_bloc.dart

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';

import 'package:ventilator_software_flutter/models/DataModel.dart';
import 'package:ventilator_software_flutter/grpc_client/client.dart';

part 'monitor_event.dart';

part 'monitor_state.dart';

class MonitorBloc extends Bloc<MonitorEvent, MonitorState> {
  MonitorBloc(this._grpcClient) : super(MonitorInitial());

  final Client _grpcClient;
  StreamSubscription _subscription;

  @override
  Stream<MonitorState> mapEventToState(
    MonitorEvent event,
  ) async* {
    if (event is MonitorStarted) {
      await _subscription?.cancel();
      _subscription = _grpcClient
          .runMonitor()
          .listen((dataPoint) => add(_NewVentData(dataPoint)));
    }
    if (event is _NewVentData) {
      final currentState = state;
      List<VentData> ventDataPoints = [];
      if (currentState is MonitorDataPresent) {
        ventDataPoints = currentState.ventDataPoints;
      }
      print(event.dataPoint.peep);
      ventDataPoints.add(event.dataPoint);
      yield MonitorDataPresent(ventDataPoints);
    }
  }

  @override
  Future<void> close() {
    print("calling close");
    _subscription?.cancel();
    return super.close();
  }
}

When I print the data using print(event.dataPoint.peep);, I get the following lines in the console:

I/flutter ( 7928): 48
I/flutter ( 7928): executing this thing
I/flutter ( 7928): 35
I/flutter ( 7928): executing this thing
I/flutter ( 7928): 11
I/flutter ( 7928): executing this thing
I/flutter ( 7928): 37
I/flutter ( 7928): executing this thing
I/flutter ( 7928): 26
I/flutter ( 7928): executing this thing
I/flutter ( 7928): 41
I/flutter ( 7928): executing this thing
I/flutter ( 7928): 8

But the UI is constant on length 1. Earlier versions used to have a BlocDelegate which we could override to easily debug, I do not know how to do that anymore..... Please help.

question

All 5 comments

If anyone wants to have a look at the full code, they can go and see https://github.com/amartya-dev/ventilator_software_flutter

Hi @amartya-dev 馃憢
Thanks for opening an issue!

I believe the issue is you are mutating the state rather than emitted a new instance of the state each time.

If you change

if (currentState is MonitorDataPresent) {
  ventDataPoints = currentState.ventDataPoints;
}

to

if (currentState is MonitorDataPresent) {
  ventDataPoints = List.of(currentState.ventDataPoints);
}

I believe that should solve the issue. Let me know if that helps 馃憤

Please refer to the FAQs for more information.

Closing for now but if you have additional questions let me know and I'm happy to continue the conversation 馃槃

Yes you are right, I was resetting the state every time and basically returning an empty list there, I changed the code to actually copy all the points and then add a new one, you think there can be optimization on that?

List<VentData> ventDataPoints = [];
      if (currentState is MonitorDataPresent) {
        if (currentState.ventDataPoints.length < 20) {
          ventDataPoints.addAll(currentState.ventDataPoints);
        } else {
          ventDataPoints.addAll(currentState.ventDataPoints
              .sublist(1, currentState.ventDataPoints.length));
        }
      }
      ventDataPoints.add(event.dataPoint);
      yield MonitorDataPresent(ventDataPoints);

Also, I have tried to maintain the length at 20, if you think there is a better way to do this, please let me know :) Thank you very much for your help

No problem!

You should be able to do something like:

if (currentState is MonitorDataPresent) {
  yield MonitorDataPresent(
    currentState.ventDataPoints.length < 20
      ? List.of(currentState.ventDataPoints)
      : currentState.ventDataPoints.take(20)
  );
  return;
}

yield MonitorDataPresent([event.dataPoint]);

Hope that helps 馃憤

Yeah I get your point, the thing is I wanted to be able to add the datapoint to the current list and remove the first one, though it is fine, I get the thing. Thank you very much for your help :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nerder picture nerder  路  3Comments

abinvp picture abinvp  路  3Comments

shawnchan2014 picture shawnchan2014  路  3Comments

Reidond picture Reidond  路  3Comments

wheel1992 picture wheel1992  路  3Comments