felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.71k stars 3.38k forks source link

refactor: update emit method annotations #3043

Closed zeshuaro closed 2 years ago

zeshuaro commented 2 years ago

Description

Would you be able to explain the reason for annotating the emit method with @protected (source)?

I have been using a very large cubit (with lots of methods/events). To make the code easier to read, I split it up by using separate files and extensions. For example:

import 'package:bloc/bloc.dart';

class MainCubit extends Cubit<MainState> {
  MainCubit() : super(MainState());

  void foo() {
    emit(state);
  }
}

class MainState {}

extension SubCubit on MainCubit {
  void boo() {
    emit(state);
  }
}

This has been working fine until the latest update to v8.0.0, where the emit method is now marked with @protected. The analysis tool now complains that the emit method cannot be used in the extension class.

I can ignore this linting rule but I'll rather not if this can be updated in the bloc package. If not, is there another workaround for this use case?

felangel commented 2 years ago

Hi @zeshuaro 👋 Thanks for opening an issue!

emit is intended for use only within the bloc/cubit instance itself. I highly advise against using extensions here and instead consider using something like a mixin:

class MainCubit extends Cubit<MainState> {
  MainCubit() : super(MainState());

  void foo() {
    emit(state);
  }
}

mixin BooMixin on MainCubit {
  void boo() {
    emit(someState);
  }
}

Let me know if that helps and sorry for any inconvenience!

zeshuaro commented 2 years ago

Thanks for your reply and suggestion. Also there's no need to apologise.

Using a mixin did help me to get around with the error messages. But this is my first time using mixin and I have a follow-up question.

For the code below, I could call the method boo within context.read<FooCubit>() previously with extension, but I can't do it now with mixin. Would you be able to have a look and see what am I missing?

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

class FooCubit extends Cubit<FooState> {
  FooCubit() : super(FooState());

  void foo() {
    emit(state);
  }
}

class FooState {}

mixin BooCubit on FooCubit {
  void boo() {
    emit(state);
  }
}

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

  @override
  Widget build(BuildContext context) {
    return BlocListener<FooCubit, FooState>(
      listener: (context, state) {
        context.read<FooCubit>().boo();
      },
      child: Container(),
    );
  }
}
felangel commented 2 years ago
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class FooCubit extends Cubit<FooState> {
  FooCubit() : super(FooState());

  void foo() {
    emit(state);
  }
}

class FooState {}

mixin BooCubit on FooCubit {
  void boo() {
    emit(state);
  }
}

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

  @override
  Widget build(BuildContext context) {
    return BlocListener<FooCubit, FooState>(
      listener: (context, state) {
        context.read<FooCubit>().boo();
      },
      child: Container(),
    );
  }
}

Yup you'd just need to use context.read<BooCubit>().boo() instead. Let me know if that helps 👍

felangel commented 2 years ago

Closing for now but feel free to comment with any additional questions and I'm happy to continue the conversation 👍

zeshuaro commented 2 years ago

I tried your suggestion with context.read<BooCubit>().boo() but I got the following error:

 Could not find the correct Provider<BooCubit> above this BlocBuilder

And I couldn't initiate a BooCubit instance to the BlocProvider as it's an mixin so I'm not sure how to make BooCubit available for the lookup.

In addition, I face a similar issue when trying to update the unit tests with using the mixin. Again I can't call boo from the cubit. If I update the code to blocTest<BooCubit, FooState>, then I need to initiate BooCubit which is not possible as it's a mixin.

void main() {
  late FooCubit cubit;

  setUp(() {
    cubit = FooCubit();
  });

  group('boo test', () {
    blocTest<FooCubit, FooState>(
      'should emit boo state',
      build: () => cubit,
      act: (cubit) => cubit.boo(),
      expect: () => [FooState()],
    );
  });

Do you have any thoughts on this?