flutterriverpodflutter-freezedriverpod-generatorriverpod-annotation

Merge different states with Riverpod and Freezed


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);
    });
  }
}


Solution

  • 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