Dart - Problem replicating sealed classes like kotlin - flutter

I'm getting this error on a starting example that I'm writing
type 'Error<dynamic>' is not a subtype of type 'FutureOr<Success<List<TaskEntity>>>'
I've created a generic Result class like this:
enum DataStatus { local, remote, error }
enum ErrorStatus { unknown, backend_error, timeout }
#sealed
abstract class Result<T> {}
class Success<T> extends Result<T> {
final T data;
final DataStatus dataStatus;
final String message;
Success({this.data, this.dataStatus, this.message});
}
class Error extends Result {
final Exception exception;
final ErrorStatus errorStatus;
final String message;
Error({this.exception, this.errorStatus, this.message});
}
On my repository I have this method:
Future<Result<List<TaskEntity>>> getTasks() {
return remoteDataSource
.getTasks()
.then((value) => Success(
data: value.map((e) => e.toTaskEntity()).toList(),
dataStatus: DataStatus.remote))
.catchError((Object obj) {
switch (obj.runtimeType) {
case DioError:
final dioErrorResponse = (obj as DioError).response;
return Error(
exception: Exception(dioErrorResponse.statusMessage),
errorStatus: ErrorStatus.backend_error,
message: dioErrorResponse.statusMessage);
break;
default:
}
});
My approach is to use this class to return the responses and If there's an error return the error.
Right now I'm blocked because I'm getting this error
type 'Error<dynamic>' is not a subtype of type 'FutureOr<Success<List<TaskEntity>>>'
I also tried to use the freezed library but no luck.
part 'result.freezed.dart';
#freezed
abstract class Result<T> with _$Result<T> {
const factory Result.success({T data, DataStatus dataStatus, String message}) = Success<T>;
const factory Result.error({Exception exception, ErrorStatus errorStatus, String message}) = Error;
}

Your problem is that you never specified a T for your error class, neither directly, nor implicitely. So it is an Error<dynamic>. But an Error<dynamic> does not extend Result<List<TaskEntity>>. An Error<List<TaskEntity>> would. So you need to make sure that your Error class gets a type for T too.
To be honest the whole .then .catch thing is way to complicated for me. I would have used async and await with normal try/catch blocks. But if you want to keep it as it is, at least make sure you do this:
return Error<List<TaskEntity>>(
so your Error<> class is of the right type syntactically.
Just as an example of what I mean by making it easier by using async and await:
Future<Result<List<TaskEntity>>> getTasks() async {
try {
final results = await remoteDataSource.getTasks();
final tasks = results.map((e) => e.toTaskEntity()).toList()
return Success<List<TaskEntity>>(data: tasks, dataStatus: DataStatus.remote);
} catch (e) {
if (e is DioError) {
return Error<List<TaskEntity>>(
exception: Exception(e.response.statusMessage),
errorStatus: ErrorStatus.backend_error,
message: e.response.statusMessage);
} else {
// you are missing a case here? what do you want to return here?
}
}
}
For me this is a lot easier to read, to the point where it's obvious you are missing a case. There needs to be a return statement there.

Related

The argument type 'List<HospitalListModel>?' can't be assigned to the parameter type 'HospitalListModel'

i have bloc class and it throw an error with a message The argument type 'List<HospitalListModel>?' can't be assigned to the parameter type 'HospitalListModel'.
this is the bloc class:
class HospitalListBloc extends Bloc<HospitalListEvent, HospitalListState> {
HospitalListBloc() : super(HospitalListInitial()) {
final ApiRepository _apiRepository = ApiRepository();
on<GetCovidList>((event, emit) async {
try {
emit(HospitalListLoading());
final mList = await _apiRepository.fetchHospitalList();
emit(HospitalListLoaded(mList));
} on NetworkError {
emit(HospitalListError("Failed to fetch data. is your device online?"));
}
});
}
}
and the error is on emit(HospitalListLoaded(mList));, and in case if you want to know the API provider:
class ApiProvider {
final Dio _dio = Dio();
final String _url = 'http://lovemonster.my.id/hospital';
Future<List<HospitalListModel>?> fetchHospitalList() async {
try {
Response response = await _dio.get(_url);
return hospitalListModelFromJson(response.data);
} catch (error, stacktrace) {
print("Exception occurred: $error stackTrace: $stacktrace");
return Future.error("");
}
}
}
your HospitalListLoaded function should be
HospitalListLoaded(List<HospitalListModel> mList)
The argument type 'List<HospitalListModel>?' can't be assigned to the parameter type 'HospitalListModel'.
Your HospitalListLoaded function is declared as this:
void HospitalListLoaded(HospitalListModel model){
....
}
Here the parameter type is a single HospitalListModel, not a list of them. So, you can either pass a single HospitalListModel or you can change the parameter type to List<HospitalListModel>. In that case, you must change your logic inside that function.
Plus, notice the ? null operator. If the List you pass can be null, then the parameter type must be nullable. In that case,
void HospitalListLoaded(List<HospitalListModel>? models){
....
}
You are returning an Object of HospitalListModel but your Bloc class having method which accept list of HospitalListModel
You need to return list not an object
Check below code which will be useful
class ApiProvider {
final Dio _dio = Dio();
final String _url = 'http://lovemonster.my.id/hospital';
Future<List<HospitalListModel>?> fetchHospitalList() async {
try {
List<HospitalListModel> hospitalList = [];
Response response = await _dio.get(_url);
var mData = responseData.data as List;
hospitalList = mData.
.map<HospitalListModel?>((e) => hospitalListModelFromJson(e)
.toList();
return hospitalList;//return List not object
} catch (error, stacktrace) {
print("Exception occurred: $error stackTrace: $stacktrace");
return Future.error("");
}
}
}

Flutter Bloc/Cubit Error Handling - what is the best architectural approach?

I'm a beginner developer and I have problem with implementation of BloC framework. Let's assume that I have this code (Model, NetworkService, Repository, Cubit, State, Widget):
class NetworkService {
Future getData(Uri uri) async {
try {
http.Response httpsResponse = await http.get(
uri,
headers: {
// some headers //
},
);
if (httpsResponse.statusCode == 200) {
return httpsResponse.body;
} else {
throw 'Request failed with status: ${httpsResponse.statusCode}';
}
} catch (e) {
// What I shloud return here?
return e.toString();
}
}
Future<List<dynamic>> fetchData() async {
final uri = Uri.parse('some url');
var data = await getData(uri);
return = jsonDecode(data) as List;
}
}
class Repository {
final NetworkService networkService = NetworkService();
Future<List<SomeObject>> fetchDataList() async {
final dataRaw =
await networkService.fetchDataList();
return dataRaw.map((e) => SomeObject.fromJson(e)).toList();
}
}
class SomeCubit extends Cubit<CubitState> {
final Repository repository;
SomeCubit(this.repository) : super(LoadingState()) {
fetchDataList();
}
void fetchDataList() {
try {
repository
.fetchDataList()
.then((dataList) => emit(LoadedState(dataList)));
} catch (e) {
// What I shloud return here?
emit(ErrorState(e.toString()));
}
}
}
How to make this code "bullet proof" because I don't know how to "pass" error from NetworkService to Cubit? It works fine till I have dynamic responses in functions but in Repository class I want to return List of specific objects and when function fail I will return null. If I write try/catch I have to provide return statement in catch block - and I can't return List. I want to return some kind of Error...
I suggest that you use the excellent class named Either from the dartz package. It will allow you to return X if things went bad, and return Y if all is well, as such: Future<Either<X, Y>>
Then you can check on your variable (e.g. result) as follows: result.isLeft() for error, or do result.fold( ... ) to easily handle the return type (error or success).
In your particular case you could do as follows when returning from the repository to the cubit:
Future<Either<RepositoryError, List<SomeObject>>> fetchDataList() async { ... }
Where RepositoryError could be a class containing information about the type of error.
So in the cubit you do:
final result = await repository.fetchDataList();
emit(
result.fold(
(error) => ErrorState(error),
(dataList) => LoadedState(dataList)
)
);
Then you continue with this pattern all the way to NetworkService getData(). Either with the same common "error class" in the Repository and the NetworkService, or separate ones in the different layers and you "translate" between different "error classes". Perhaps it makes sense to have a NetworkServiceError that is returned there..
In your NetworkService you could do as follows:
Future<Either<NetworkServiceError, String>> getData(Uri uri) async { ... }
Future<Either<NetworkServiceError, List<dynamic>>> fetchData() async { ... }
This will give you great flexibility and passing of information from the service, to the repository and to the cubit.
You can let exceptions propagate through Futures from NetworkService up to the cubit, by removing the try/catch from getData.

Value of type 'Resource<dynamic>' can't be returned as T where T extends Resource

Here's my program (you can run it as a dart file):
/// Fetches data from TestRepository and shows result
main () async {
final repository = TestRepository();
final resource = await repository.fetchString();
if (resource.isError) {
print("| Error: ${resource.error}");
} else {
print("| Success: ${resource.data}");
}
}
/// A class to get Success or Error responses
class Resource<S> {
final S? _data;
final String? _message;
Resource.success(S data)
: _data = data,
_message = null;
Resource.error(String message)
: _data = null,
_message = message;
bool get isError => _message != null;
S get data => _data!;
String get error => _message!;
}
/// A sample repository class to use API and handle errors
class TestRepository {
Future<Resource<String>> fetchString() {
return _dummyApiCall().useErrorHandler();
}
/// Dummy API Call mimicking
Future<Resource<String>> _dummyApiCall() async {
await Future.delayed(const Duration(seconds: 1));
if (true) {
// to mimic an exception so that we can catch it
throw Exception("Error Occurred");
}
return Resource.success("Hello World");
}
}
/// An extension to wrap Resource Futures with error handler
extension FutureResourceExt<T extends Resource> on Future<T> {
Future<T> useErrorHandler() {
return onError((error, stacktrace) => handleError(error));
}
}
/// A function to handle errors thrown by Future.onError
T handleError<T extends Resource>(dynamic error) {
return Resource.error("Error: $error");
}
I have a code error on following line:
return Resource.error("Error: $error");
The error says:
A value of type 'Resource<dynamic>' can't be returned from the function 'handleError' because it has a return type of 'T'
If I change the implementation to add as T to above statement, code error disappears and gets thrown on runtime.
Unhandled Exception: type 'Resource<dynamic>' is not a subtype of type 'Resource<SomeDataType>' in type cast
I don't know why can I not assign Resource to T extends Resource return type.
How should I implement Resource class such that I can do this without knowing S type of Resource?
Here's the line with error code:
return Resource.error("Error: $error");
Here's the solution:
return Resource<Never>.error("Error: $error") as T;
I received a hint from this nice answer. Never knew something like Never existed.

How to cast Right side of Either to generic type in flutter

I have an extension to Task (from the dartz package) which looks like this
extension TaskX<T extends Either<Object, U>, U> on Task<T> {
Task<Either<Failure, U>> mapLeftToFailure() {
return this.map(
// Errors are returned as objects
(either) => either.leftMap((obj) {
try {
// So we cast them into a Failure
return obj as Failure;
} catch (e) {
// If it's an unexpected error (not caught by the service)
// We simply rethrow it
throw obj;
}
}),
);
}
}
The goal of this extension is to return a value of type Either<Failure, U>
This was working fine, until I switch my flutter project to null safety.
Now, for some reason, the return type is Either<Failure, dynamic>.
In my code, it looks like so:
await Task(
() => _recipesService.getProduct(),
)
.attempt() // Attempt to run the above code, and catch every exceptions
.mapLeftToFailure() // this returns Task<Either<Failure, dynamic>>
.run()
.then(
(product) => _setProduct(product), // Here I get an error because I expect a type of Either<Failure, Product>
);
My question is, how can I cast the Right side of Either to the correct type?

What is right way to pass error to widget

What is the right way to pass exception/failure from services or other parts to your widget.
I would like to pass error code from register to onSignIn. Whats the right way. Is it ok to do it the way I am doing or should I
again throw exception from register and catch it in onSignIn.
don't catch in register but catch in onSignIn
File A
void onSignIn() async {
dynamic result = await _auth.register();
if (result.runtimeType == String) {
print(result);
}
}
File B
Future register() async {
try {
AuthResult result = await _auth.createUser();
return _user(result.user);
} catch (e) {
return e.code;
}
}
For error handling I usually use a wrapper object to help me out better handling, errors or even "loading". This is a patter from one of Google's Android examples and I've used on flutter with success. So basically you create a generic class, in my case I call it "Response" that has the structure of:
class Response<T> {
final ApiError error;
final T data;
final ResponseType type;
Response({
this.error,
this.data,
this.type,
});
bool get isOk {
return type == ResponseType.ok;
}
bool get isLoading {
return type == ResponseType.loading;
}
bool get hasError {
return type == ResponseType.error;
}
factory Response.loading() {
return Response<T>(type: ResponseType.loading);
}
factory Response.ok(T data) {
return Response<T>(type: ResponseType.ok, data: data);
}
factory Response.empty() {
return Response<T>.ok(null);
}
factory Response.error(ApiError data) {
return Response<T>(type: ResponseType.error, error: data);
}
}
enum ResponseType { loading, error, ok }
ApiError is my custom implementation that handles errors for a specific project, it can be replaced with a generic Error.
This approach is super helpful when you're using Streams, because it won't close a stream in case of error, you have the possibility to mark the beginning of the task by replying with a loading and it will handle succes/error properly.
This approach would also allow you to send a more well defined error to the UI, let's say you need the error code, error message, and maybe you get an input field name where you want to show that error on.
Altho, in your case it might be a bit of overkill, I would still use it like this:
Future<Response<AuthResult>> register() async {
try {
AuthResult result = await _auth.createUser();
return Response.ok(_user(result.user));
} catch (e) {
return Response.error(e);
}
}
void onSignIn() async {
Response<AuthResult> result = await _auth.register();
if (result.isOk) {
//TODO: all good let's use data with result.data
}
else if (result.isLoading) {
// TOOD: well...show a loading indicator
}
else {
//TODO: we got error handle it using result.error
}
}
In flutters' documentation, they almost always caught the error in the parent function(take a look at here as an example). Also using dynamic may be dangerous since it accepts all kinds of objects, a better approach would be using the final keyword. so the preferred way would be:
Future<AuthResult> register() async {
AuthResult result = await _auth.createUser();
return _user(result.user);
}
and then:
void onSignIn() async {
try{
final result = await _auth.register();
print(result);
} on Exception catch(ex){
// do something with error
}
}
I usually like to use Provider for such kind of things.
class Auth with ChangeNotifier{
AuthResult _authResult;
AuthResult get authResult => _authResult;
}
enum AuthResult{
Successful,
Error,
OTPError,
}
Then, I will use the provider package to get the data wherever needed.