Search code examples
flutterclean-architectureriverpodriverpod-generator

Riverpod 2.0 generator and Clean Architecture


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



Solution

  • 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