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

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.

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("");
}
}
}

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.

Dart - Problem replicating sealed classes like kotlin

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.

dart is rethrow exception to the calling function

I have two classes the first is AppModel and AppViewModel
AppModel have a method that do some api operation and return the value according to the state.
AppViewModel has a method that calling the appModel object method to do the api request.
AppModel's method code is:
try {
final response = await apicall();
if (response.statusCode != 200)
return Resource.error(jsonDecode(response.error)['message']);
return Resource.success(
ProductDetails.fromJson(jsonDecode(response.body)).productData);
} catch (error, stackTrace) {
print('Exception occurred: $error stackTrace: $stackTrace');
return Resource.error(error);
}
The problem is when an error has raised the catch method is called as expected and it should return a value to the calling but instead the error is also thrown to the AppViewModel function and that cause an exception is not handled error.
How to prevent the AppModel function from throwing the error to the AppViewModel function and instead return the value specified.
The Resource class code is :
class Resource<T> {
Resource({#required this.status, this.data, this.message});
String message;
Status status;
T data;
factory Resource.success(T data) =>
Resource(status: Status.SUCCESS, data: data);
factory Resource.error(String msg) =>
Resource(status: Status.ERROR, message: msg);
factory Resource.loading() =>
Resource(status: Status.LOADING);
}

dartz: exception in function is not caught in attempt

I am using dartz with flutter. I want to use Either type with all API calls as suggested in this article
Below is the way I have wrapped API to Task
Future<Either<ApiException, SendOtpDto>> receiveOtp(
String phoneNumber) async {
return Task<SendOtpDto>(() async {
final String url = 'AuthenticationApi/sendOtp/$phoneNumber';
final Map<String, dynamic> response = await apiWrapper.get(url);
final SendOtpDto dto = SendOtpDto.fromJson(response);
if (!dto.success) {
throw ServerErrorException(dto.error); //Exception is thrown here
}
return dto;
}).attempt().mapLeftToFailure().run();
}
I expect attempt() should catch all throw as ApiException. Currently it gives error type 'Future<Either<ApiException, dynamic>>' is not a subtype of type 'FutureOr<Either<ApiException,
SendOtpDto>>
Below is how I am using the Api
if (event is GetOtpButtonPressed) {
yield LoginApiLoading();
final Either<ApiException, SendOtpDto> result = await apiManager.authentication(context).receiveOtp(event.username);
yield result.fold<LoginState>(
(ApiException exception) { return LoginFailure(error: exception.toString()); },
(SendOtpDto dto) { return LoginOtpSent(dto: dto); });
}