How to create Custom Annotations in Dart [duplicate] - flutter

I'd like to have common try/catch/finally logic in a decorator-like feature that can "wrap" a function or class method. Consider the scenario:
Class MyClass {
void someMethodA() {
doSomeInitialWork();
try {
doSomething();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
void someMethodB() {
doSomeInitialWork();
try {
doSomethingElse();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
}
So on and so forth. The unique parts of each method are just the try body. If I have a bunch of methods, some which require the same logic, is there a "nice" way to avoid redundant code?
Ideally it could be syntax like:
#wrapper
void someMethodA() {
doSomething();
}
#wrapper
void someMethodB() {
doSomethingElse();
}
MyClassInstance.someMethodA(); // call it like this and the wrapper takes care of everything
but I know those are annotations in Dart and not applicable here.
UPDATE
Following jamesdlin answer, I am trying to incorporate the anonymous function solution to a futures/async/await scenario:
Future<dynamic> trySomething(Future<dynamic> Function() callback) async {
doSomeInitialWork();
try {
return await callback();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
class MyClass {
Future<List<String>> someMethodA() async {
return await trySomething(() async {
return await someApiCall();
});
}
}
That seems to work, but it looks kind of messy. I'm not sure if what I'm doing in the async/await example is appropriate.

Anonymous functions in Dart are rather common (unlike Python, where lambda is very restricted).
You therefore could make a helper function that takes the unique part as a callback.
void trySomething(void Function() body) {
doSomeInitialWork();
try {
body();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
void someMethodA() {
trySomething(() {
doSomething();
});
}
void someMethodB() {
trySomething(() {
doSomethingElse();
});
}
That's basically what test() from package:test (or testWidgets() from Flutter) do.
Update for the case described in the comment: It's not much different if the methods return Futures. For example, if you start with:
Future<List<String>> someMethodA() async {
return await blah();
}
then you could do:
Future<R> trySomethingAsync<R>(Future<R> Function() body) async {
doSomeInitialWork();
try {
return await body();
} catch (err) {
throw err;
} finally {
doSomeCleanUpWork();
}
}
Future<List<String>> someMethodA() {
return trySomethingAsync(() async {
return await blah();
});
}

Related

Dart: Why is an async error not caught when it is thrown in the constructor body?

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;
}
}

How can I write "Event1 'or' Event2" inside on<Event> method from flutter_bloc?

That's my code for PostsBloc:
class PostsBloc extends Bloc<PostsEvent, PostsState> {
final _dataService = DataService();
// Constructor
PostsBloc() : super(LoadingPostsState()) {
on<LoadPostsEvent>((event, emit) async {
emit(LoadingPostsState());
try {
final posts = await _dataService.getPosts();
emit(LoadedPostsState(posts: posts));
} catch (e) {
emit(FailedToLoadPostsState(error: e));
}
});
}
}
So, I want to use the same method with new event, just without emitting LoadingPostsState() like this:
PostsBloc() : super(LoadingPostsState()) {
on<LoadPostsEvent || PullToRefreshEvent>((event, emit) async {
if(event == LoadPostsEvent){
emit(LoadingPostsState());
}
try {
final posts = await _dataService.getPosts();
emit(LoadedPostsState(posts: posts));
} catch (e) {
emit(FailedToLoadPostsState(error: e));
}
});
}
What you want is the is operator:
if (event is LoadPostsEvent)
However you run into another problem:
on<LoadPostsEvent || PullToRefreshEvent>
this is not a thing. I believe you have two options:
Either make a new event X and have LoadPostsEvent and PullToRefreshEvent extend it, like this:
class LoadEvent extends PostsEvent { ... }
class LoadPostsEvent extends LoadEvent { ... }
class PullToRefreshEvent extends LoadEvent { ... }
on<LoadEvent>((event, emit) {
if (event is LoadPostsEvent)
});
or, in order to minimize code repetition, declare this event handler as a function
on<LoadPostsEvent>(_loadEvent);
on<PullToRefreshEvent>(_loadEvent);
...
void _loadEvent(PostsEvent event, Emitter<PostsState> emit) {
...
}

Flutter custom exception not throwing

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

Flutter : Avoid using `forEach` with a function literal

Hi everyone this is my whole method :
Future<void> init() async {
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.limit(3)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
snapshot.docs.forEach((document) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
});
notifyListeners();
});
} else {
_loginState = ApplicationLoginState.loggedOut;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
}
notifyListeners();
});
}
the part that dart complains about is this one :
snapshot.docs.forEach((document) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
});
how can I change this method without ruining the whole functionality ?
Im just looking for a way that makes dart happy .
I appreciate your help in advance.
AVOID using forEach with a function literal.
BAD:
snapshot.docs.forEach((document) {
...
});
GOOD:
for (var document in snapshot.docs) {
// Rest of your code
}
Using like data.forEach(function);
example:
void main() async {
List<int> list = [1, 2, 3, 4, 5];
//recommended
list.forEach(showSquareNumbers);
//not recommended
list.forEach((int number) => showSquareNumbers(number));
list.forEach((int number) {
showSquareNumbers(number);
});
}
void showSquareNumbers(int data) {
print("$data * $data = ${data * data}");
}
This is my opinion.
I think the forEach seems more complicated than the for loop and the forEach can't use continue and break (return is available but not a thing happens when using return).
void test(List data) {
data.forEach((element) {
print(element);
if(element) return;
});
for (var element in data) {
print(element);
if(element) continue;
if(element) break;
if(element) return;
}
}
I think we should use the for instead of the forEach loop when your forEach loop seems like this code below because the for loop have many options more than forEach as I said.
data.forEach((element) {
print(element)
});
//or
data.forEach((element) => print(element));
I think the forEach loop is used for short code (easy to understand) and when you want to do something you don't care about the result like this code (using with Function(dynamic)).
void test(List data) {
void showInTerminal(e) {
print("data is $e");
}
data.forEach(showInTerminal);
Function(dynamic) function = showInTerminal;
data.forEach(function);
}
Make sure the data type and function(type) are the same.
//error
void test(List<Map> data) {
void showInTerminal(String e) {
print("data is $e");
}
data.forEach(showInTerminal);
}
//success
void test(List<Map> data) {
void showInTerminal(Map e) {
print("data is $e");
}
data.forEach(showInTerminal);
}
I code like this. I think it's easy to read.
void test() {
dataList.forEach(removeData);
fileList.forEach(removeFile);
}
Use await for:
await for (final document in snapshot.docs) {
// Your code...
}

How to get the value from Future.error inside catchError?

void main() {
foo().catchError((error) {
if (error is Future) {
error.then((value) => print('value = $value'));
}
});
}
Future<void> foo() async {
throw Future.error('FooError');
}
The error is caught inside catchError but I am not able to retrieve the value of Future.error which is FooError in this example.
Well that's Future.error so you'll have to again use catchError on it.
void main() {
foo().catchError((error) {
if (error is Future) {
error.catchError((error) => print(error)); // prints 'FooError'
}
});
}
Future<void> foo() async {
throw Future.error('FooError');
}
You just don't, there is no point in doing so.
You can either throw the error:
void main() {
foo().catchError((error) {
print('error = $error');
});
}
Future<void> foo() async {
throw 'FooError';
}
Or if that is for whatever reason not convinient, you can use Future.error to create a Future that will have an error already:
void main() {
foo().catchError((error) {
print('error = $error');
});
}
Future<void> foo() {
return Future.error('FooError');
}
But actually throwing a Future.error is redundant and not useful.