flutterdartflutter-dependenciesflutter-bloc

Page reloading data every time it's displayed


Description

GitHub project: SanwarJW/multi_bloc_provider

I'm facing an issue with state management and navigation in my Flutter application. I have two pages (HomePage and ImageListPage) where I'm using a bloc. I pass the bloc instance from the HomePage to the ImageListPage. However, every time I navigate to the ImageListPage, the bloc instance seems to reload, causing unexpected behavior.

HomePageState

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    final imageListBloc = BlocProvider.of<ImageListBloc>(context);

    return Scaffold(
        appBar: AppBar(title: const Text('Home')),
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => ImageList(
                            imageListBloc: imageListBloc,
                          )));
            },
            child: const Text('Go to Image List'),
          ),
        ));
  }
}

ImageList

class ImageList extends StatefulWidget {
  final ImageListBloc imageListBloc;
  const ImageList({super.key, required this.imageListBloc});

  @override
  State<ImageList> createState() => _ImageListState();
}

class _ImageListState extends State<ImageList> {
  @override
  void initState() {
    super.initState();
    widget.imageListBloc.loadImages();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Image List')),
      body: Center(
        child: StreamBuilder<List<String>>(
          stream: widget.imageListBloc.imageListStream,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            }
            if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            }
            if (snapshot.hasData) {
              return ListView.builder(
                itemCount: snapshot.data!.length,
                itemBuilder: (context, index) {
                  return Text(snapshot.data![index]);
                },
              );
            } else {
              // Handle the case when data is not available
              return const Text('No data available');
            }
          },
        ),
      ),
    );
  }
}

The Flutter Bloc code below defines an ImageListBloc responsible for managing image list data. It initializes with an initial state and provides a stream of image lists. The loadImages method fetches data from DataList and emits it to the stream.

ImageListBloc

DataList dataList = DataList();

class ImageListBloc extends Bloc<ImageListEvent, ImageListState> {
  ImageListBloc() : super(ImageListInitial()) {}

  final _imageListController = StreamController<List<String>>.broadcast();
  Stream<List<String>> get imageListStream => _imageListController.stream;

  loadImages() async {
    if (!_imageListController.hasListener) {
      var dList = await dataList.gitListData();
      _imageListController.sink.add(dList);
    }
  }
}

What I've Tried

Expected Outcome

I expect the bloc instance to be maintained without reloading when navigating between HomePage and ImageListPage, ensuring consistent behavior and state management.

Any insights or suggestions on how to resolve this issue would be greatly appreciated. Thank you!


Solution

  • I don't understand the purpose of passing the ImageListBloc instance into the ImageList widget.

    I see you already added it in main() BlocProvider<ImageListBloc>(create: (context) => ImageListBloc()), then just call context.read<ImageListBloc>(); to get the BLoc instance within the widget tree.

    And there is no use of the Bloc at all in your widget, your usage of the Bloc is just like a random class that contains the StreamController.

    The ImageList reloads every time you navigate to it because the loadImages() function is called in the initState() method before the build method runs. As a result, there is no listener for the StreamController, and without any listeners, the data will be fetched.

    When you navigate back, the ImageList widget is disposed and its listener is removed from the stream.

    How to fix

    My suggestion is to use the BLoc correctly, add some more state to the ImageListState

    part of 'image_list_bloc.dart';
    
    @immutable
    sealed class ImageListState {}
    
    final class ImageListInitial extends ImageListState {}
    final class ImageListLoading extends ImageListState {}
    
    final class ImageListLoaded extends ImageListState {
      final List<String> listData;
      ImageListLoaded ({required this.listData});
    }
    
    final class ImageListError extends ImageListState {}
    

    ImageListBloc (I'm using Cubit here for simple example)

    class ImageListBloc extends Cubit<ImageListState> {
      ImageListBloc() : super(ImageListInitial()) {}
    
      loadImages() async {
        emit(ImageListLoading());
        var dList = await dataList.gitListData();
        emit(ImageListLoaded(listData: dList));
      }
    }
    

    main()

    BlocProvider<ImageListBloc>(create: (context) => ImageListBloc()..loadImages())
    

    ImageList

    class ImageList extends StatefulWidget {
      const ImageList({super.key});
    
      @override
      State<ImageList> createState() => _ImageListState();
    }
    
    class _ImageListState extends State<ImageList> {
      @override
      void initState() {
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Image List')),
          body: BlocBuilder<ImageListBloc, ImageListState>(
            buildWhen: (old, current) {
              return current is ImageListLoading || current is ImageListLoaded || current is ImageListError;
            },
            builder: (context, state) {
              if(state is ImageListLoading) {
                return const CircularProgressIndicator();
              }
    
              if(state is ImageListError) {
                return Text('Error');
              }
    
              if(state is ImageListLoaded) {
                return ListView.builder(
                  itemCount: (state).listData.length,
                  itemBuilder: (context, index) {
                    return Text((state).listData[index]);
                  },
                );
              }
    
              return const Text('No data available');
            },
          ),
        );
      }
    }
    

    Finally, correct the navigation call

     Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => ImageList())
     );