Streambuilder not rebuilding after BLOC event

570 views Asked by At

I am trying to implement pagination in my application but I have not been successful in doing so.

I am using Firebase, specifically Firestore with the BLOC pattern alongside Built Value which I started using recently to make pagination easier.

I would really appreciate any help or referral links how to use these technologies together.

My application architecture is as follows:

Application Architecture

I have tried to keep to the BLOC pattern as much as possible but this in turn has made it really difficult to paginate largely ,because of using built value as built value make it really difficult to use Streams and Futures. I have looked all over the Internet but I could not find any tutorial or docs to use built value with Firestore and BLOC specifically to paginate.

The problem is that when I do any of the CRUD functions for example delete an Category from a list, the Stream Builder is not updating the list despite the pagination and everything else working.

Currently I have tried using the a Listview builder by itself which obviously didn't work at all,so I moved to a Stream Builder and tryed both Streams and Futures(.asStream) but it is not updating.

Below is some of the code:

The model:

abstract class CategoryCard
    implements Built<CategoryCard, CategoryCardBuilder> {
  String get category;
  String get icon;
  double get budget;
  double get spent;
  String get categoryRef;
  DocumentSnapshot get document;

  CategoryCard._();

  factory CategoryCard([updates(CategoryCardBuilder b)]) = _$CategoryCard;

  static Serializer<CategoryCard> get serializer => _$categoryCardSerializer;
}

The query:

  Future<Stream<fs.QuerySnapshot>> getMoreCategoryAmounts(
      fs.DocumentSnapshot documentSnapshot) async {
    var user = await getCurrentUser();

    print(currentMonth);

    fs.Query categoryAmountQuery = _instance
        .collection('users')
        .document(user.uid)
        .collection('amounts')
        .where('year', isEqualTo: currentYear)
        .where('month', isEqualTo: currentMonth)
        .orderBy('category', descending: false)
        .limit(7);

    return documentSnapshot != null
        ? categoryAmountQuery.startAfterDocument(documentSnapshot).snapshots()
        : categoryAmountQuery.snapshots();
  }

The BLOC:

class CategoryCardBloc extends Bloc<CategoryCardEvents, CategoryCardState> {
  final BPipe bPipe;
  final FirebaseRepository firebaseRepository;

  CategoryCardBloc({@required this.bPipe, @required this.firebaseRepository})
      : assert(bPipe != null),
        assert(firebaseRepository != null);

  @override
  CategoryCardState get initialState => CategoryCardState.intial();

  @override
  Stream<CategoryCardState> mapEventToState(CategoryCardEvents event) async* {
    if (event is LoadCategoryCardEvent) {
      yield* _mapToEventLoadCategoryCard(event);
    }
  }

  Stream<CategoryCardState> _mapToEventLoadCategoryCard(
      LoadCategoryCardEvent event) async* {
    if (event.amountDocumentSnapshot == null) {
      yield CategoryCardState.loading();
    }
    try {
      Future<BuiltList<CategoryCard>> _newCategoryCards =
          bPipe.getMoreCategoryCards(event.amountDocumentSnapshot);

      yield CategoryCardState.loaded(
          FutureMerger()
              .merge<CategoryCard>(state.categoryCards, _newCategoryCards));
    } on NullException catch (err) {
      print('NULL_EXCEPTION');
      yield CategoryCardState.failed(err.objectExceptionMessage,
          state?.categoryCards ?? Stream<BuiltList<CategoryCard>>.empty());
    } on NoValueException catch (_) {
      print('NO VALUE EXCEPTION');
      yield state.rebuild((b) => b..hasReachedEndOfDocuments = true);
    } catch (err) {
      print('UNKNOWN EXCEPTION');
      yield CategoryCardState.failed(
          err != null ? err.toString() : NullException.exceptionMessage,
          state.categoryCards);
    }
  }
}

The State:

abstract class CategoryCardState
    implements Built<CategoryCardState, CategoryCardStateBuilder> {
  Future<BuiltList<CategoryCard>> get categoryCards;
  //*Reached end indicator
  bool get hasReachedEndOfDocuments;
  //*Error state
  String get exception;
  //*Loading state
  @nullable
  bool get isLoading;
  //*Success state
  @nullable
  bool get isSuccessful;
  //*Loaded state
  @nullable
  bool get isLoaded;

  CategoryCardState._();

  factory CategoryCardState([updates(CategoryCardStateBuilder b)]) =
      _$CategoryCardState;

  factory CategoryCardState.intial() {
    return CategoryCardState((b) => b
      ..exception = ''
      ..isSuccessful = false
      ..categoryCards =
          Future<BuiltList<CategoryCard>>.value(BuiltList<CategoryCard>())
      ..hasReachedEndOfDocuments = false);
  }

  factory CategoryCardState.loading() {
    return CategoryCardState((b) => b
      ..exception = ''
      ..categoryCards =
          Future<BuiltList<CategoryCard>>.value(BuiltList<CategoryCard>())
      ..hasReachedEndOfDocuments = false
      ..isLoading = true);
  }
  factory CategoryCardState.loaded(Future<BuiltList<CategoryCard>> cards) {
    return CategoryCardState((b) => b
      ..exception = ''
      ..categoryCards = cards
      ..hasReachedEndOfDocuments = false
      ..isLoading = false
      ..isLoaded = true);
  }
  factory CategoryCardState.success(Future<BuiltList<CategoryCard>> cards) {
    return CategoryCardState((b) => b
      ..exception = ''
      ..categoryCards =
          Future<BuiltList<CategoryCard>>.value(BuiltList<CategoryCard>())
      ..hasReachedEndOfDocuments = false
      ..isSuccessful = true);
  }
  factory CategoryCardState.failed(
      String exception, Future<BuiltList<CategoryCard>> cards) {
    return CategoryCardState((b) => b
      ..exception = exception
      ..categoryCards = cards
      ..hasReachedEndOfDocuments = false);
  }
}

The event:

abstract class CategoryCardEvents extends Equatable {}

class LoadCategoryCardEvent extends CategoryCardEvents {
  final DocumentSnapshot amountDocumentSnapshot;

  LoadCategoryCardEvent({@required this.amountDocumentSnapshot});

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

The pagination screen(Contained inside a stateful widget):

//Notification Handler
  bool _scrollNotificationHandler(
      ScrollNotification notification,
      DocumentSnapshot amountDocumentSnapshot,
      bool hasReachedEndOfDocuments,
      Future<BuiltList<CategoryCard>> cards) {
    if (notification is ScrollEndNotification &&
        _scollControllerHomeScreen.position.extentAfter == 0 &&
        !hasReachedEndOfDocuments) {
      setState(() {
        _hasReachedEnd = true;
      });

      _categoryCardBloc.add(LoadCategoryCardEvent(
          amountDocumentSnapshot: amountDocumentSnapshot));
    }
    return false;
  }


BlocListener<CategoryCardBloc, CategoryCardState>(
                    bloc: _categoryCardBloc,
                    listener: (context, state) {
                      if (state.exception != null &&
                          state.exception.isNotEmpty) {
                        if (state.exception == NullException.exceptionMessage) {
                          print('Null Exception');
                        }  else {
                          ErrorDialogs.customAlertDialog(
                              context,
                              'Failed to load',
                              'Please restart app or contact support');

                          print(state.exception);
                        }
                      }
                    },
                    child: BlocBuilder<CategoryCardBloc, CategoryCardState>(
                        bloc: _categoryCardBloc,
                        builder: (context, state) {
                          if (state.isLoading != null && state.isLoading) {
                            return Center(
                              child: CustomLoader(),
                            );
                          }

                          if (state.isLoaded != null && state.isLoaded) {
                            return StreamBuilder<BuiltList<CategoryCard>>(
                              stream: state.categoryCards.asStream(),
                              builder: (context, snapshot) {

                                if (!snapshot.hasData) {
                                  return Center(
                                    child: CustomLoader(),
                                  );
                                } else {
                                  BuiltList<CategoryCard> categoryCards =
                                      snapshot.data;

                                  _hasReachedEnd = false;

                                  print(state.hasReachedEndOfDocuments &&
                                      state.hasReachedEndOfDocuments != null);

                                  return Container(
                                    height: Mquery.screenHeight(context),
                                    width: Mquery.screenWidth(context),
                                    child: NotificationListener<
                                        ScrollNotification>(
                                      onNotification: (notification) =>
                                          _scrollNotificationHandler(
                                              notification,
                                              categoryCards.last.document,
                                              state.hasReachedEndOfDocuments,
                                              state.categoryCards),
                                      child: SingleChildScrollView(
                                        controller: _scollControllerHomeScreen,
                                        child: Column(
                                          children: [
                                            CustomAppBar(),
                                            Padding(
                                              padding: EdgeInsets.all(
                                                  Mquery.padding(context, 2.0)),
                                              child: Row(
                                                children: [
                                                  Expanded(
                                                    flex: 5,
                                                    child: Padding(
                                                      padding: EdgeInsets.all(
                                                          Mquery.padding(
                                                              context, 1.0)),
                                                      child:Container(
                                                          width: Mquery.width(
                                                              context, 50.0),
                                                          height: Mquery.width(
                                                              context, 12.5),
                                                          decoration:
                                                              BoxDecoration(
                                                            color: middle_black,
                                                            borderRadius: BorderRadius
                                                                .circular(Constants
                                                                    .CARD_BORDER_RADIUS),
                                                            boxShadow: [
                                                              BoxShadow(
                                                                  color: Colors
                                                                      .black54,
                                                                  blurRadius:
                                                                      4.0,
                                                                  spreadRadius:
                                                                      0.5)
                                                            ],
                                                          ),
                                                          child: Padding(
                                                            padding: EdgeInsets.fromLTRB(
                                                                Mquery.padding(
                                                                    context,
                                                                    4.0),
                                                                Mquery.padding(
                                                                    context,
                                                                    4.0),
                                                                Mquery.padding(
                                                                    context,
                                                                    2.0),
                                                                Mquery.padding(
                                                                    context,
                                                                    1.0)),
                                                            child: TextField(
                                                              textInputAction:
                                                                  TextInputAction
                                                                      .done,
                                                              style: TextStyle(
                                                                  color: white,
                                                                  fontSize: Mquery
                                                                      .fontSize(
                                                                          context,
                                                                          4.25)),
                                                              controller:
                                                                  searchController,
                                                              decoration:
                                                                  InputDecoration(
                                                                border:
                                                                    InputBorder
                                                                        .none,
                                                                hintText: Constants
                                                                    .SEARCH_MESSAGE,
                                                                hintStyle: TextStyle(
                                                                    fontSize: Mquery
                                                                        .fontSize(
                                                                            context,
                                                                            4.25),
                                                                    color:
                                                                        white),
                                                              ),
                                                            ),
                                                          ),
                                                    ),
                                                  ),
                                                  Expanded(
                                                      flex: 1,
                                                      child: Padding(
                                                        padding: EdgeInsets.all(
                                                            Mquery.padding(
                                                                context, 1.0)),
                                                        child: Container(
                                                          decoration:
                                                              BoxDecoration(
                                                            boxShadow: [
                                                              BoxShadow(
                                                                  color: Colors
                                                                      .black54,
                                                                  blurRadius:
                                                                      4.0,
                                                                  spreadRadius:
                                                                      0.5)
                                                            ],
                                                            color: middle_black,
                                                            borderRadius: BorderRadius
                                                                .circular(Constants
                                                                    .CARD_BORDER_RADIUS),
                                                          ),
                                                          width: Mquery.width(
                                                              context, 12.5),
                                                          height: Mquery.width(
                                                              context, 12.5),
                                                          child: IconButton(
                                                            splashColor: Colors
                                                                .transparent,
                                                            highlightColor:
                                                                Colors
                                                                    .transparent,
                                                            icon: Icon(
                                                              Icons.search,
                                                              color: white,
                                                            ),
                                                            onPressed: () {
                                                              _onSearchButtonPressed();
                                                            },
                                                          ),
                                                        ),
                                                      ))
                                                ],
                                              ),
                                            ),
                                            ListView.builder(
                                              shrinkWrap: true,
                                              itemCount: categoryCards.length,
                                              physics:
                                                  NeverScrollableScrollPhysics(),
                                              itemBuilder: (context, index) {
                                                return GestureDetector(
                                                    onTap: () {
                                                      //Navigate
                                                    },
                                                    child:
                                                        CategoryCardWidget(
                                                            categoryCount:
                                                                categoryCards
                                                                    .length,
                                                            categoryCard:
                                                                categoryCards[
                                                                    index]));
                                              },
                                            ),
                                            _hasReachedEnd
                                                ? Padding(
                                                    padding: EdgeInsets.all(
                                                        Mquery.padding(
                                                            context, 4.0)),
                                                    child: CustomLoader(),
                                                  )
                                                : Container()
                                          ],
                                        ),
                                      ),
                                    ),
                                  );
                                }
                              },
                            );
                          }

                          return Container();
                        }))

Thank you for you time and sorry for being so verbose.

1

There are 1 answers

0
dotmp3 On BEST ANSWER

I managed to solve this problem,

Below is the Github link: https://github.com/felangel/bloc/issues/1707

Hope that helps someone! -Matt