Home > Back-end >  RangeError (index): Invalid value: Valid value range is empty: 0 Flutter Bloc Stream
RangeError (index): Invalid value: Valid value range is empty: 0 Flutter Bloc Stream

Time:07-13

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.

  • Related