0

I'm using flutter_bloc to manage the states of my app, and get_it to inject the needed dependencies following the idea suggested by the Reso Coder's Flutter Clean Architecture Proposal.

Everything is working fine except that the bloc is not changing its state (it's stuck in the initial state)

Here is the code of the involved classes:

The States

abstract class PaintingsState extends Equatable {
  final properties = const <dynamic>[];

  PaintingsState([properties]);

  @override
  List<Object> get props => [properties];
}

class PaintingsLoading extends PaintingsState {}

class PaintingsLoaded extends PaintingsState {
  final PaintingCardItems cardItems;

  PaintingsLoaded({@required this.cardItems}) : super([cardItems]);
}

class Error extends PaintingsState {
  final String message;

  Error({@required this.message}) : super([message]);
}

The Events

abstract class PaintingsEvent extends Equatable {
  const PaintingsEvent();

  @override
  List<Object> get props => [];
}

/// Tells the bloc that it needs to load the paintings from the PaintingsRepository
class GetPaintings extends PaintingsEvent {}

The Bloc

const String FILE_NOT_FOUND_MESSAGE = 'FileNotFound Failure';

class PaintingsBloc extends Bloc<PaintingsEvent, PaintingsState> {
  final GetPaintingCardItems getCardItems;

  PaintingsBloc({@required this.getCardItems}) : super(PaintingsLoading());

  @override
  Stream<PaintingsState> mapEventToState(PaintingsEvent event) async* {
    if (event is GetPaintings) {
      yield* _mapGetPaintingsToState();
    } 
  }

  Stream<PaintingsState> _mapGetPaintingsToState() async* {
    yield PaintingsLoading();
    final failureOrPaintingCardItems = await getCardItems(NoParams());
    yield failureOrPaintingCardItems.fold(
        (failure) => Error(message: _mapFailureToMessage(failure)),
        (paintingCardItems) => PaintingsLoaded(cardItems: paintingCardItems));
  }

  String _mapFailureToMessage(Failure failure) {
    switch (failure.runtimeType) {
      case FileNotFound:
        return FILE_NOT_FOUND_MESSAGE;
      default:
        return 'Unexpected error';
    }
  }
}

Dependencies injection

/// Ambient variable to access the service locator
final sl = GetIt.instance;

/// Set up all the objects you want to access later through the service locator [sl]
void setUpServiceLocator() {
  initFeatures();
}

void initFeatures() {
  //! Features - Paintings
  // Bloc
  sl.registerLazySingleton<PaintingsBloc>(() => PaintingsBloc(getCardItems: sl<GetPaintingCardItems>()));

  // Use cases
  sl.registerLazySingleton<GetPaintingCardItems>(() => GetPaintingCardItems(sl<PaintingsRepository>()));

  // Repository
  sl.registerLazySingleton<PaintingsRepository>(
      () => PaintingsRepositoryImpl(dataSource: sl<PaintingsDataSource>()));

  // Data sources
  sl.registerLazySingleton<PaintingsDataSource>(() => PaintingsDataSourceImpl());    
}

main.dart

void main() {
  // dependencies injection
  setUpServiceLocator();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<PaintingsBloc>(
      create: (_) => sl<PaintingsBloc>(),
      child: MaterialApp(
        title: 'My Paintings',
        theme: appTheme,
        initialRoute: '/',
        onGenerateRoute: RouteGenerator.generateRoute,
      ),
    );
  }
}

Page where I use BlocBuilder

class PaintingsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
       ...
      ),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Stack(
          children: <Widget>[
            SafeArea(
                child: Column(
                ...
                BlocBuilder<PaintingsBloc, PaintingsState>(
                  builder: (context, state) {
                    if(state is PaintingsLoading) {
                      return Container(
                        child: Center(
                           child: CircularProgressIndicator(),
                        ),
                      );
                    } else if(state is PaintingsLoaded) {
                      List<PaintingCardItem> _list = state.cardItems.paintingCardItems;
                      return Expanded(
                            child: SizedBox(
                              child: _list.length != 0
                                  ? ListCardView(
                                      cardItems: _list)
                                  : Container(
                                      child: Center(child: Text('Empty list'))),
                            ),
                          );
                    } else if(state is Error){
                      return Container(
                            child: Center(child: Text(state.message)));
                    } else {
                       return Container(
                            child: Center(child: Text('Unknown Error')));
                    }
                  }
                )
              ],
            ))
          ],
        ),
      ),
    );
  }
}

So, somehow the state of the bloc does not change from PaintingsLoading to either PaintingsLoaded or Error.

If someone can give me some idea to solve this problem, I will really appreciate it.

Yunet Luis Ruíz
  • 425
  • 4
  • 16
  • Where have you added the `GetPaintings` event to your bloc? – Ali Alizadeh Mar 09 '21 at 16:48
  • Sorry, I'm not sure I understand what you asked me. Are you referring to the bloc file? – Yunet Luis Ruíz Mar 09 '21 at 18:37
  • No problem. I'm saying where have you fired the event you wanted; Something like `yourBloc.add(GetPaintings());`. – Ali Alizadeh Mar 09 '21 at 19:07
  • Well, actually I didn't, but where should I do that? Should I add the event in the create parameter of the BlocProvider()? Something like `create: (_) => sl().add(GetPainting())` – Yunet Luis Ruíz Mar 09 '21 at 19:34
  • Where ever you need that event to run; Like when a page is opened, when user taps on a button, etc. Yeah you could do sth like that with a little change though; `create: (_) => sl()..add(GetPainting())`. In create you should return an instance of a bloc that this way it will. – Ali Alizadeh Mar 11 '21 at 06:27
  • You are right, that works as well and at the same time, I just write less code :) and I don't need to change anything in the tests. Thanks for your help! – Yunet Luis Ruíz Mar 11 '21 at 07:46

1 Answers1

0

I solved it, I just needed to add the event to the bloc. So, my solution was to create another state called PaintingsInitialState like so:

The States

...
class PaintingsInitialState extends PaintingsState {}
...

Then in the Bloc, I just changed the constructor of the bloc.

PaintingsBloc({@required this.getCardItems}) : super(PaintingsInitialState());`

Finally, I added the following condition inside the builder parameter of the BlocBuilder.

if (state is PaintingsInitialState) {
  _paintingsBloc.add(GetPaintings());
}

I think that the information provided in the offitial site of the bloc library can be useful to understand how to use bloc pattern and libraries properly - particularly Flutter Wheather Tutorial.

Yunet Luis Ruíz
  • 425
  • 4
  • 16