Dart : Make reusable try catch block for error handling - dart

I have simple function login request to server, in this function i have some error handling .
Example Source Code
try {
final response = await http.post(
'${appConfig.baseApiUrl}/${appConfig.userController}/loginUser',
headers: appConfig.headersApi,
body: {
"username": username,
"password": password,
},
);
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson["status"] == "ok") {
List userList = responseJson['data'];
List<UserModel> result = userList.map((e) => UserModel.fromJson(e)).toList();
return result;
} else {
throw CustomError(responseJson['message']);
}
} on SocketException catch (_) {
return Future.error(ConstText.NO_CONNECTION);
} on TimeoutException catch (_) {
return Future.error(ConstText.TIMEOUT_EXCEPTION);
} on FormatException catch (_) {
return Future.error(ConstText.FORMAT_EXCEPTION);
} catch (e) {
return Future.error(e.toString());
}
}
In above source code, I have 4 Handling error like SocketException,TimeoutException,FormatException and UnknownException. This function work fine, but if i create another function for request server i should repeat the error handling again.
My question is , it's possible to make error handling reusable ? I want something like this.
Example Reusable Try Catch
requestServer(yourRequestServer) async{
try{
return yourRequestServer;
}on SocketException catch (_) {
return Future.error(ConstText.NO_CONNECTION);
} on TimeoutException catch (_) {
return Future.error(ConstText.TIMEOUT_EXCEPTION);
} on FormatException catch (_) {
return Future.error(ConstText.FORMAT_EXCEPTION);
} catch (e) {
return Future.error(e.toString());
}
}
How To Use it
Future<String> testLogin(String username,String password)async{
requestServer({
final response = await http.post(
'${appConfig.baseApiUrl}/${appConfig.userController}/loginUser',
headers: appConfig.headersApi,
body: {
"username": username,
"password": password,
},
);
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson["status"] == "ok") {
List userList = responseJson['data'];
List<UserModel> result = userList.map((e) => UserModel.fromJson(e)).toList();
return result;
} else {
throw CustomError(responseJson['message']);
}
});
}
Or if you have another suggestion , i really appreciate that.
Thank's.

Reusing code is always a matter of figuring out what to keep an what to abstract away.
In your case, you want to reuse the catch clauses.
The thing you are abstracting over is the body of the try clause, which appears to contain some asynchronous code.
Since you are abstracting over code, you'll need to pass in a function. That's the only way to make code into an argument value.
So you'll need something like:
Future<T> requestServer(FutureOr<T> computation()) {
try {
return await computation();
} on SocketException catch (_) {
throw ConstText.NO_CONNECTION;
} on TimeoutException catch (_) {
throw ConstText.TIMEOUT_EXCEPTION;
} on FormatException catch (_) {
throw ConstText.FORMAT_EXCEPTION;
} catch (e) {
throw e.toString();
}
}
You can then use it as:
var result = await requestServer(() {
final response = await http.post(...
...
return result;
...
});
I changed the return Future.error(someString); to throw someString because it's the same thing, and the latter is much more readable. You are throwing strings here, not exception or error objects. That's a bold move, but as long as you are the one to catch them again, it's reasonable. It's not a good API for other people to have to catch.

Related

Why can't catch exceptions with "catchError" in Dart?

Test:
void testAs() async {
try {
String b = await test();
print(b);
} catch (e) {
print("1 await error");
}
test().then((value) => print(value)).catchError(() {
print("2 then error");
});
}
Future<String> test() {
List<String> bb = ["2222"];
return Future.value(bb[1]);
}
1 await error
RangeError (index): Invalid value: Only valid value is 0: 1
Why is it ineffective?
If I want to deal with "future" through "then", how should I catch the exception and not let it throw out.
Thank you friends,the last problem has been solved,the problem can be solved by adding async and await flags to the test() method.
But there is a new problem, Now I use the correct code and find that it can only be printed once.why can't it print "then success",then the program ends
,modify as follows:
void testAs() async {
try {
await test();
print("await success");
} catch (e) {
print("await error");
}
test().then((value) => print("then success")).catchError((e) {
print("then error");
});
}
Future<String> test() async{
List<String> bb = ["2222"];
return await Future.value(bb[0]);
}
print:await success

Dart future with non async function

I am creating a function for firebase phone auth using Dart. There are two functions getCredential and then signIn. When using a try/catch block I am unsure of how this should be coded. Should the non-async function getCredential be outside of the try/catch block or inside?
Should it be coded as:
// Sign in with phone
Future signInWithPhoneNumber(String verificationId, String smsCode) async {
AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCode,
);
try {
AuthResult result = await _auth.signInWithCredential(credential);
FirebaseUser user = result.user;
return user;
} catch (e) {
print(e.toString());
return null;
}
}
Or should it be coded like this?
// Sign in with phone
Future signInWithPhoneNumber(String verificationId, String smsCode) async {
try {
AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCode,
);
AuthResult result = await _auth.signInWithCredential(credential);
FirebaseUser user = result.user;
return user;
} catch (e) {
print(e.toString());
return null;
}
}
If coded as the second option does the try/catch only work with the async function or both. For example, if the getCredential function generated an error would it be caught in the catch block?
Yes the catch will handle anything that throws in your try block, it's not async specific. To confirm this you could write a function that gets called at the beginning of the try for example:
// this is a function that throws
void doSomething(String param) {
if (param == null) {
throw FormatException('param can not be null');
}
}
Future signInWithPhoneNumber(String verificationId, String smsCode) async {
try {
doSomething(null); // this will be caught
AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCode,
);
AuthResult result = await _auth.signInWithCredential(credential);
FirebaseUser user = result.user;
return user;
} catch (e) {
print(e.toString()); // this prints 'FormatException: param can not be null'
return null;
}
}
So async is not related to whether your function will be caught or not so it's better to use the second option.

How to setState() of a text field after handling an error in Flutter?

I have a function called LoginWithFb(). The function has a try catch block:
void loginWithFb() async {
try {
var auth = AuthProvider.of(context).auth;
print('Signing up with fb...');
setState(() {
_showProgressIndicator = true;
});
FirebaseUser user = await auth.signInWithFBAcc();
uId = user?.uid;
if (uId != null) {
print('Signed in: $uId');
widget.onSignedIn(user);
} else {
print('fb login cancelled');
}
// _showAlert(context);
setState(() {
_showProgressIndicator = false;
});
} catch (exception) {
print(exception.toString());
setState(() {
_showProgressIndicator = false;
});
}
When the error is caught, I want to display message to the user. The message has to be in a text field and not via a dialog. At the moment I have an empty Text('') widget in my build method. I want to write text to the text widget when the error is caught..
Just use local variable for storing message and show it via Text widget
String message = "";
void loginWithFb() async {
try {
...
} catch (exception) {
print(exception.toString());
setState(() {
message = "Some error happens";
_showProgressIndicator = false;
});
}
In widget:
Text(message);

Pass over throwned error with completeError

I try to write an application that insert record into mongodb.
First look at my test:
test('Password test failed, not strong enough.', () {
Account.create({'name': 'eric', 'email': 'koston#mail.com', 'password': 'Test'})
.catchError((err) {
expect(err, throwsA(new isInstanceOf<DuplicateError>()));
});
});
This test should be failed, because the password is not strong enough. And the code, that try to insert record.
static Future<String> create(Map account) {
var completer = new Completer();
String hashed_password;
var self = new Account();
if(self._signUpKeys.length != account.length) {
return completer.completeError(new LengthError(I18n.instance.getTextByMap('TEXT1')));
}
for(var singUpKey in self._signUpKeys) {
if (!account.containsKey(singUpKey)) {
return completer.completeError(new ArgumentError(I18n.instance.getTextByMap('TEXT1')));
}
}
// Try to find duplication
Future.wait([findAccountByField('name', account['name']),
findAccountByField('email', account['email'])])
.then((Iterable<Map<String, String>> lists) {
// Check of any duplications
lists.forEach((value){
value.forEach((String key, String value) {
switch(key) {
case('name'):
return completer.completeError(new DuplicateError(
I18n.instance.getTextWithMarker('TEXT2', {'&1': value})));
case('email'):
return completer.completeError(new DuplicateError(
I18n.instance.getTextWithMarker('TEXT3', {'&1': value})));
}
});
hashed_password = Account.generateHashPassword(account['password']);
self._insert(self._fillDbFields(name: account['name'], email: account['email'], hashed_password: hashed_password,
created_at: new DateTime.now(), activated: false))
.then((result) => completer.complete(result));
});
})
.catchError((err) {
completer.completeError(err);
});
return completer.future;
}
this allocation will thrown an error, because the password is not according to security requirement.
hashed_password = Account.generateHashPassword(account['password']);
and this part should catch error and complete it.
.catchError((err) {
completer.completeError(err);
});
but in the test, I've got NoSuchMethodError. Why here, the error object is not pass over to test? What i do here wrong?
I think you should check what value lists has here
Future.wait([findAccountByField('name', account['name']),
findAccountByField('email', account['email'])])
.then((Iterable<Map<String, String>> lists) {
if it is null you can't call forEach on it

q.js automatically propagate errors (catch async thrown errors)?

i would like to know if there's any way to automatically propagate errors from a promise to another? IE: catch the thrown error from a nested promise.
for example, in the following code sample, the "internalWorker" nested promise function needs
.fail(function (error) {
return deferred.reject(error);
});
in order to propagate the error. if this line isn't contained, the error is throw to the top. (crashed app)
would it be possible to automatically propagate the error so i don't need to add .fail() functions to all my nested promises?
```
function top(input) {
var deferred = q.defer();
internalWorker(input).then(function (value) {
logger.inspectDebug("top success", value);
}).fail(function (error) {
return deferred.reject(error);
});
return deferred.promise;
}
function internalWorker(input) {
var deferred = q.defer();
q.delay(100).then(function () {
throw new Error("internal worker async error");
}).fail(function (error) {
return deferred.reject(error);
});
return deferred.promise;
}
top("hello").then(function (value) {
logger.inspectDebug("outside success", value);
}).fail(function (error) {
logger.inspectDebug("outside fail", error);
}).done();
```
If you are using https://github.com/kriskowal/q, this will do what you intend:
function top(input) {
return internalWorker(input).then(function (value) {
logger.inspectDebug("top success", value);
return value;
});
}
function internalWorker(input) {
return q.delay(100).then(function () {
throw new Error("internal worker async error");
return value;
});
}
top("hello").then(function (value) {
logger.inspectDebug("outside success", value);
}, function (error) {
logger.inspectDebug("outside fail", error);
}).done();
Return promises or values from within callbacks. Errors propagate implicitly.

Resources