Search code examples
flutterdartstatebloc

Flutter Bloc copyWith Function not working


I am using Flutter Bloc with event and states. I have tried multiple things but the copyWith functions is not working at all. The bloc just yields now state every time and reset's the previous one. How to preserve the previous states..?

My code is like this: I have create a bloc which has multiple events that can be triggerred.

class AddressBloc2 extends Bloc<AddressEvent, AddressState> {
  AddressBloc2() : super(AddressInitial()) {
    on<GetAddressList>(_getAddressList);
    on<GetCurrentLocation>(_getCurrentLocation);
    on<GetGeoAddress>(_getGeoAddress);
    on<AddAddress>(_addAddress);
    on<UpdateAddress>(_updateAddress);
    on<RemoveAddress>(_removeAddress);
    on<SetDefaultAddress>(_setDefaultAddress);
  }

  void _getAddressList(GetAddressList event, emit) async {
    try {
      final response = await AddressServices.getAddressList();
      final Address? defaultAddress = response
          .firstWhereOrNull((element) => element.addressDefault == true);
      emit(AddressLoaded(addresses: response, defaultAddress: defaultAddress));
    } catch (e) {
      emit(AddressError(e.toString()));
    }
  }

  void _getCurrentLocation(GetCurrentLocation event, emit) async {
    if (event.currentLocation == null) {
      logger('this is crashing currentlocation');
    }
    emit(
      const AddressLoaded().copyWith(currentLocation: event.currentLocation),
    );
    logger(state);
  }

  void _getGeoAddress(GetGeoAddress event, emit) async {
    if (state is GetGeoAddressLoading) return;
    emit(GetGeoAddressLoading());
    try {
      Map<String, dynamic> geoAddress =
          await AddressServices.getGeoAddress(event.currentLocation);
      logger('before geoAddress');
      logger(state);
      emit(const AddressLoaded().copyWith(geoAddress: geoAddress));
      logger(state);
    } catch (e) {
      emit(GetGeoAddressError(e.toString()));
      logger(e.toString());
      emit(const AddressLoaded());
    }
  }
}


My event file:

part of 'address_bloc.dart';

sealed class AddressEvent extends Equatable {
  const AddressEvent();
  @override
  List<Object> get props => [];
}

class GetAddressList extends AddressEvent {}

class GetCurrentLocation extends AddressEvent {
  final LatLng currentLocation;

  const GetCurrentLocation(this.currentLocation);

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

class GetGeoAddress extends AddressEvent {
  final LatLng currentLocation;

  const GetGeoAddress(this.currentLocation);

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

and my state file is like this, since I am getting the address, and loading the address list from the api and adding and removing addresses to. I want it to be in a single bloc file.

part of 'address_bloc.dart';

sealed class AddressState extends Equatable {
  const AddressState();

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

class AddressInitial extends AddressState {}

class AddressLoading extends AddressState {}

class AddressLoaded extends AddressState {
  final List<Address> addresses;
  final Address? defaultAddress;
  final LatLng? currentLocation;
  final Map<String, dynamic>? geoAddress;

  const AddressLoaded({
    this.addresses = const [],
    this.defaultAddress,
    this.currentLocation,
    this.geoAddress,
  });

  @override
  List<Object?> get props =>
      [addresses, defaultAddress, currentLocation, geoAddress];

  AddressLoaded copyWith({
    List<Address>? addresses,
    Address? defaultAddress,
    LatLng? currentLocation,
    Map<String, dynamic>? geoAddress,
  }) {
    return AddressLoaded(
      addresses: addresses ?? this.addresses,
      defaultAddress: defaultAddress ?? this.defaultAddress,
      currentLocation: currentLocation ?? this.currentLocation,
      geoAddress: geoAddress ?? this.geoAddress,
    );
  }
}

class AddressError extends AddressState {
  final String error;

  const AddressError(this.error);

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

class GetGeoAddressLoading extends AddressState {}

class GetGeoAddressError extends AddressState {
  final String error;

  const GetGeoAddressError(this.error);
  @override
  List<Object> get props => [error];
}

apart from it there are multiple events too, and all together they need to update the single AddressLoaded file.


Solution

  • You should use copyWith to create a new object with the same properties as the original, but with some of the values changed. So there are no any sens do this:

    emit(const AddressLoaded().copyWith(geoAddress: geoAddress));
    

    You create empty new instance and then with copyWith create another new instance without old values. You could check more info in this answer.

    if you sure that this case is real(I mean that _getCurrentLocation is called after AddressLoaded state) you could do this:

      void _getCurrentLocation(GetCurrentLocation event, emit) async {
        if (event.currentLocation == null) {
          logger('this is crashing currentlocation');
        }
        if (state is AddressLoaded) {
            emit(
             (state as AddressLoaded).copyWith(currentLocation: event.currentLocation),
             );
        }
        logger(state);
    

    }

    But in the _getGeoAddress it will not work with copyWith because your state is GetGeoAddressLoading when you are retrieving data. So you can create new state and, if you need some data, you could pre-extract data before change state to loading:

    void _getGeoAddress(GetGeoAddress event, emit) async {
        if (state is GetGeoAddressLoading) return;
    final currentLocation = state is AddressLoaded? (state as AddressLoaded).AddressLoaded : null;
        //..other data
        emit(GetGeoAddressLoading());
        try {
          Map<String, dynamic> geoAddress =
              await AddressServices.getGeoAddress(event.currentLocation);
          logger('before geoAddress');
          logger(state);
          emit(const AddressLoaded(
                 currentLocation: currentLocation,
                 //..other data
                 geoAddress: geoAddress));
          logger(state);
        } catch (e) {
          emit(GetGeoAddressError(e.toString()));
          logger(e.toString());
          emit(const AddressLoaded());
        }
      }