Riverpod 2.0 generator and Clean Architecture

1k Views Asked by At

I am trying to use the riverpod 2.0 state management with generator and Clean Architecture. My goal is simple: I do an API call to fetch a document from a server, and if it fails, I fetch the file from assets. I've already successfully set the data (data sources, model and repo impl.) and domain (entities, repo, use case) layers. Now I am working on the presentation layer but I am sure that I am doing something wrong.

In particular, I don't think that init the data sources and repo impl. in this way is completely correct. But mainly the problem is that on the page in the data state, I don't receive the Entity. I already tried to change the notifier return type but I was able only to get the State and not the Entity.

Any suggestion?

Use case:

import 'package:example/features/app_language/domain/repositories/available_languages_repository.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_state.dart';

class AvailableLanguagesUseCase {
 final AvailableLanguagesRepository availableLanguagesRepository;

 AvailableLanguagesUseCase({required this.availableLanguagesRepository});

 Future<AvailableLanguagesState> getAvailableLanguages() async {
   final availableLanguages =
       await availableLanguagesRepository.getAvailableLanguages();
   return availableLanguages
       .fold((error) => const AvailableLanguagesState.error(),
           (availableLanguagesEntity) {
     print(availableLanguagesEntity);
     return AvailableLanguagesState.data(
         availableLanguagesEntity: availableLanguagesEntity);
   });
 }
}

State:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:example/features/app_language/domain/entities/available_languages_entity.dart';

part 'available_languages_state.freezed.dart';

@freezed
abstract class AvailableLanguagesState with _$AvailableLanguagesState {
  ///Loading
  const factory AvailableLanguagesState.loading() =
      _AvailableLanguagesStateLoading;

  ///Data
  const factory AvailableLanguagesState.data(
          {required AvailableLanguagesEntity availableLanguagesEntity}) =
      _AvailableLanguagesStateData;

  ///Error
  const factory AvailableLanguagesState.error([String? error]) =
      _AvailableLanguagesStateError;
}

AsyncNotifier:

import 'package:http/http.dart' as http;
import 'package:example/features/app_language/data/datasources/available_languages_local_data_source.dart';
import 'package:example/features/app_language/data/datasources/available_languages_remote_data_source.dart';
import 'package:example/features/app_language/data/repositories/available_languages_repository_impl.dart';
import 'package:example/features/app_language/domain/usecase/available_languages_use_case.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'available_languages_notifier.g.dart';

@riverpod
class AvailableLanguagesAsyncNotifier
    extends _$AvailableLanguagesAsyncNotifier {
  @override
  Future<void> build() async {
    return getAvailableLanguages();
  }

  Future<void> getAvailableLanguages() async {
    final AvailableLanguagesLocalDataSourceImpl
        availableLanguagesLocalDataSourceImpl =
        AvailableLanguagesLocalDataSourceImpl();
    final AvailableLanguagesRemoteDataSourceImpl
        availableLanguagesRemoteDataSourceImpl =
        AvailableLanguagesRemoteDataSourceImpl(client: http.Client());

    final AvailableLanguagesUseCase availableLanguagesUseCase =
        AvailableLanguagesUseCase(
            availableLanguagesRepository: AvailableLanguagesRepositoryImpl(
                availableLanguagesLocalDataSource:
                    availableLanguagesLocalDataSourceImpl,
                availableLanguagesRemoteDataSource:
                    availableLanguagesRemoteDataSourceImpl));

    state = const AsyncValue.loading();
    //TODO DELETE DELAY
    await Future.delayed(const Duration(seconds: 2));
    state = AsyncValue.data(
        await availableLanguagesUseCase.getAvailableLanguages());
  }
}

Page:

class InitialSetupPage extends StatelessWidget {
 const InitialSetupPage({super.key});

 @override
 Widget build(BuildContext context) {
   return Consumer(
     builder: (context, ref, child) {
       final a = ref.watch(availableLanguagesAsyncNotifierProvider);
       print("state: $a");
       return a.maybeWhen(
         loading: () => Container(
           color: Colors.purple,
         ),
         data: (availableLanguagesEntity) =>
             Container(color: Colors.green, child: Center()),
         error: (error, stackTrace) => Container(
           color: Colors.red,
         ),
         orElse: () => Container(
           color: Colors.lightBlue,
         ),
       );
     },
   );
 }
}

Thanks in advance

EDIT:

Just to add details to what I've already tried but did not convince me:

Notifier:

import 'package:http/http.dart' as http;
import 'package:example/features/app_language/data/datasources/available_languages_local_data_source.dart';
import 'package:example/features/app_language/data/datasources/available_languages_remote_data_source.dart';
import 'package:example/features/app_language/data/repositories/available_languages_repository_impl.dart';
import 'package:example/features/app_language/domain/usecase/available_languages_use_case.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'available_languages_notifier.g.dart';

@riverpod
class AvailableLanguagesAsyncNotifier
    extends _$AvailableLanguagesAsyncNotifier {
  @override
  Future<AvailableLanguagesState> build() async {
    getAvailableLanguages();
    return const AvailableLanguagesState.loading();
  }

  getAvailableLanguages() async {
    final AvailableLanguagesLocalDataSourceImpl
        availableLanguagesLocalDataSourceImpl =
        AvailableLanguagesLocalDataSourceImpl();
    final AvailableLanguagesRemoteDataSourceImpl
        availableLanguagesRemoteDataSourceImpl =
        AvailableLanguagesRemoteDataSourceImpl(client: http.Client());

    final AvailableLanguagesUseCase availableLanguagesUseCase =
        AvailableLanguagesUseCase(
            availableLanguagesRepository: AvailableLanguagesRepositoryImpl(
                availableLanguagesLocalDataSource:
                    availableLanguagesLocalDataSourceImpl,
                availableLanguagesRemoteDataSource:
                    availableLanguagesRemoteDataSourceImpl));

    state = const AsyncValue.loading();
    //TODO DELETE DELAY
    await Future.delayed(const Duration(seconds: 2));
    state = AsyncValue.data(
        await availableLanguagesUseCase.getAvailableLanguages());
  }
}

Page:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_notifier.dart';

class InitialSetupPage extends StatelessWidget {
  const InitialSetupPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (context, ref, child) {
        final a = ref.watch(availableLanguagesAsyncNotifierProvider);
        print("state: $a");
        return a.maybeWhen(
          loading: () => Container(
            color: Colors.purple,
          ),
          data: (availableLanguagesEntity) => Container(
              color: Colors.green,
              child: Center(
                child: Text(availableLanguagesEntity.maybeWhen(
                    data: (availableLanguagesEntity) =>
                        availableLanguagesEntity.availableLanguagesEntity.first,
                    orElse: () {
                      return " ";
                    })),
              )),
          error: (error, stackTrace) => Container(
            color: Colors.red,
          ),
          orElse: () => Container(
            color: Colors.lightBlue,
          ),
        );
      },
    );
  }
}


1

There are 1 best solutions below

0
On BEST ANSWER

Found a solution. The main issue was the use case class.

Now it returns Future<Either<Failure, AvailableLanguagesEntity>> and not the AvailableLanguagesState

import 'package:dartz/dartz.dart';
import 'package:example/core/errors/failures.dart';
import 'package:example/features/app_language/domain/entities/available_languages_entity.dart';
import 'package:example/features/app_language/domain/repositories/available_languages_repository.dart';

class AvailableLanguagesUseCase {
  final AvailableLanguagesRepository availableLanguagesRepository;

  AvailableLanguagesUseCase({required this.availableLanguagesRepository});

  Future<Either<Failure, AvailableLanguagesEntity>>
      getAvailableLanguages() async {
    return await availableLanguagesRepository.getAvailableLanguages();
  }
}

This is the Notifier

import 'package:http/http.dart' as http;
import 'package:example/features/app_language/data/datasources/available_languages_local_data_source.dart';
import 'package:example/features/app_language/data/datasources/available_languages_remote_data_source.dart';
import 'package:example/features/app_language/data/repositories/available_languages_repository_impl.dart';
import 'package:example/features/app_language/domain/usecase/available_languages_use_case.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'available_languages_notifier.g.dart';

@riverpod
class AvailableLanguagesNotifier extends _$AvailableLanguagesNotifier {
  @override
  AvailableLanguagesState build() {
    getAvailableLanguages();
    return const AvailableLanguagesState.loading();
  }

  void getAvailableLanguages() async {
    final AvailableLanguagesLocalDataSourceImpl
        availableLanguagesLocalDataSourceImpl =
        AvailableLanguagesLocalDataSourceImpl();
    final AvailableLanguagesRemoteDataSourceImpl
        availableLanguagesRemoteDataSourceImpl =
        AvailableLanguagesRemoteDataSourceImpl(client: http.Client());

    final AvailableLanguagesUseCase availableLanguagesUseCase =
        AvailableLanguagesUseCase(
            availableLanguagesRepository: AvailableLanguagesRepositoryImpl(
                availableLanguagesLocalDataSource:
                    availableLanguagesLocalDataSourceImpl,
                availableLanguagesRemoteDataSource:
                    availableLanguagesRemoteDataSourceImpl));

    //TODO DELETE DELAY
    await Future.delayed(const Duration(seconds: 2));

    final failureOrAvailableLanguagesEntity = await availableLanguagesUseCase.getAvailableLanguages();

    failureOrAvailableLanguagesEntity .fold(
        (error) => state = const AvailableLanguagesState.error(),
        (availableLanguagesEntity) async => state =
            AvailableLanguagesState.data(
                availableLanguagesEntity: availableLanguagesEntity));
  }
}

Page:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_notifier.dart';

class InitialSetupPage extends StatelessWidget {
  const InitialSetupPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (context, ref, child) {
        final a = ref.watch(availableLanguagesNotifierProvider);

        print("state: $a");
        return a.maybeWhen(
          loading: () => Container(
            color: Colors.purple,
          ),
          data: (state) => Container(
              color: Colors.green,
              child: Center(
                child: Text(state.availableLanguagesEntity.first),
              )),
          error: (_) => Container(
            color: Colors.red,
          ),
          orElse: () => Container(
            color: Colors.lightBlue,
          ),
        );
      },
    );
  }
}

Still have some doubts about the quality of the AvailableLanguagesNotifier