skip to Main Content

I have a page that needs two different API calls.

I am applying the clean architecture to write the code and Riverpod as State Management. Then I am using the Freezed package to map the different states.

How can I combine the different states?
What I would like to achieve is to emit a success state only if both states give me data or emit an error state if one of them is an error, otherwise loading state.

Thanks in advance.

These are the two State classes:

import 'package:freezed_annotation/freezed_annotation.dart';
import '...eatures/profile/domain/entities/user_profile_entity.dart';

part 'user_profile_details_state.freezed.dart';

@freezed
class UserProfileDetailsState with _$UserProfileDetailsState {
  ///Initial
  const factory UserProfileDetailsState.initial() =
      _UserProfileDetailsStateInitial;

  ///Loading
  const factory UserProfileDetailsState.loading() =
      _UserProfileDetailsStateLoading;

  ///Data
  const factory UserProfileDetailsState.data(
      {required ProfileEntity profileEntity}) = _UserProfileDetailsStateData;

  ///Error
  const factory UserProfileDetailsState.error([String? error]) =
      _UserProfileDetailsStateError;
}
import 'package:freezed_annotation/freezed_annotation.dart';
import '....features/profile/domain/entities/user_properties_entity.dart';

part 'user_properties_state.freezed.dart';

@freezed
class UserPropertiesState with _$UserPropertiesState {
  ///Initial
  const factory UserPropertiesState.initial() = _UserPropertiesStateInitial;

  ///Loading
  const factory UserPropertiesState.loading() = _UserPropertiesStateLoading;

  ///Data
  const factory UserPropertiesState.data(
          {required UserPropertiesEntity userPropertiesEntity}) =
      _UserPropertiesStateData;

  ///Error
  const factory UserPropertiesState.error([String? error]) =
      _UserPropertiesStateError;
}

And these are the two notifiers:

import '...core/di/dependency_injection.dart';
import '...core/errors/failures.dart';
import '...core/presentation/riverpod/check_token_notifier.dart';
import '...features/profile/presentation/riverpod/user_profile_details_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'user_profile_details_notifier.g.dart';

@riverpod
class UserProfileDetailsNotifier extends _$UserProfileDetailsNotifier {
  @override
  UserProfileDetailsState build() {
    getUserProfileDetailsData();
    return const UserProfileDetailsState.initial();
  }

  Future<void> getUserProfileDetailsData() async {
    state = const UserProfileDetailsState.loading();
    final userProfileDetailsOrFailure = await ref
        .read(userProfileDetailsUseCaseProvider)
        .getUserProfileDetailsData();
    userProfileDetailsOrFailure.fold((error) {
      if (error is TokenFailure) {
        ref.read(checkTokenNotifierProvider.notifier).deAuthUser();
        return;
      }
      state = UserProfileDetailsState.error(error.errorMessage);
    }, (userProfileDetailsEntity) {
      state =
          UserProfileDetailsState.data(profileEntity: userProfileDetailsEntity);
    });
  }
}
import '...core/di/dependency_injection.dart';
import '...core/errors/failures.dart';
import '...core/presentation/riverpod/check_token_notifier.dart';
import '...features/profile/presentation/riverpod/user_properties_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'user_properties_notifier.g.dart';

@riverpod
class UserPropertiesNotifier extends _$UserPropertiesNotifier {
  @override
  UserPropertiesState build() {
    getUserPropertiesData();
    return const UserPropertiesState.initial();
  }

  Future<void> getUserPropertiesData() async {
    state = const UserPropertiesState.loading();

    final userPropertiesOrFailure =
        await ref.read(userPropertiesUseCaseProvider).getUserPropertiesData();
    userPropertiesOrFailure.fold((error) {
      if (error is TokenFailure) {
        ref.read(checkTokenNotifierProvider.notifier).deAuthUser();
        return;
      }
      state = UserPropertiesState.error(error.errorMessage);
    }, (userPropertiesEntity) {
      state =
          UserPropertiesState.data(userPropertiesEntity: userPropertiesEntity);
    });
  }
}

2

Answers


  1. You can create a separate @freezed class where the corresponding new fields will be collected based on your two states. In code, you can do this:

    state = CommonState.loading();
    
    UserPropertiesState userProperties;
    UserProfileDetailsState userProfileDetails;
    
    final CommonState? state = userProperties.whenOrNull(
      orElse: () => null,
      data: (properties) => userProfileDetails.whenOrNull(
          orElse: () => null,
          data: (details) => CommonState.data(properties + details),
      ),
    );
    
    if (state == null) {
      // same with repeat with error
    }
    

    You can use the required fields in whenOrNull, or split into ifs

    Login or Signup to reply.
  2. To combine the states of two different notifiers and emit a success state only if both states have data or emit an error state if one of them is an error, you can use ProviderContainer to listen to both states and update a new state accordingly. Here’s an example of how you can achieve this: First, create a new state class that represents the combined state:

    import 'package:freezed_annotation/freezed_annotation.dart';
    import '...features/profile/domain/entities/user_profile_entity.dart';
    import '...features/profile/domain/entities/user_properties_entity.dart';
    
    part 'combined_states.freezed.dart';
    
    @freezed
    class CombinedStates with _$CombinedStates {
      ///Loading
      const factory CombinedStates.loading() = _CombinedStateLoading;
    
      ///Data
      const factory CombinedStates.success({
        required ProfileEntity profileEntity,
        required UserPropertiesEntity userPropertiesEntity,
      }) = _CombinedStateSuccess;
    
      ///Error
      const factory CombinedStates.error([String? error]) = _CombinedStateError;
    }
    

    Next, create a new notifier that combines the states of both UserProfileDetailsNotifier and UserPropertiesNotifier:

    import '...core/presentation/riverpod/check_token_notifier.dart';
    import '...features/profile/presentation/riverpod/user_profile_details_state.dart';
    import '...features/profile/presentation/riverpod/user_properties_state.dart';
    import 'path/to/combined_states.dart';
    import 'package:riverpod_annotation/riverpod_annotation.dart';
    
    final combinedNotifierProvider =
        Provider<CombinedNotifier>((ref) => CombinedNotifier(ref.read));
    
    class CombinedNotifier extends StateNotifier<CombinedStates> {
      final Reader _read;
    
      late final UserProfileDetailsNotifier _userProfileDetailsNotifier;
      late final UserPropertiesNotifier _userPropertiesNotifier;
    
      CombinedNotifier(this._read) : super(const CombinedStates.loading()) {
        _userProfileDetailsNotifier =
            _read(userProfileDetailsNotifierProvider.notifier);
        _userPropertiesNotifier = _read(userPropertiesNotifierProvider.notifier);
    
        // Start listening to both notifiers
        _userProfileDetailsNotifier.addListener(_onProfileDetailsStateChanged);
        _userPropertiesNotifier.addListener(_onUserPropertiesStateChanged);
      }
    
      void _onProfileDetailsStateChanged() {
        final userProfileDetailsState = _userProfileDetailsNotifier.state;
        final userPropertiesState = _userPropertiesNotifier.state;
    
        // Handle the combination of states
        if (userProfileDetailsState is UserProfileDetailsState.loading() ||
            userPropertiesState is UserPropertiesState.loading()) {
          state = const CombinedStates.loading();
        } else if (userProfileDetailsState is UserProfileDetailsState.error ||
            userPropertiesState is UserPropertiesState.error) {
          final error = userProfileDetailsState is UserProfileDetailsState.error
              ? userProfileDetailsState.error
              : userPropertiesState.error;
          state = CombinedStates.error(error);
        } else if (userProfileDetailsState is UserProfileDetailsState.data &&
            userPropertiesState is UserPropertiesState.data) {
          state = CombinedStates.success(
            profileEntity: userProfileDetailsState.profileEntity,
            userPropertiesEntity: userPropertiesState.userPropertiesEntity,
          );
        }
      }
    
      void _onUserPropertiesStateChanged() {
        // Call the same handler as profile details state changed
        _onProfileDetailsStateChanged();
      }
    
      @override
      void dispose() {
        // Stop listening to notifiers when disposing
        _userProfileDetailsNotifier.removeListener(_onProfileDetailsStateChanged);
        _userPropertiesNotifier.removeListener(_onUserPropertiesStateChanged);
        super.dispose();
      }
    }
    

    Finally, you can use the CombinedNotifier in your UI to access the combined state:

    final combinedNotifier = useProvider(combinedNotifierProvider);
    
    Widget build(BuildContext context) {
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search