Here's the simplifed code to show what I'm trying to achieve:
class MyClass {
MyClass() {
authorize();
}
String? token;
void authorize() async {
final response =
await Future<String>.delayed(Duration(seconds: 1), (() => "14dw65d1q"));
token = response;
print('Token: $token');
}
String printToken() {
return 'Token: $token';
}
}
void main() {
final myClass = MyClass();
print(myClass.printToken());
}
Basically I'm trying to use token
(whose default value of null
is changed by authorize()
) in another function. The output is:
Token: null
Token: 14dw65d1q
Apparently, main()
runs before the value of token gets assigned. Thus, I get a 401 error when I try to use token
in the header of a network request. (Not shown in code) How can I make printToken()
run after the value of token
gets assigned? Making main()
async and/or awaiting myClass.printToken()
doesn't work. I get 'await' applied to 'String', which is not a 'Future'.
when I await 'Token: $token'
or myClass.printToken()
anyway.
CodePudding user response:
Your MyClass
implementation does not provide any Future
s to its consumers, and consumers therefore will not be able to be notified when asynchronous operations in MyClass
complete. There are two specific problems:
You've made the
authorize
method "fire-and-forget" by returningvoid
. That instead should return aFuture<void>
to allow callers to be notified when it completes.You call
authorize
from within theMyClass
constructor. However, invoking a constructor must always result in an instance of the class, not aFuture
.
Possible fixes for problem #2:
Construct
MyClass
with astatic
method that returns aFuture
.class MyClass { MyClass._(); static Future<MyClass> createInstance() async { var instance = MyClass._(); await instance.authorize(); return instance; } String? token; Future<void> authorize() async { final response = await Future<String>.delayed(Duration(seconds: 1), (() => "14dw65d1q")); token = response; print('Token: $token'); } String printToken() { return 'Token: $token'; } } void main() async { final myClass = await MyClass.createInstance(); print(myClass.printToken()); }
Use a
Completer
.import 'dart:async'; class MyClass { MyClass() { authorize(); } String? token; final _completer = Completer<void>(); Future<void> get authorizationDone => _completer.future; Future<void> authorize() async { final response = await Future<String>.delayed(Duration(seconds: 1), (() => "14dw65d1q")); token = response; _completer.complete(); print('Token: $token'); } String printToken() { return 'Token: $token'; } } void main() async { final myClass = MyClass(); await myClass.authorizationDone; print(myClass.printToken()); }
Move asynchronous operations out of the constructor.
class MyClass { MyClass(); String? token; Future<void> authorize() async { final response = await Future<String>.delayed(Duration(seconds: 1), (() => "14dw65d1q")); token = response; print('Token: $token'); } String printToken() { return 'Token: $token'; } } void main() async { final myClass = MyClass(); await myClass.authorize(); print(myClass.printToken()); }
I recommend using the first approach since it's less error-prone for callers and since you usually shouldn't be using Completer
directly.