spebbe / dartz

Functional programming in Dart
MIT License
749 stars 60 forks source link

Perform several async operations in one flow #115

Closed VincentJouanne closed 1 year ago

VincentJouanne commented 1 year ago

Hey everyone ! First of all, thank you for this amazing library, my code gets a lot cleaner ❤️

Still, I have a question, I am trying to refactor a piece of code but didn't achieve to do so...

NB: gateways and repositories are Future<Either<E,R>>.

    User fetchedUser = User.empty;
    List<Training> fetchedTrainings = [];

    final currentUserAccount = _authenticationGateway.currentUser;
    final currentUserId = currentUserAccount.id;

    final getByIdResult =
          await _userRepository.getById(userId: currentUserId);

    getByIdResult.isLeft()
          ? emit(const _Error())
          // Here I know I've a User, I'd like to access it directly and not perform a getOrElse()
          : fetchedUser = getByIdResult.getOrElse(() => User.empty);

     final getAllByWeek = await _trainingRepository.getAllByWeekNumber(
          weekNumber: fetchedUser.week);

     getAllByWeek.isLeft()
          ? emit(const _Error())
          : fetchedTrainings = getAllByWeek.getOrElse(() => []);

     final shortTraining = fetchedTrainings.firstWhere((element) =>
          element.type == const TrainingType(TrainingTypeEnum.short));

     final longTraining = fetchedTrainings.firstWhere((element) =>
          element.type == const TrainingType(TrainingTypeEnum.long));

     // Ugly condition I wanna get rid of
     if (fetchedUser.isNotEmpty && fetchedTrainings.isNotEmpty) {
       //Do Stuff
        emit(const _Success(())
      } else {
        emit(const _Error());
     }

Coming from Typescript and using fp-ts I used to have one flow with: pipe(taskEither.chain(), taskEither.map(), etc.) but I did not find the equivalent with dartz.

I think this issue might help a lot of people 😁

Have a nice day !

VincentJouanne commented 1 year ago

Well, I moved to fpdart and found a way:

    emit(const _Loading());

    final currentUserAccount = _authenticationGateway.currentUser;
    final currentUserId = currentUserAccount.id;

    final useCase = await _userRepository
        .getById(userId: currentUserId)
        .flatMap((user) => _trainingRepository
                .getAllByWeekNumber(weekNumber: user.week)
                .chainEither(
              (trainings) {
                final shortTraining = trainings.firstWhere((element) =>
                    element.type == const TrainingType(TrainingTypeEnum.short));

                final longTraining = trainings.firstWhere((element) =>
                    element.type == const TrainingType(TrainingTypeEnum.long));

                return right(Tuple2(shortTraining, longTraining));
              },
            ).chainEither((trainings) {
              final status = _computeTrainingStatus(
                userAccountCreationDay: currentUserAccount.creationDate,
                today: _dateProvider.now(),
                trainingsDone: user.trainingsDone,
                firstTrainingId: trainings.item1.id,
                secondTrainingId: trainings.item2.id,
                userFirstTrainingDay: user.trainingDays[0].weekDay,
                userSecondTrainingDay: user.trainingDays[1].weekDay,
              );
              final trainingDates =
                  _computeTrainingDate(user, _dateProvider.now());

              return right(Tuple4(user, trainings, status, trainingDates));
            }))
        .run();
    useCase.fold((l) => emit(const _Error()), (result) {
      emit(_Success(
        user: result.item1,
        firstTraining: result.item2.item1,
        firstTrainingStatus: result.item3.a,
        firstTrainingDate: result.item4.a,
        secondTraining: result.item2.item2,
        secondTrainingStatus: result.item3.b,
        secondTrainingDate: result.item4.b,
      ));
    });