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.
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'),
),
));
}
}
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.
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);
}
}
}
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!
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())
);