Bloc: Managing the state when onBackPressed

Created on 4 Nov 2019  路  8Comments  路  Source: felangel/bloc

First of all thank you for this awesome library, it's awesome!
I'm sorry that I'm posting this here, but I've tried asking many people but I just feel stuck with this issue. Hope you would be able to help.
So I have a simple list that's clickable and goes to DetailScreen, issue I have is when I click back from the DetailScreen, how can I manage this state to save the last list

Bloc

if (event is GetNews && !_hasReachedMax(state)) {
      try {
        if (currentState is NewsInitial) {
          final news = await fetchNews(event.cat, pageNumber);
          yield NewsLoaded(news, false);
        }
        if (currentState is NewsLoaded) {
          pageNumber++;
          final news = await fetchNews(event.cat, pageNumber);
          yield news.isEmpty
              ? currentState.copyWith(hasReachedMax: true)
              : NewsLoaded(currentState.node + news, false);
        }
      } catch (error) {
        print(error);
        yield NewsError("Error fetching news" + error);
      }
    } else if (event is GetDetailedNews) {
      try {
        final filter = await fetchDetailedNews(event.id);
        yield DetailedNewsLoaded(filter);
      } catch (error) {
        yield NewsError("Couldn't fetch news : $error");
      }
    }

Attaching the event to the bloc

  @override
  void initState() {
    super.initState();
    _postBloc = BlocProvider.of<NewsBloc>(context)
      ..add(GetNews(widget.cat));
  }

BlocBuilder

OnBackPressed I'm just stick in the else since I don't know how to manage the state

    return BlocBuilder<NewsBloc, NewsState>(builder: (context, state) {
      if (state is NewsLoaded) {
          return ListView.builder(
              controller: _scrollController,
              itemCount: state.hasReachedMax
                  ? state.node.length
                  : state.node.length + 1,
              itemBuilder: (context, index) {
                fullList = state.node;
                print("list: ${state.node} \nlength: ${state.node
                    .length} \nindex: $index \n--------------");
                return index >= state.node.length ?
                BottomLoader() :
                listViews(context, state.node[index], index);
              });
      }
      else if (state is NewsError) {
          return Center(
              child: Container(
                child: Text(state.message),
              ));
      }
      else {
        return Center(child: CircularProgressIndicator(),);
      }
    });

States

abstract class NewsState extends Equatable {
  const NewsState();

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

class NewsInitial extends NewsState {
  const NewsInitial();

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

class NewsLoading extends NewsState {
  const NewsLoading();

  @override
  List<Object> get props => [];
}
class NewsLoaded extends NewsState {
  final List<Node> node;
  final bool hasReachedMax;

  NewsLoaded(this.node, this.hasReachedMax);

  NewsLoaded copyWith({List<Node> node, bool hasReachedMax}) {
    return NewsLoaded(node ?? this.node, hasReachedMax ?? this.hasReachedMax);
  }

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

class DetailedNewsLoaded extends NewsState {
  final List<Node> node;

  DetailedNewsLoaded(this.node);

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

In the detail screen i add the GetDetailScreen event, and this event stays when onBackPressed

  @override
  void initState() {
    BlocProvider.of<NewsBloc>(context)
      ..add(GetDetailedNews(widget.id));
    super.initState();
  }

Hope you can help me! Thanks in advance.

question

Most helpful comment

I fixed it by creating a separate bloc, I'm guessing by default each screen should have it's own separate Bloc.
So when I click back I keep my last state (from that screen)
Thank you for help again!

All 8 comments

Hi @Milansu 馃憢
Thanks for opening an issue and for the positive feedback!

I think the problem is you are yielding NewsLoading in your bloc every time a GetNews event is added/dispatched. Instead, you should only yield NewsLoading if the current state is not NewsLoaded.

// news_bloc.dart

final state = currentState;
if (state is! NewsLoaded) {
  yield NewsLoading();
}
...

Let me know if that helps 馃憤

I've tried the WillPopScope before, I had an issue with it, since when I add the event when I press back, it rebuilds the initState where I add the DetailScreen event.

I've tried HydratedBloc but I'm not sure, this only works with initialState?

class NewsBloc extends HydratedBloc<NewsEvent, NewsState> {
  @override
  NewsState get initialState {
    return super.initialState ?? NewsInitial();
  }
  @override
  NewsState fromJson(Map<String, dynamic> json) {
   try{
     final node = Node.fromJson(json) as List<Node>;
     return NewsLoaded(node, false);
   }
   catch(error) {
     return null;
   }}


  @override
  Map<String, dynamic> toJson(NewsState state) {
    if(state is NewsLoaded) {
        return state.news.toJson(state.node[0]);
    }
    else {
      return null;
    }
  }

NewsLoading is not making a problem, when I onBackPressed the last state is DetailedNewsLoaded, so my List can't be rebuilt or I can't get its previous state :(

@Milansu are you able to share a link to the code?

I'm sorry I'm not allowed to share it 馃槥

@Milansu can you create a simple sample app which illustrates the issue you're having?

I fixed it by creating a separate bloc, I'm guessing by default each screen should have it's own separate Bloc.
So when I click back I keep my last state (from that screen)
Thank you for help again!

@Milansu not each screen, more like each big enough functionality. 馃槈

@tenhobi Thats it!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

komapeb picture komapeb  路  3Comments

craiglabenz picture craiglabenz  路  3Comments

hivesey picture hivesey  路  3Comments

wheel1992 picture wheel1992  路  3Comments

timtraversy picture timtraversy  路  3Comments