Yaser Hesham
Yaser Hesham

Reputation: 407

emit state from listening to stream inside a bloc doesn't work

Im using StreamController to track the progress of uploading an image using Dio, so when i sink a value in my stream i trigger an event to emit new state to rebuild my widget, the problem is it does not emit new state.

This is the code from my ChatBloc

class Chat extends Bloc<ChatEvent, ChatState> {
  final StreamController<FileDetails> uploadStream =
      StreamController<FileDetails>();

  Chat(this.chatUseCases, this.chat)
      : super(const SingleChatState()) {
    on<ChatEmitUploadList>(_emitUploadList);

    /// listen to changes form upload stream
    uploadStream.stream.listen((event) {
      List<FileDetails> details = state.fileDetailsList;
      details
          .firstWhereOrNull((element) => element.id == event.id)!
          .percentage = event.percentage;
      add(SingleChatEvent.emitUploadList(fileDetails: event));
    });
  }
}

  FutureOr<void> _emitUploadList(
      ChatEmitUploadList event, Emitter<SingleChatState> emit) {
    print('emit once');
    List<FileDetails> details = List.from(state.fileDetailsList);
    details
        .firstWhereOrNull((element) => element.id == event.fileDetails.id)!
        .percentage = event.fileDetails.percentage;
    emit(state.copyWith(fileDetailsList: details));
  }

This is my BlocBuilder

class UploadingWidget extends StatelessWidget {
  const UploadingWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ChatBloc, ChatState>(
      builder: (context, state) {
        if (state.status == SingleChatStatus.uploadingFiles) {
          return Container(
            decoration: BoxDecoration(
                color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
            margin: const EdgeInsets.all(8),
            padding: const EdgeInsets.all(8),
            child: CustomScrollView(
              shrinkWrap: true,
              slivers: [
                SliverGrid.builder(
                  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
                      maxCrossAxisExtent: 100),
                  itemBuilder: (context, index) => Material(
                    child: SizedBox(
                      height: 100,
                      width: 100,
                      child: Center(
                        child: Text(
                            state.fileDetailsList[index].percentage.toString()),
                      ),
                    ),
                  ),
                  itemCount: state.fileDetailsList.length,
                )
              ],
            ),
          );
        }
        return const SizedBox.shrink();
      },
    );
  }
}

This is my method in Dio

final response = await dio.post(BASE_TEST+sendMessageEndPoint,data: data,
onSendProgress: (count, total) {
  if(file!=null){
    String fileName = file.path.toString().split('/').last;
    double percentage = (count / total * 100).toDouble();
    uploadStream.sink.add(FileDetails(id: file.path!, percentage: percentage,file: file));
  }
},);

This is my debug console

[log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails')
[log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails')
[log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails')
[log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails')
[log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails')

Upvotes: 5

Views: 1671

Answers (2)

Loren.A
Loren.A

Reputation: 5595

That StreamController you're creating in the Chat class is not the same instance as the one you're adding to in the Dio method. You're creating a stream, then listening to it, but never adding to that actual stream.

You need to listen to the stream that the Dio is actually updating.

Below is an incomplete example but should get you going in the right direction. The preferred way to listen to a Stream in bloc is to use emit.forEach then return a new state on each Stream update.

This requires that your Dio method returns a Stream or if there is a repository class in between, then have that return a Stream, that then gets passed into the stream argument of emit.forEach

class Chat extends Bloc<ChatEvent, ChatState> {
  Chat(this.chatUseCases, this.chat) : super(const SingleChatState()) {
    on<ChatEmitUploadList>(_emitUploadList);
    on<ChatInitStreamListener>(_onChatInitStreamListener); // adding this event

    add(ChatInitStreamListener()); // call this event from constructor
  }

  FutureOr<void> _onChatInitStreamListener(event, Emitter<ChatState> emit) {
    final fileStream = // stream from the Dio method

    emit.forEach(fileStream, onData: (FileDetails fileDetails) {
      // handle each stream update here and return a new ChatState
      // if no changes in state are required then `return state;`
    });
  }
}

Upvotes: 3

Mario carlos chita
Mario carlos chita

Reputation: 56

I think the problem i when you add the event add(SingleChatEvent.emitUploadList(fileDetails: event)).on the construct of chat there is not register of a method which implements SingleChatEvent. , there should have a method in the chat construct that implements the SingleChatEvent event.

Upvotes: 1

Related Questions