1

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

patana93
  • 73
  • 1
  • 8

1 Answers1

0

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

Ruble
  • 2,589
  • 3
  • 6
  • 29
  • Thanks for the reply. I have done this, but I hoped there was a cleaner way to do that because let's say that I will have 4 or 5 different API calls with different states, then I will have a cascade of whenOrNull that is not so nice to see. – patana93 May 10 '23 at 21:08
  • But you can create a state provider that listens on other states and updates it's state based on your logic. In your UI there you can watch on the new created provider that only updates their state if you want to / it needs to was that understandable? – st3ffb3 May 17 '23 at 07:24
  • 1
    At the end the solution was to make the States public and make a new ProfilePageWrapperState that watches the others 2 and makes an if(UserPropertiesStateData && UserProfileDetailsStateData) -> ProfilePageWrapperStateData – patana93 Jun 20 '23 at 16:48