I am trying to catch thrown exception when server does not return 200 status code. Here is the code:
late Future<List<Data>> datasFuture;
String _warning = "";
#override
void initState() {
try {
datasFuture = RestfulServiceProvider.fetchDatas();
} on Exception {
_warning = "There is no data to fetch!";
}
super.initState();
}
//RestfulServiceProvider class
static Future<List<Data>> fetchDatas() async {
List jsonResponse = [];
final response =
await http.get(Uri.parse('http://localhost:8080/datas'));
if (response.statusCode == 200) {
jsonResponse = json.decode(response.body);
return jsonResponse.map((data) => Data.fromJson(data)).toList();
} else {
throw Exception();
}
}
When exception occurs code does not go in on Exception block, I cant handle the error. Any idea or solutions? thanks.
Try-catch block can catch exceptions occured in the bounds of its block. Your will not catch error because you are just assigning Future to a variable ( not awaiting its value ). So can the block catch exceptions from single assignment operation? No. Variable is just assigned and the program moves on and quits try-catch block immediately. You must await the value to catch it the block - awaiting it. But you can not use async-await syntax directly inside initState. So you have 2 options:
catchError of Future
RestfulServiceProvider.fetchDatas().catchError((error){Your code here}));
Utilizing it into another function with async-await
void someFunc () async{
try {
await RestfulServiceProvider.fetchDatas();
} on Exception catch(e) {
_warning = "There is no data to fetch!";
}
}
And call it in initState
Related
Inside main.dart I am using the 2 callbacks for handling exceptions caught by flutter and not caught by flutter as follows:
void main() async {
MyErrorsHandler.initialize();
FlutterError.onError = (details) async {
FlutterError.presentError(details);
MyErrorsHandler.onErrorDetails(details);
};
PlatformDispatcher.instance.onError = (error, stack){
MyErrorsHandler.onError(error, stack);
return true;
};
runApp(const ProviderScope(child: MyApp()));
}
But when I throw exceptions myself like when making an HTTP request:
static Future<BasketViewDto> getAsync(String basketId) async {
final url = Uri.parse('${ApiConfigurations.BaseUrl}/Baskets/$basketId');
final response = await http.get(url);
if (response.statusCode == 200) {
final result = json.decode(response.body)['result'];
final BasketViewDto basket = BasketViewDto.fromJson(result);
return basket;
} else {
throw Exception("Failed to get Basket");
}
}
OnError does not catch that exception. So how can I define a global place where I can handle(log) all exceptions that I throw in any place inside my code?
I wrote the following code and encountered the error The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
I thought it was strange, so I debugged FutureProvider's onDispose and found that it was disposed during the await of the API call, which is confirmed by the output of disposed!
class HogeNotifier extends StateNotifier<Hoge> {
onFormSubmitted(String input) async {
final value = await _reader(searchProvider(input).future); // The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
// execute by using value
}
}
final searchProvider =
FutureProvider.autoDispose.family<Data, String>((ref, value) async {
ref.onDispose(() {
print("disposed!");
});
try {
final result = await dataSource.find(keyword: value); //call api asynchronously
return Future.value(result);
} on Exception catch (e) {
return Future<Data>.error(e);
}
});
Why does the above error occur? How can I solve this problem?
maybe this can help solve the problem:
final searchProvider =
FutureProvider.autoDispose.family<Data, String>((ref, value) async {
ref.keepAlive();
/// do next
});
main() async {
try {
final t = Test();
await Future.delayed(Duration(seconds: 1));
} catch (e) {
// Never printed
print("caught");
}
}
void willThrow() async {
throw "error";
}
class Test {
Test() {
willThrow();
}
}
If the "async" keyword is removed from willThrow everything works as expected.
Is it because you can't await a constructor? If so is there anyway to catch async errors in a constructor body?
Have this a go:
void main() async {
try {
final t = Test();
await Future.delayed(Duration(seconds: 1));
} catch (e) {
// Never printed
print("caught");
}
}
Future<void> willThrow() async {
throw "error";
}
class Test {
Test() {
willThrow().catchError((e){print('Error is caught here with msg: $e');});
}
}
As to the 'why':
You use a normal try/catch to catch the failures of awaited asynchronous computations. But since you cannot await the constructor, you have to register the callback that handles the exception in another way. I think :)
Since you never awaited the Future that was returned from willThrow(), and you never used the result of the Future, any exception thrown by the function is discarded.
There is no way to write an asynchronous constructor. So you are stuck with using old-school callbacks to handle errors, or simulate an async constructor with a static method:
void main() async {
try {
final t = await Test.create();
await Future.delayed(Duration(seconds: 1));
} catch (e) {
// Never printed
print("caught");
}
}
Future<void> willThrow() async {
throw "error";
}
class Test {
Test._syncCreate() {}
Future<void> _init() async {
await willThrow();
}
static Test create() async {
Test result = Test._syncCreate();
await result._init();
return result;
}
}
I upgraded Flutter from version 2.0.2 to version 2.2.2 and now the custom exceptions that are thrown from a Future function are not being catch.
For example, I got this Future function, where I call another Future that does a server request and returns back the response or throws a custom exception (ApiException) in case of error:
static Future<bool> signUpCustomerRequest(Map<String, dynamic> params) async {
try {
// Here we call this Future function that will do a request to server API.
dynamic _response = await _provider.signUpCustomer(params);
if (_response != null) {
updateUserData(_response);
return true;
}
return false;
} on ApiException catch(ae) {
// This custom exception is not being catch
ae.printDetails();
rethrow;
} catch(e) {
// This catch is working and the print below shows that e is Instance of 'ApiException'
print("ERROR signUpCustomerRequest: $e");
rethrow;
} finally {
}
}
And this is the Future function that does the request to server and throws the ApiException:
Future<User?> signUpCustomer(Map<String, dynamic> params) async {
// POST request to server
var _response = await _requestPOST(
needsAuth: false,
path: routes["signup_client"],
formData: params,
);
// Here we check the response...
var _rc = _response["rc"];
switch(_rc) {
case 0:
if (_response["data"] != null) {
User user = User.fromJson(_response["data"]["user"]);
return user;
}
return null;
default:
print("here default: $_rc");
// And here we have the throw of the custom exception (ApiException)
throw ApiException(getRCMessage(_rc), _rc);
}
}
Before upgrading to Flutter 2.2.2 the catch of custom exceptions worked perfectly. Did something change on this Flutter version? Am I doing something wrong?
Thanks!
I was able to reproduce your bug with the following code:
class ApiException implements Exception {
void printDetails() {
print("ApiException was caught");
}
}
Future<void> doSomething() async {
await Future.delayed(Duration(seconds: 1));
throw ApiException();
}
void main() async {
try {
await doSomething();
} on ApiException catch (ae) {
ae.printDetails();
} catch (e) {
print("Uncaught error: $e"); // This line is printed
}
}
There's an open issue on the dart sdk, which I think might be related, though I'm not sure: https://github.com/dart-lang/sdk/issues/45952.
In any case, I was able to correct the error by returning a Future.error, instead of throwing the error directly:
class ApiException implements Exception {
void printDetails() {
print("ApiException was caught"); // This line is printed
}
}
Future<void> doSomething() async {
await Future.delayed(Duration(seconds: 1));
return Future.error(ApiException());
}
void main() async {
try {
await doSomething();
} on ApiException catch (ae) {
ae.printDetails();
} catch (e) {
print("Uncaught error: $e");
}
}
I am trying to create a test for timeout using Dio, I expect get DioError with type CONNECT_TIMEOUT then throw a custom exception
My test I mock Dio with Mockito and try throw DioError
test(
'Should throw [ConnectionTimeOutException] when reach timeout',
() async {
//arange
when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
when(mockDio.post(paths.login, data: tParams.toJson())).thenThrow(
(_) async => DioError(type: DioErrorType.CONNECT_TIMEOUT));
//act
final call = loginDataSource.login;
//assert
expect(() => call(params: tParams),
throwsA(TypeMatcher<ConnectTimeOutException>()));
},
);
My data source class:
class LoginDataSourceImpl implements LoginDataSource {
final Dio dio;
final NetworkInfo networkInfo;
LoginDataSourceImpl({#required this.dio, #required this.networkInfo});
#override
Future<CredencialModel> login({#required Params params}) async {
if (!await networkInfo.isConnected) {
throw NoNetworkException();
}
try {
final response = await dio.post(paths.login, data: params.toJson());
if (response.statusCode == 200) {
return CredencialModel.fromJson(response.data);
} else if (response.statusCode == 400) {
final error = ResponseError.fromJson(response.data);
switch (error.error) {
case 'invalid_request':
throw InvalidRequestException();
break;
case 'invalid_device':
throw InvalidDeviceException();
break;
case 'invalid_user_credentials':
throw InvalidUserCredentialException();
break;
case 'user_disabled':
throw UserDisableException();
default:
throw UnknowException();
}
} else if (response.statusCode == 500) {
throw ServerException();
} else {
throw UnknowException();
}
} on DioError catch (e) {
if (e.type == DioErrorType.CONNECT_TIMEOUT) {
throw ConnectTimeOutException();
} else if (e.type == DioErrorType.RECEIVE_TIMEOUT) {
} else {
throw UnknowException();
}
}
}
}
The result of the test is:
Expected: throws <Instance of 'ConnectTimeOutException'>
Actual: <Closure: () => Future<CredencialModel>>
Which: threw <Closure: (dynamic) => DioError>
stack package:mockito/src/mock.dart 385:7
How can i solve this issue and create a Timeout test with Dio?
There are a couple of problems with your approach.
First, you are testing an async method but you are not awaiting it. This is going to cause the raw Future object to be returned to the expect function which is going to consider it a successful call, even if the future ends up throwing an error. You will need to await your call, although doing so as a closure passed to expect is awkward. I would suggest wrapping the asynchronous call in a try/catch instead.
Second, you are providing a closure to Mockito's thenThrow method. This method takes whatever you give to it and uses it as the actual thrown value, so it isn't going to call the closure you passed to it - it will just throw it as-is.
Fixing these both, you end up with this:
test(
'Should throw [ConnectionTimeOutException] when reach timeout',
() async {
// arrange
when(mockNetworkInfo.isConnected)
.thenAnswer(true);
when(mockDio.post(paths.login, data: tParams.toJson()))
.thenThrow(DioError(type: DioErrorType.CONNECT_TIMEOUT));
// act
final call = loginDataSource.login;
// assert
try {
await call(params: tParams);
} catch(e) {
expect(e, isInstanceOf<ConnectTimeOutException>());
}
},
);