I am working with flutter bloc stream to get data from firestore. But bloc is giving a null list and when I do hot reload it shows me the complete products. My problem is why it is showing the products when the app is build? After hot reload it is showing products perfectly
Here is my code
BlocBuilder
BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if (state is ProductLoading) {
return Center(
child: CircularProgressIndicator(
color: primaryColor,
),
);
} else if (state is ProductLoaded) {
print(state.products);
return Container(
height: screenSize.height * .31,
child: ListView.builder(
shrinkWrap: true,
primary: false,
itemCount: 4,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return productCard(
product: state.products[index],
);
}),
);
} else {
return Container();
}
},
),
Product Bloc.Dart
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:async';
import 'dart:typed_data';
import 'package:bloc/bloc.dart';
import 'package:burgerhub/constants/utils.dart';
import 'package:burgerhub/models/product_model.dart';
import 'package:burgerhub/services/category_services.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:burgerhub/view/admin/services/admin_services.dart';
part 'product_event.dart';
part 'product_state.dart';
class ProductBloc extends Bloc<ProductEvent, ProductState> {
CategoryServices categoryServices;
StreamSubscription? productStreamSubscription;
AdminServices? adminServices;
ProductBloc(
this.categoryServices,
) : super(ProductInitial()) {
on<getProductsEvent>((event, emit) async {
emit(ProductLoading());
final List<ProductModel> products = [];
productStreamSubscription =
categoryServices.getProductsFromDatabase().listen((snapshot) {
snapshot.docs.forEach((snap) {
ProductModel product = ProductModel.fromJson(snap.data());
products.add(product);
});
emit(
ProductLoaded(products: products),
);
});
print(products);
});
}
@override
Future<void> close() {
productStreamSubscription!.cancel();
// TODO: implement close
return super.close();
}
}
ProductState.dart
// ignore_for_file: public_member_api_docs, sort_constructors_first
part of 'product_bloc.dart';
@immutable
abstract class ProductState extends Equatable {}
class ProductInitial extends ProductState {
@override
List<Object?> get props => [];
}
class ProductUploading extends ProductState {
@override
List<Object?> get props => [];
}
class ProductUploaded extends ProductState {
@override
List<Object?> get props => [];
}
class ProductLoading extends ProductState {
@override
List<Object?> get props => [];
}
class ProductLoaded extends ProductState {
final List<ProductModel> products;
ProductLoaded({
required this.products,
});
@override
List<Object?> get props => [products];
}
ProductEvent.dart
// ignore_for_file: public_member_api_docs, sort_constructors_first
part of 'product_bloc.dart';
@immutable
abstract class ProductEvent {}
class getProductsEvent extends ProductEvent {}
getProducts Service
Stream<QuerySnapshot<Map<String, dynamic>>> getProductsFromDatabase() {
Stream<QuerySnapshot<Map<String, dynamic>>> snapshot =
firestore.collection('products').snapshots();
return snapshot;
}
CodePudding user response:
This type of error may occur if the number of incoming data in your state management is less than 4! You are calling only 4 items. Try to replace the following code with the itemCount line:
itemCount: state.products.length,
if you only need 4 date you should check this case:
itemCount: state.products.length < 4 : state.products.length : 4,
CodePudding user response:
You are checking only loaded case, You are ignoring the time it takes for the data to arrive. try to check loading state:
BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if (state is ProductLoaded) {
return Container(
height: screenSize.height * .31,
child: ListView.builder(
shrinkWrap: true,
primary: false,
itemCount: 4,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final List<ProductModel> products = state.products;
return productCard(product: products[index]);
},
),
);
} else {
return CircularProgressIndicator();
}
},
);
CodePudding user response:
When you are running the .listen()
method, I anticipate that you get updates when database events occur. If you'd include the code for the getProductsFromDatabase()
method it could be a bit clearer for us.
If you want to have the ListView
to update when e.g. posts are made, or other changes occur, then you'd have to yield/emit updated states inside the listen callback where you add the products to the list. Meaning, you can simply try to emit(ProductLoaded(products: products));
inside the listen callback. I suspect that you will need to merge your current states products with what you get in the listen callback. But that is entirely up to what your service returns to you...
If you as now, only emit the Loaded state once after you have awaited the listen
method, then it will most likely always be empty. The await for a listener will go really fast, so the callback that add stuff to the products list will probably not have been run. This is probably why a hot reload show your products.
And for the bloc to be able to compare two states with lists of ProductModel
, that class needs to be comparable. Otherwise the BlocBuilder won't recognize that there is a new state that differs from the previous, and updates will not be seen.
Furthermore, what @Rustam Usmanov wrote in his post about the itemCount
is also worth taking into consideration. You could otherwise have unwated side effects.