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