What is right way to pass error to widget - flutter

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.

Related

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.

flutter return future list in a var to use outside the loop

Hello I'm trying to recuperate the list value of a database.
i can but what i want is to export the result in a var so i can use in all my code just by calling "print(myList);"
this is my code :
static const URL =
'https://xxxhost/employee_actions3.php';
static Future<List<Employee>> getEmployees() async {
try {
final response = await http.post(Uri.parse(
URL,
));
print("getEmployees >> Response:: ${response.body}");
if (response.statusCode == 200) {
List<Employee> list = parsePhotos(response.body);
return list;
} else {
throw <Employee>[];
}
} catch (e) {
return <Employee>[];
}
}
and my classe Employee
class Employee {
String id;
String firstName;
String lastName;
Employee({required this.id, required this.firstName, required this.lastName});
factory Employee.fromJson(Map<String, dynamic> json) {
return Employee(
id: json['id'] as String,
firstName: json['lat'] as String,
lastName: json['lng'] as String,
);
}
}
can i have help please ?
There are two ways to access async data in most modern languages, including dart, they are:
1. By providing a callback then
2. By using the function in an async context and awaiting the result
I've wrapped the code above in a class called API so the examples below are easier to follow,
class API {
static const URL = 'https://xxxhost/employee_actions3.php';
static Future<List<Employee>> getEmployees() async {
try {
final response = await http.post(Uri.parse(URL));
print("getEmployees >> Response:: ${response.body}");
if (response.statusCode == 200) {
List<Employee> list = parsePhotos(response.body);
return list;
} else {
throw("${response.statusCode} Failed to parse photos");
}
} catch (e) {
throw e;
}
}
}
Method 1: Providing a callback to .then, this method will allow you to work with async actions in a synchronous context, but be aware it will not halt the execution flow.
void main() {
API.getEmployees().then((resp) => print(resp)).catchError(e) => print(e);
}
Method 2: Async/Await, this method will allow you to access the data inline, that is var x = await myAsyncFunc() remember the await keyword requires the function to be called within an async context. And the await keyword will halt the execution flow till the future completes.
void main() async {
try {
final list = await API.getEmployees();
print(list);
} catch (e) {
print(e);
}
}
Using either one of the two methods outlined above will allow you to access the data of the list later on.
Additional Reading:
Async programming in dart
Futures and error handling

Riverpod giving a bad state exception when one hits back button on webpage

I'm getting this error in my StateNotifiers when one hits the back button on their webpage. I've isolated it to happening where the longRunningAPI request is below.
Exception has occurred.
"Error: Bad state: Tried to use RunListNotifier after `dispose` was called.
and I have code like this.
final runListController = StateNotifierProvider.autoDispose
.family<RunListNotifier, AsyncValue<List<Run>>, RunListParameter>(
(ref, param) {
return RunListNotifier(read: ref.read, param: param);
});
class RunListNotifier extends StateNotifier<AsyncValue<List<Run>>> {
RunListNotifier({required this.read, required this.param})
: super(AsyncLoading()) {
fetchViaAPI(param);
}
final Reader read;
final RunListParameter param;
void fetchViaAPI(RunListParameter param) async {
state = AsyncLoading();
try {
List<Run> stuff = await read(apiProvider).longRunningAPI(param: param);
state = AsyncData(stuff);
} catch (e) {
state = AsyncError(e);
}
}
}
is it safe to simply do something like this in the catch?
} catch (e) {
if (e.runtimeType.toString() == 'StateError') {
// ignore the error
} else {
state = AsyncError(e);
}
}
I believe you could solve this problem by checking mounted before setting the state after your API call like so:
List<Run> stuff = await read(apiProvider).longRunningAPI(param: param);
if (!mounted) return;
state = AsyncData(stuff);
This simply checks if dispose was called and if so, don't attempt to modify the state.
Another resource that could be useful is adding a cancelToken to your API call and canceling if the provider is disposed.
final longRunningApi = FutureProvider.autoDispose.family<List<Run>, RunListParameter>((ref, param) async {
final cancelToken = CancelToken();
ref.onDispose(cancelToken.cancel);
final api = await ref.watch(apiProvider);
final res = await api.longRunningApi(param, cancelToken);
ref.maintainState = true;
return res;
});
Then you'd have to add the cancelToken to your actual request. A great example of this in the marvel example project by the author of Riverpod can be found here.

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.