felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.8k stars 3.39k forks source link

BlocBuilder Keeps old state after leaving the page #1566

Closed ConfidenceYobo closed 4 years ago

ConfidenceYobo commented 4 years ago

After leaving the page and re-navigate to the page BlocBuilder still keeps old state.

Here is my widget:

class CreateRoom extends StatefulWidget {
  @override
  _CreateRoomState createState() => _CreateRoomState();
}

class _CreateRoomState extends State<CreateRoom>
    with SingleTickerProviderStateMixin {
  bool pressAttention = true;
  final _scrollController = ScrollController();
  final _scrollThreshold = 200.0;
  InviteBloc _inviteBloc;
  final List<User> invitedUsers = [];
  String query;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
    _inviteBloc = BlocProvider.of<InviteBloc>(context);
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<InviteBloc, InviteState>(
        builder: (blocContext, state) {
          List _users = [];
          if (state is InviteSuccess) {
            _users = state.props[0];
          }
          print('show state');
          print(state);
        return Scaffold(
          backgroundColor: Colors.white,
          body: GestureDetector(
            onTap: () {
              FocusScope.of(context).requestFocus(new FocusNode());
            },
            child: NestedScrollView(
              controller: _scrollController,
              headerSliverBuilder: (BuildContext context, bool boxIsScrolled) {
                return <Widget>[
                  SliverAppBar(
                    floating: false,
                    pinned: true,
                    elevation: 0,
                    automaticallyImplyLeading: false,
                    centerTitle: true,
                    leading: IconButton(
                        icon: Icon(
                          Icons.arrow_back_ios,
                          size: 18,
                          color: CustomColor.black,
                        ),
                        onPressed: () {
                          Navigator.pop(context);
                        }),
                    title: PageTitle(
                        title: 'New Group',
                        textStyle: TextStyle(
                          color: CustomColor.black,
                          fontFamily: 'CircularStd',
                          fontWeight: FontWeight.w700,
                          fontSize: 19.0,
                        )),
                    actions: <Widget>[
                      Row(
                        children: <Widget>[
                          invitedUsers.isNotEmpty
                              ? GestureDetector(
                                  child: Container(
                                    child: Padding(
                                      padding: const EdgeInsets.only(
                                          left: 25.0, top: 10, bottom: 10),
                                      child: Text(
                                        'Next',
                                        style: TextStyle(
                                          color: CustomColor.primary,
                                          fontFamily: 'CircularStd',
                                          fontWeight: FontWeight.w600,
                                          fontSize: 16.0,
                                        ),
                                      ),
                                    ),
                                  ),
                                  onTap: () {
                                    Navigator.push(
                                      context,
                                      MaterialPageRoute(
                                          builder: (context) =>
                                              CreateRoomImageUpload(
                                                  users: invitedUsers)),
                                    );
                                  },
                                )
                              : Container(),
                          SizedBox(width: 15)
                        ],
                      ),
                    ],
                  ),
                ];
              },
              body: SingleChildScrollView(
                physics: BouncingScrollPhysics(),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    SearchInput(
                        placeHolderText: 'Search for people to invite',
                        onChange: (text) {
                          if (text.length > 0) {
                            setState(() => query = text);
                            _inviteBloc.add(
                                InviteFetched(query: query, isSearching: true));
                          }
                        }),
                    state is InviteSuccess
                        ? ListView.builder(
                            physics: ClampingScrollPhysics(),
                            shrinkWrap: true,
                            scrollDirection: Axis.vertical,
                            padding: EdgeInsets.symmetric(vertical: 0),
                            itemCount: _users.length,
                            itemBuilder: (BuildContext context, int index) {
                              User _userDetails = _users[index];
                              return index >= _users.length
                                  ? BottomLoader()
                                  : UserInviteWidget(
                                      userDetails: _userDetails,
                                      onInvite: (user) => _inviteUser(user));
                            },
                          )
                        : Container(),
                  ],
                ),
              ),
            ),
          ),
        );
  });
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  void _onScroll() {
    if (query != null && query.length > 0) {
      final maxScroll = _scrollController.position.maxScrollExtent;
      final currentScroll = _scrollController.position.pixels;
      if (maxScroll - currentScroll <= _scrollThreshold) {
        _inviteBloc.add(InviteFetched(isSearching: false, query: query));
      }
    }
  }

  void _inviteUser(user) {
    setState(() {
      user.isInvited ? invitedUsers.remove(user) : invitedUsers.add(user);
      user.isInvited = !user.isInvited;
    });
  }
}

My user model:

class User {
  int id;
  String username;
  String email;
  String profileImage;
  String gender;
  String city;
  bool roomInvite = false;
  bool roomMember = false;

  User(
      {this.id,
      this.username,
      this.profileImage,
      this.email,
      this.gender,
      this.city});

  User.fromJson(Map<String, dynamic> parsedJson)
      : id = parsedJson['id'] ?? '',
        username = parsedJson['username'] ?? '',
        profileImage = parsedJson['profileImage'] ?? '',
        email = parsedJson['email'] ?? '',
        gender = parsedJson['gender'] ?? '',
        city = parsedJson['city'] ?? '';

  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      "id": id,
      "username": username,
      "profileImage": profileImage,
      "email": email,
      "gender": gender,
      "city": city
    };
  }

  set isInvited(bool invited) {
    this.roomInvite = invited;
  }

  bool get isInvited {
    return roomInvite;
  }

  set isRoomMember(bool roomMember) {
    roomMember = roomMember;
  }

  get isRoomMember {
    return roomMember;
  }

  @override
  String toString() =>
      'User { id: $id, username: $username, profileImage: $profileImage, ' +
      'email: $email, gender: $gender, city: $city }';
}

My bloc is like:

import 'dart:async';
import 'package:bloc/bloc.dart';
import '../models/user.dart';
import '../services/rooms_repository.dart';
import 'package:meta/meta.dart';
import 'package:rxdart/rxdart.dart';
import 'bloc.dart';

class InviteBloc extends Bloc<InviteEvent, InviteState> {
  final RoomsRepository roomsRepository;

  InviteBloc({@required this.roomsRepository}) : super(InviteInitial());

  @override
  Stream<Transition<InviteEvent, InviteState>> transformEvents(
    Stream<InviteEvent> events,
    TransitionFunction<InviteEvent, InviteState> transitionFn,
  ) {
    return super.transformEvents(
      events.debounceTime(const Duration(milliseconds: 500)),
      transitionFn,
    );
  }

  @override
  Stream<InviteState> mapEventToState(InviteEvent event) async* {
    final currentState = state;

    if (event is InviteFetched && !_hasReachedMax(currentState)) {
      try {
        if (currentState is InviteInitial || event.isSearching) {
          yield* _getUsersOnInitialState(query: event.query);
        }
        if (currentState is InviteSuccess) {
          yield* _getUsersOnSuccessState(currentState,
              query: event.query, isSearching: event.isSearching);
        }
      } catch (e) {
        print(e);
        // yield InviteFailure();
      }
    }
  }

  Stream<InviteState> _getUsersOnInitialState(
      {String query, bool isSearching}) async* {
    try {
      final initialPage = 1;
      final result = await _fetchUsersAndGetPages(initialPage, query: query);
      final users = result['users'];
      yield InviteSuccess(
          users: users,
          currentPage: result['currentPage'],
          lastPage: result['lastPage'],
          hasReachedMax: false);
    } catch (e) {
      print(e);
      // yield InviteFailure();
    }
  }

  /// Takes the current state of the bloc, check if the nextPage (`currentState.currentPage + 1`) is beyond
  /// the lastPage (`nextPage > currentState.lastPage`). if it is setState to hasReachedMax
  /// else get the requested users and set in state
  Stream<InviteState> _getUsersOnSuccessState(currentState,
      {String query, bool isSearching}) async* {
    try {
      int nextPage = currentState.currentPage + 1;

      if (isSearching == false) {
        if (nextPage > currentState.lastPage) {
          yield currentState.copyWith(hasReachedMax: true);
          return;
        }
      }

      if (isSearching) {
        nextPage = 1;
      }

      final result = await _fetchUsersAndGetPages(nextPage, query: query);
      if (result == null) {
        return;
      }
      final users = result['users'];
      yield InviteSuccess(
        users: isSearching ? users : currentState.users + users,
        currentPage: result['currentPage'],
        lastPage: result['lastPage'],
        hasReachedMax: false,
      );
    } catch (e) {
      print(e);
      // yield InviteFailure();
    }
  }

  bool _hasReachedMax(InviteState state) =>
      state is InviteSuccess && state.hasReachedMax;

  Future<Map<String, dynamic>> _fetchUsersAndGetPages(nextPage,
      {String query}) async {
    try {
      final response =
          await roomsRepository.getRoomInvite(page: nextPage, query: query);
          print('Getting response');
          print(response);
      if (response == null) {
        return null;
      }
      final users = _users(response['results']);

      final int currentPage = response['pagination']['current_page'];
      final int lastPage = response['pagination']['last_page'];
      return {'users': users, 'currentPage': currentPage, 'lastPage': lastPage};
    } catch (e) {
      print(e);
      throw Exception('error fetching users');
    }
  }

  List _users(users) {
    return users.map((rawDetail) {
      return User(
          id: rawDetail['id'],
          username: rawDetail['username'],
          email: rawDetail['email'],
          profileImage: rawDetail['profile_image'],
          gender: rawDetail['gender'],
          city: rawDetail['city']);
    }).toList();
  }
}

I'm using:

flutter_bloc: ^6.0.1
equatable: ^1.0.0
meta: ^1.1.6
rxdart: ^0.23.1
narcodico commented 4 years ago

Hi @ConfidenceYobo 👋

Could you create a minimal reproduction of your issue in a repo and share it ?

ConfidenceYobo commented 4 years ago

@RollyPeres https://github.com/ConfidenceYobo/bloc_demonstration is sample repo to demonstrate the issue.

narcodico commented 4 years ago

Since your bloc is globally available it keeps state between your navigations, so it needs to be resetted with the help of an event, as you can see in this PR I've opened. Hope it helps 👍