Search code examples
flutterbloc

Nested bloc bloc depends on bloc to build


I'm currently bringing myself up to speed on bloc architecture working with open api

1.https://jsonplaceholder.typicode.com/albums and 2.https://jsonplaceholder.typicode.com/photos/albumId

i.e photos/albumId depends on albums response

albums response

 [
    {
        "userId": 1,
        "id": 1,
        "title": "quidem molestiae enim"
    },
    {
        "userId": 1,
        "id": 2,
        "title": "sunt qui excepturi placeat culpa"
    },
    {
        "userId": 1,
        "id": 3,
        "title": "omnis laborum odio"
    },
]

photos response with id gotten from albums i.e photos/albumId=1

 [
    {
        "albumId": 1,
        "id": 1,
        "title": "accusamus beatae ad facilis cum similique qui sunt",
        "url": "https://via.placeholder.com/600/92c952",
        "thumbnailUrl": "https://via.placeholder.com/150/92c952"
    },
    {
        "albumId": 1,
        "id": 2,
        "title": "reprehenderit est deserunt velit ipsam",
        "url": "https://via.placeholder.com/600/771796",
        "thumbnailUrl": "https://via.placeholder.com/150/771796"
    },
    {
        "albumId": 1,
        "id": 3,
        "title": "officia porro iure quia iusto qui ipsa ut modi",
        "url": "https://via.placeholder.com/600/24f355",
        "thumbnailUrl": "https://via.placeholder.com/150/24f355"
    },
    {
        "albumId": 1,
        "id": 4,
        "title": "culpa odio esse rerum omnis laboriosam voluptate repudiandae",
        "url": "https://via.placeholder.com/600/d32776",
        "thumbnailUrl": "https://via.placeholder.com/150/d32776"
    },
    {
        "albumId": 1,
        "id": 5,
        "title": "natus nisi omnis corporis facere molestiae rerum in",
        "url": "https://via.placeholder.com/600/f66b97",
        "thumbnailUrl": "https://via.placeholder.com/150/f66b97"
    },
    {
        "albumId": 1,
        "id": 6,
        "title": "accusamus ea aliquid et amet sequi nemo",
        "url": "https://via.placeholder.com/600/56a8c2",
        "thumbnailUrl": "https://via.placeholder.com/150/56a8c2"
    },
]

I have been able to implement this but what seems to be the problem now is I'm trying to generate widget based on what i'm trying to do

what i'm trying to achieve image

** the problem ** (image)(https://drive.google.com/file/d/1LU_bwCooDT6Tgx-DpDNAFGDrwStkn2Gq/view?usp=sharing)

source

BLOC ALBUMS

class AlbumsBloc extends Bloc<AlbumsEvent, AlbumsState> {
  final AlbumsRepository albumsRepository;

  AlbumsBloc(this.albumsRepository) : super(AlbumsLoadingState()) {
    on<AlbumsEvent>((event, emit) async {
      if (event is LoadAlbumsEvent || event is RefreshAlbumsEvent) {
        emit(AlbumsLoadingState());
        try {
          final albums = await albumsRepository.fetchAlbumsData();
          emit(AlbumsLoadedState(albums: albums));
        } catch (e) {
          emit(AlbumsErrorState(error: e.toString()));
        }
      }
    });
  }
}
abstract class AlbumsEvent extends Equatable {
  const AlbumsEvent();
}

class LoadAlbumsEvent extends AlbumsEvent {
  @override
  List<Object> get props => [];
}

class RefreshAlbumsEvent extends AlbumsEvent {
  @override
  List<Object> get props => [];
}
abstract class AlbumsState extends Equatable {
  const AlbumsState();
}

class AlbumsEmptyState extends AlbumsState {
  @override
  List<Object?> get props => [];
}

class AlbumsLoadingState extends AlbumsState {
  @override
  List<Object> get props => [];
}

class AlbumsLoadedState extends AlbumsState {
  final List<Albums> albums;

  const AlbumsLoadedState({required this.albums});

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

class AlbumsErrorState extends AlbumsState {
  final String error;

  const AlbumsErrorState({required this.error});

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

BLOC PHOTOS

class PhotosBloc extends Bloc<PhotosEvent, PhotosState> {
  final PhotosRepository photosRepository;

  PhotosBloc(this.photosRepository) : super(PhotosLoadingState()) {
    on<PhotosEvent>((event, emit) async {
      if (event is LoadPhotosEvent) {
        emit(PhotosLoadingState());
        try {
          final photos = await photosRepository.fetchPhotosData(event.albumId);
          emit(PhotosLoadedState(photos: photos));
        } catch (e) {
          emit(PhotosErrorState(error: e.toString()));
        }
      }
    });
  }
}
abstract class PhotosEvent extends Equatable {
  const PhotosEvent();
}

class LoadPhotosEvent extends PhotosEvent {
  final int albumId;

  const LoadPhotosEvent({required this.albumId});

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

class PhotosEmpty extends PhotosState {
  @override
  List<Object?> get props => [];
}

class PhotosLoadingState extends PhotosState {
  @override
  List<Object> get props => [];
}

class PhotosLoadedState extends PhotosState {
  final List<Photos> photos;

  const PhotosLoadedState({required this.photos});

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

class PhotosErrorState extends PhotosState {
  final String error;

  const PhotosErrorState({required this.error});

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

THE CODE

class AlbumScreen extends StatefulWidget {
  const AlbumScreen({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return AlbumScreenState();
  }
}

class AlbumScreenState extends State<AlbumScreen> {

  final PhotosRepository photosRepository = PhotosRepository();

  @override
  void didChangeDependencies() {
    BlocProvider.of<AlbumsBloc>(context).add(LoadAlbumsEvent());
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {
    //final photoBloc = BlocProvider.of<PhotosBloc>(context);
    return  Scaffold(
      backgroundColor: Colors.white,
      appBar: PreferredSize(
          preferredSize: const Size.fromHeight(60.0),
          child: AppBar(
            backgroundColor: Colors.white,
            elevation: 0,
            title: const Text("", style: TextStyle(color: Colors.black, fontSize: 20.0),),
          ),
      ),
      body: SafeArea(
        child: SingleChildScrollView(
          child: BlocBuilder<AlbumsBloc,AlbumsState>(
            builder: (context,state){
              if( state is AlbumsLoadingState){
                return  const Center(
                  child: CircularProgressIndicator(),
                );
              }
              if(state is AlbumsLoadedState){
                  return Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children:   [
                    for(int i = 0; i < state.albums.length; i++)
                    BlocBuilder <PhotosBloc,PhotosState>(
                      bloc:  BlocProvider.of<PhotosBloc>(context)..add(LoadPhotosEvent(albumId: i)),
                      builder: (context,state){
                        if( state is PhotosLoadingState){
                          return  const Center(
                            child: CircularProgressIndicator(),
                          );
                        }
                        if(state is PhotosLoadedState){
                          final photos = state.photos;
                          return AlbumsWithAlbumCard(
                            albumIndex: i,
                            photos: photos,
                          );
                        }
                        return Container();
                      }
                    )
                  ],
                );
              }
              return Container();
            },
          ),
        ),
      ),
    );
  }
}

Solution

  • Check this small restructure:

    class AlbumScreen extends StatelessWidget {
      const AlbumScreen({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return BlocProvider(
          create: (context) => AlbumsBloc()..add(LoadAlbumsEvent()),
          child: AlbumView(),
        );
      }
    }
    
    class AlbumView extends StatelessWidget {
       AlbumView({Key? key}) : super(key: key);
    
      final PhotosRepository photosRepository = PhotosRepository();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.white,
          appBar: PreferredSize(
            preferredSize: const Size.fromHeight(60.0),
            child: AppBar(
              backgroundColor: Colors.white,
              elevation: 0,
              title: const Text(
                "",
                style: TextStyle(color: Colors.black, fontSize: 20.0),
              ),
            ),
          ),
          body: SafeArea(
            child: SingleChildScrollView(
              child: BlocBuilder<AlbumsBloc, AlbumsState>(
                builder: (context, state) {
                  if (state is AlbumsLoadingState) {
                    return const Center(
                      child: CircularProgressIndicator(),
                    );
                  }
                  if (state is AlbumsLoadedState) {
                    return Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: state.albums.map((album) {
                        BlocListener<AlbumBloc, AlbumState>(
                          listenWhen: (previous, current) => previous != current,
                          listener: (context, state) {
                            if(state is AlbumsLoadedState) {
                              context.read<PhotosBloc>().add(LoadPhotosEvent(albumId: album.id));
                            }
                          },
                          child: BlocBuilder<PhotosBloc, PhotosState>(
                            builder: (context, state) {
                              if( state is PhotosLoadingState){
                                return  const Center(
                                  child: CircularProgressIndicator(),
                                );
                              }
                              if(state is PhotosLoadedState){
                                final photos = state.photos;
                                return AlbumsWithAlbumCard(
                                  albumIndex: album.id,
                                  photos: photos,
                                );
                              }
                              return Container();
                            },
                          ),
                        )
                      }).toList(),
                    );
                  }
                  return Container();
                },
              ),
            ),
          ),
        );
      }
    }