skip to Main Content

I’m trying to update state after creating an Object.
I’ve created ObjectsState using freezed package, so there is no sence to use Equatable, because it is already add by package.

class ObjectsState with _$ObjectsState {
  const factory ObjectsState.initial() = Initial;
  const factory ObjectsState.loading() = _Loading;
  const factory ObjectsState.loaded(ObjectsListResponse response) = Loaded;
  const factory ObjectsState.error(String message) = _Error;
}

then, this is my event for fetching objects

@freezed
class ObjectsEvent with _$ObjectsEvent {
  const factory ObjectsEvent.onFetchObjects() = _OnFetchObjects;
}

but I also have CreateObjectEvent separately.
And my bloc file:

class ObjectsBloc extends Bloc<ObjectsEvent, ObjectsState> {
  final ObjectsUseCase objectsUseCase;

  ObjectsBloc({required this.objectsUseCase})
      : super(const ObjectsState.initial()) {
    on<ObjectsEvent>((event, emit) => event.when(
          onFetchObjects: () => _fetchObjects(emit),
        ));
  }

  Future<void> _fetchObjects(Emitter<ObjectsState> emit) async {
    emit(const ObjectsState.loading());

    try {
      final response = await objectsUseCase.executeFetchObjects();
      if (response.isError || response.result == null) {
        emit(ObjectsState.error(
            response.errorMessage ?? 'Failed to fetch objects'));
      } else {
        final updatedResponse = response.result!.copyWith(
          objectsList: List.from(response.result!.objectsList),
          total: response.result!.total,
        );

        final loadedState = ObjectsState.loaded(updatedResponse);
        final updatedState = (loadedState as Loaded).copyWith(
          response: updatedResponse,
        );
        emit(updatedState);
      }
    } catch (e) {
      emit(ObjectsState.error(e.toString()));
    }
  }
}

so, on the stage emit(updatedState); updatedState has right value, but when it goes to the UI part – nothing is updated

class ObjectsScreenWrapper extends StatefulWidget {
  ObjectsScreenWrapper({super.key});

  @override
  State<ObjectsScreenWrapper> createState() => _ObjectsScreenWrapperState();
}

class _ObjectsScreenWrapperState extends State<ObjectsScreenWrapper> {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<ObjectsBloc>(
          create: (_) =>
              ObjectsBloc(
                objectsUseCase: GetIt.I<ObjectsUseCase>(),
              ),
        ),
        BlocProvider<BalanceBloc>(
          create: (_) =>
              BalanceBloc(
                balanceUseCase: GetIt.I<BalanceUseCase>(),
              ),
        ),
      ],
      child: ObjectsScreen(),
    );
  }
}

class ObjectsScreen extends StatelessWidget {
  ObjectsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    if (context.read<ObjectsBloc>().state is Initial) {
      context.read<ObjectsBloc>().add(const ObjectsEvent.onFetchObjects());
    }
     BlocBuilder<ObjectsBloc, ObjectsState>(
                key: ValueKey(context.watch<ObjectsBloc>().state),                   
                builder: (context, state) {
                    return state.when(
                           initial: ...
                           loading: ...
                           loaded: (objectsListResponse) {
                                if (objectsListResponse.total <= 0) {
                                  return ... ;
                                } else {
                                  return ObjectsLayoutGrid(
                                    key: ValueKey(objectsListResponse
                                        .copyWith()
                                        .objectsList
                                        .hashCode),

I’ve found a lot of related issues, but nothing worked for me. If anyone know how to fix this, please help

2

Answers


  1. As per my knowledge, When we emit the same state with Freezed, the UI doesn’t rebuild because Freezed compares the states and skips the rebuild if they are equal, so to ensure the UI rebuilds, you can add a field that changes with every emit like timestamp as below:

    Modify Your State

    Add a DateTime or int like below field to your state:

    const factory ObjectsState.loaded(
      ObjectsListResponse response,
      {required DateTime updatedAt}
    ) = Loaded;
    

    Emit Updated State

    When emitting the new state, include an updated timestamp:

    emit(ObjectsState.loaded(
      updatedResponse,
      updatedAt: DateTime.now(),
    ));
    
    Login or Signup to reply.
  2. First of all, do not run this in the build method

    if (context.read<ObjectsBloc>().state is Initial) {
      context.read<ObjectsBloc>().add(const ObjectsEvent.onFetchObjects());
    }
    

    instead put it in the provider create method

    BlocProvider<ObjectsBloc>(
      create: (_) => ObjectsBloc(
         objectsUseCase: GetIt.I<ObjectsUseCase>(),
      )..add(const ObjectsEvent.onFetchObjects()),
    ),
    

    or use initState of a stateful widget because it is possible for that to run multiple times before the state changes to loading if you keep it in the build method.

    Remove the key you gave to the BlocBuilder, idk why would you need to do that.

    The rest of the code seems fine and it should be rebuilding the ui.

    It could be that the code has not been updated on the app, running a flutter clean can fix some issues related to code not being updated on the app

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search