Flutter/Dart 에서의 Future, async/await
개인적으로 자바스크립트를 공부하면서 가장 헤매었던 개념이 Promise, async/await 였습니다. 그래서 확실히 이해하지 않고 넘어갔는데 요즘 Flutter를 공부하면서 Future, async/await 에서 다시 발목이 잡혔습니다... 그래서 이 기회에 저도 개념정리할 겸 글을 씁니다.
Future란 무엇인가
Dart에서의 Future는 자바스크립트에서의 Promise에 대응됩니다. Future와 Promise는 모두 싱글스레드 환경에서 비동기 처리를 위해 존재합니다.
- JS의 Promise는 특정 동작의 수행이 완료되면 다음 동작을 수행하겠다는 약속
- Dart의 Future는 지금은 없지만 미래에 요청한 데이터 혹은 에러가 담길 그릇
Promise가 무엇인지 잘 알고 있다면, 이 설명만으로 이해하실 수 있을 것입니다. 하지만 Promise가 무엇인지 잘 모르는 분들을 위해 Future에 대해 조금 더 설명하겠습니다.
Future<int>
라는 상자가 있습니다. 이 상자는 지금은 닫혀있습니다. 하지만 이 상자를 준 함수가 말합니다.
"지금은 그 상자는 닫혀있지만, 나중에 열리면int
나error
가 나올 거야. 두 경우 모두를 대비해 줘."
이 상자를 받은 변수는 상자로부터int
가 나올 때를 대비해then
메소드를,error
가 나올 경우를 대비해catchError
메소드를 준비해야 합니다.
계속될 설명에서 이 상자 비유를 자주 쓸 예정입니다.
이제 코드와 함께 보겠습니다.
1-1 번 코드
import 'dart:async';
Future<int> futureNumber() {
// 3초 후 100이 상자에서 나옵니다
return Future<int>.delayed(Duration(seconds: 3), () {
return 100;
});
}
void main() {
// future 라는 변수에서 미래에(3초 후에) int가 나올 것입니다
Future<int> future = futureNumber();
future.then((val) {
// int가 나오면 해당 값을 출력
print('val: $val');
}).catchError((error) {
// error가 해당 에러를 출력
print('error: $error');
});
print('기다리는 중');
}
futureNumber
함수는 3초가 되기 전까지는 닫혀있다가 3초가 되면 100이 나오는 상자 Future<int>
를 return 합니다. main
함수에서 future
변수에 해당 함수의 return값을 저장합니다. 여기서 주의해야 합니다. 3초 후에 future
는 int
로 바뀌는 것이 아닙니다. future
는 계속해서 Future<int>
입니다. 그렇기 때문에 future
가 100으로 바뀌는 것이 아닙니다.
그렇다면 어떻게 그 Future<int>
에서 나오는 값을 다루느냐. then
함수로 다룰 수 있습니다. 위 코드에서 future.then(...)
을 통해 다루고 있습니다. then
내부에는 또다른 함수가 들어있으며 이 함수로 Future<int>
로부터 나오는 값을 다룰 수 있는 것입니다. then
내부 함수에서 val
에 Future<int>
상자가 열렸을 때 나오는 값이 들어갈 것이므로 val: 100
이라고 출력됩니다.
위 코드를 수행했을 때 다음과 같은 출력이 됩니다.
기다리는 중
val: 100
"어째서 val: 100
이 아니라 기다리는 중
이 먼저 출력되는거지?" 라고 생각하실 분들이 있을 것입니다. 이 부분이 제가 위에서 Future는 비동기를 위해 있다는 이유입니다. 비동기와 동기가 무엇인지 간단히 설명드리자면 다음과 같습니다.
- 동기란 C언어, C++과 같이 모든 동작을 차례대로 완료 후 수행하는 것
- 비동기란 어떤 동작이 완료가 되지 않아도 다음 동작을 수행하는 것
비동기처리는 보통 디스크로부터 읽거나 쓸 때, 네트워크 통신처럼 다소 오랜 시간이 필요한 경우에 유용합니다. 해당 동작들이 완료될 때까지 기다리지 않고 다른 동작을 수행할 수 있기 때문이죠.
그러면 다시 돌아와서 Future
는 비동기를 위해서 존재합니다. 위 코드에서 main
함수의 가장 마지막 줄에 print
가 있습니다만 100줄의 코드였다고 가정해봅시다. 동기적으로 처리했을 경우 Future<int>
에서 값이 나올 때까지 100줄의 코드는 동작하지 않고 정지해있을 것입니다. 이 100줄의 코드에 Future<int>
로부터 나올 값이 전혀 필요없다면 정말 심한 낭비일 것입니다.
이러한 낭비를 막고자 비동기적으로 처리하는 것입니다. Future<int>
에서 값이 나오지 않아도 계속해서 동작을 수행할 수 있도록 말이죠. 그렇기 때문에 위 코드에서 3초가 지나 future
의 then
이 동작하기 전에 계속해서 코드를 수행하기 때문에 가장 밑의 print
가 수행됩니다.
이번에는 error
가 나오는 Future
을 보겠습니다.
1-2 번 코드
import 'dart:async';
Future<int> futureNumber() {
// 3초 후 Error!가 상자에서 나옵니다
return Future<int>.delayed(Duration(seconds: 3), () {
throw 'Error!';
});
}
void main() {
// future 라는 변수에서 미래에(3초 후에) error가 나올 것입니다
Future<int> future = futureNumber();
future.then((val) {
// int가 나오면 해당 값을 출력
print('val: $val');
}).catchError((error) {
// error가 해당 에러를 출력
print('error: $error');
});
print('기다리는 중');
}
이번에는 futureNumber
함수에서 error
가 3초 후에 나올 상자 Future<int>
를 전해주었습니다. 이번에는 catchError
함수가 수행될 차례입니다. then
함수와 마찬가지로 내부의 함수가 수행될 것입니다. 위 코드의 경우 error
에 Future<int>
상자가 열렸을 때 나올 Error!
가 들어갈 것이므로 error: Error!
가 출력 될 것입니다.
위 코드를 수행했을 때 다음과 같은 출력이 됩니다.
기다리는 중
error: Error!
async
/ await
란 무엇인가
async
/ await
또한 Dart 의 비동기 처리를 위한 것으로 Future
를 조금 더 용이하게 다루기 위한 키워드입니다. 어떤 역할을 하는지 보기 전에 중요한 원칙을 이야기 하겠습니다.
await
키워드를 사용한 함수는 무조건async
함수이어야 합니다.async
함수는 무조건Future
를 반환해야 합니다.
1번 규칙은 별로 어렵지 않은 규칙입니다. await
라는 키워드를 함수 내부에서 사용한다면 해당 함수 스코프를 알려주는 { ... }
의 앞에 async
를 적어주기만 하면 됩니다.
Future functionName() async {
...
await someFunction();
...
}
2번 규칙은 좀 난해할 수 있습니다. 제가 매우 헤맸던 개념이거든요.
"아니! 내가 원하는건
Future<String>
을 return 하는 함수가 아니라String
을 return 하는 함수를 원한단 말야! 어떻게 하면Future<String>
이 아니라String
을 return 시킬 수 있지?"
라고, 매우 헤맸습니다. 이 원칙의 설명과 위의 질문에 해당하는 답은 뒤에서 설명하겠습니다.
이제 본격적으로 async
/ await
를 알아봅시다.
async
/ await
가 왜 필요하지?
설명드리기 전에 어째서 필요한지부터 보여드리도록 하겠습니다.
2번 코드
Future<ProcessedData> createData() {
return _loadFromDisk().then((id){
return _fetchNetworkData(id);
}).then((data){
return ProcessedData(data);
})
}
위와 같은 함수가 있습니다.(catchError
는 생략하겠습니다.) 이 함수는 미래에 ProcessedData
가 나올 상자인 Future<ProcessedData>
를 return 합니다. 함수의 과정을 보죠. 가장 먼저 디스크로부터 id
값을 읽어들이는 _loadFromDsik()
함수가 수행되고, id
를 읽어들이면 _fetchNetworkData()
함수에 id
를 인자로 넣어서 네트워크를 이용해 data
를 받아옵니다. 그리고 data
를 다 받아오면 ProcessedData(data)
를 return 합니다.
그런데 조금 복잡하지 않나요? 복잡하지 않다고 생각하신 분들도 아래 코드를 보면 위와 같은 코드는 보기 싫으실 겁니다.
3번 코드
Future<ProcessedData> createDate() async {
final id = await _loadFromDisk();
final data = await _fetchNetworkData(id);
return ProcessedData(data);
}
훨씬 직관적이지 않나요? then
을 이용해 작성한 코드와 동일한 일을 하는 코드입니다만, 훨씬 간결합니다. 그리고 마치 동기적으로 일어나는 코드인 것 같습니다. 이러한 간결함 때문에 async
/ await
는 Future
을 조금 더 용이하게 사용하도록 도와줍니다.
async
/ await
는 어떻게 동작하지?
그러면 async
/ await
가 어떻게 2번 코드와 3번 코드가 동일한 동작을 하도록 만들어주는지 설명하겠습니다.
3번 코드
Future<ProcessedData> createDate() async {
final id = await _loadFromDisk();
final data = await _fetchNetworkData(id);
return ProcessedData(data);
}
createData()
함수가 실행될 때 가장먼저 첫 번째 줄의await
키워드가 붙은_loadFromDisk()
를 수행할 것입니다. 그러면 해당 함수를 실행하면서 '아,await
를 보니 이 함수를 수행하려면 뭔가 다른 것들이 필요하겠군.' 이라고 생각하며 일단 이 함수의 수행을 멈춥니다.- 그리고 함수를 호출한 곳에
Future<ProcessedData>
를 return 합니다. 그리고는 "이 함수는 비동기적인 처리가 좀 필요해. 일단ProcessedData
가 담길 상자Future<ProcessedData>
를 미리 줄게. 나중에 이 함수가 모두 완료되면ProcessedData
가 나올거야." 라고 말합니다. - 해당 함수는
await
가 붙은_loadFromDisk()
함수가 끝날 때까지 다음 동작을 수행하지 않고 가만히 기다립니다. 마치 상자에서 값이 나와야then
함수를 수행할 수 있었던 것과 같이 말이죠. _loadFromDisk()
함수의 수행이 완료되면 그 다음 줄을 수행합니다. 이번에도await
가 붙은 함수네요. 마찬가지로_fetchNetworkData(id)
가 수행완료 될 때까지 함수를 더 이상 진행하지 않고 기다립니다.- 2번 째 줄도 다 마치면 이제
return ProcessedData(data)
가 수행됩니다. 이 return 을 통해createData()
를 호출한 녀석이 받은 상자에서ProcessedData
가 나오게 됩니다.
이런 순서로 동작을 합니다. await
를 만나면 해당 동작을 완료되기 전까지 멈추어서 기다리기 때문에 then(...)
함수 처럼 동작을 하며 전체적인 코드는 훨씬 가독성이 좋습니다. 그리고 위 순서에서 2번 때문에 async
함수의 return 타입은 무조건 Future
이어야 합니다. await
를 사용하였을 테니 async
함수일 테고, await
를 사용하였기 때문에, 함수의 return 까지 가기 이전에 미리 Future
를 반환합니다. 따라서 async
함수는 무조건 Future
를 반환해야 하는 것입니다.
정리하자면 아래와 같습니다.
await
를 만나면 함수를 잠시 멈추고 함수를 호출한 곳에Future
를 return 합니다.await
가 붙은 동작이 완료되기 전까지 함수를 더 이상 진행하지 않습니다.- return 을 통해 1번에서 주었던
Future
에서 return 값이 나오게 합니다.
그리고 다시 한 번 강조하지만 1번에서 받은 return 값인 Future
가 3번에서 return 할 값으로 바뀌는 것이 아닙니다! 이 점 꼭 기억해주세요.
언제 await
를 쓰는거지?
위에서 async
는 await
키워드를 사용한 함수라면 무조건 붙여야 한다고 했습니다. 그렇다면 언제 await
를 써야 할까요? 저는 다음과 같은 고민을 했습니다.
async
함수를 호출할 때await
를 붙이는 건가?Future
가 return 되는 함수를 호출할 때await
를 붙이는 건가?
이 고민을 왜 하였냐하면 다음과 같은 경우를 생각했습니다.
Future A() async {
return await anyFutureFunction();
}
Future B() async {
return await A();
}
Future C() async {
return await B();
}
...
Future Z() async {
return await Y();
}
"async
함수를 호출할 때 await
키워드가 필요하면, 고작 A()
함수 하나 때문에 저 많은 함수들이 줄줄이 async
함수여야 한다고?" 이런 생각을 했습니다. (어째서 await
키워드 때문에 async
함수여야 하는지 모르겠는 분들은 위의 async
/ await
원칙을 다시 읽어주세요.) 결론부터 이야기 하자면 1번 고민의 답도 "No", 2번 고민의 답도 "No" 입니다.
4-1 번 코드
Future<String> helloWorld() {
// 3초 후에 Future<String> 에서 "Hello World" 가 나옵니다
return Future.delayed(Duration(seconds: 3), () {
final hello = "Hello World";
print(hello);
return hello;
});
}
void main() {
final future = helloWorld();
print(future);
}
이 코드의 특징은 Future
을 return 하는 함수를 호출하지만 await
키워드를 사용하지 않은 것입니다. main()
함수에서 Future<String>
을 받아 future
에 저장하고 출력합니다. 이 코드의 실행결과는 다음과 같습니다.
Instance of 'Future<String>'
Hello World
첫 번째 줄인 Instance of 'Future<String>'
은 main()
함수의 print(future)
에서, 두 번째 줄인 Hello World
는 helloWorld()
함수에서 출력된 것입니다. 어째서 future
의 출력순서가 첫 번째인지, Instance of 'Future<String>'
으로 출력 되는지 설명하겠습니다.
- 출력 순서가 첫 번째인 이유는
await
키워드를 사용하지 않았으며then()
함수를 사용하지 않았으므로 비동기적으로 처리되기 때문에 3초가 되기 전에print(future)
가 수행됩니다. - 값이
Instance of 'Future<String>'
인 이유는 제가 계속 강조했다시피Future
로 반환된 값은 상자가 열렸을 때 나오는 값으로 바뀌는 것이 아니기 때문입니다.helloWorld()
함수의 return 타입은Future<String>
이므로future
의 타입은String
으로 바뀌는 것이 아닌 계속Future<String>
입니다.
4-2 번 코드
Future<String> helloWorld() {
return Future.delayed(Duration(seconds: 3), () {
final hello = "Hello World";
print(hello);
return hello;
});
}
void main() async {
final future = await helloWorld();
print(future);
}
이 코드의 특징은 Future
을 return 하는 함수를 호출할 때, await
키워드를 사용한 것입니다. main()
함수에서 await
키워드를 사용했기 때문에 async
함수로도 만들어 주었습니다. 그 외에는 동일합니다. 이 코드의 실행 결과는 다음과 같습니다.
Hello World
Hello World
"어? helloWorld()
함수는 Future
를 return 해주는데 왜 Hello World
가 2번 출력되지?" 하시는 분이 계실 겁니다. (제가 그랬습니다...) 그 이유는 위에서 말씀드렸다시피 await
키워드를 만나면 해당 함수를 잠시 정지하고, await
키워드가 붙은 동작이 완료될 때까지 기다립니다. 그리고 결과를, 상자안의 내용물을 바로 넘겨줍니다. 이 방법을 통해 Future<String>
으로 String
을 얻을 수 있습니다.
5-1 번 코드
Future<String> helloWorld() async {
return await Future.delayed(Duration(seconds: 3), () {
final hello = "Hello World";
print(hello);
return hello;
});
}
void main() {
final future = helloWorld();
print(future);
}
이 코드의 특징은 async
함수를 호출할 때, await
키워드를 사용하지 않은 것입니다. 이 코드의 실행 결과는 다음과 같습니다.
Instance of 'Future<String>'
Hello World
결과가 이와 같은 이유는 4-1 번 코드의 설명과 동일하기 때문에 생략하겠습니다.
5-2 번 코드
Future<String> helloWorld() async {
return await Future.delayed(Duration(seconds: 3), () {
final hello = "Hello World";
print(hello);
return hello;
});
}
void main() async {
final future = await helloWorld();
print(future);
}
이 코드의 특징은 async
함수를 호출할 때, await
키워드를 사용한 것입니다. 이 코드의 실행 결과는 다음과 같습니다.
Hello World
Hello World
결과가 이와 같은 이유는 4-2 번 코드의 설명과 동일하기 때문에 생략하겠습니다.
async
/ await
를 사용하면 더 이상 then
은 필요없나?
대부분의 경우 async
/ await
로 then
을 사용하지 않아도 되지만, 완전히 then
을 대체할 수는 없다고 생각합니다. 아래와 같은 이유 때문이죠.
await
키워드는 수행 중인 함수를 중간에 동작이 완료될 때까지 멈춥니다. 따라서 해당 동작 이후 코드는 동작 완료 전까지 실행되지 않습니다.then()
함수는 수행 중인 함수를 중간에 멈추도록 하지 않습니다. 따라서 해당 동작 이후 코드는 계속 실행됩니다.
구체적인 상황을 보겠습니다. 보실 때 어째서 world()
함수의 return 타입이 6-1번 코드에서는 Future<List<String>>
이고, 6-2번 코드에서는 List<Future<String>>
인지 고민해보세요. 이유를 아실 수 있다면 async
/ await
의 동작을 잘 이해하신 겁니다.
6-1 번 코드
Future<String> helloWorld() {
return Future.delayed(Duration(seconds: 3), () {
return "Hello World";
});
}
Future<String> byeByeWorld() {
return Future.delayed(Duration(seconds: 3), () {
return "Bye Bye World";
});
}
Future<List<String>> world() async {
final future1 = await helloWorld(); // await 로 3초간 정지하지만, future1 에는 Future<String> 이 아닌 String 이 저장
final future2 = await byeByeWorld(); // await 로 3초간 정지하지만, future2 에는 Future<String> 이 아닌 String 이 저장
print('(world) future1: $future1');
print('(world) future2: $future2');
print('(world) future1과 future2와 관계 없는 print문, 빨리 수행될 수록 좋음');
return [future1, future2]; // 두 번의 await 로 world() 함수 종료까지 6초 소요
}
void main() {
final futures = world(); // world() 함수의 return 타입은 Future<List<String>> 이므로
print('(main) future: $futures'); // futures 의 값은 Instance of 'Future<List<String>>'
}
await
키워드를 보면 함수는 그 동작이 완료될 때까지 기다립니다. world()
함수는 await
가 필요한 동작 2개가 있으니 (각 3초씩) 총 6초가 소요되어 return [future1, future2]
에 도달할 것입니다. 이 말은 즉, world()
내 3개의 print
도 6초 후에 일어나며, main()
의 futures
변수는 6초 이후에 Future<List<String>>
상자를 열 수 있다는 뜻입니다.
코드 실행 결과는 다음과 같습니다. (이해하기 쉽도록 실제 출력되지 않지만 출력문이 나올 때까지 걸린 시간도 적겠습니다.)
(main) future: Instance of 'Future<List<String>>' //0초
(world) future1: Hello World // 6초
(world) future2: Bye Bye World // 6초
(world) future1과 future2와 관계 없는 print문, 빨리 수행될 수록 좋음 // 6초
world()
함수의 마지막 print
는 await
때문에 future1
과 future2
와도 관계없는데도 불구하고, 6초 후에, 그것도 가장 마지막에 실행되었습니다.
6-2 번 코드
Future<String> helloWorld() {
return Future.delayed(Duration(seconds: 3), () {
return "Hello World";
});
}
Future<String> byeByeWorld() {
return Future.delayed(Duration(seconds: 3), () {
return "Bye Bye World";
});
}
List<Future<String>> world() {
final future1 = helloWorld();
future1.then((val) {
print('(world) future1: $val'); // world() 함수가 실행되고 3초 후 출력
});
final future2 = helloWorld();
future2.then((val) {
print('(world) future2: $val'); // world() 함수가 실행되고 3초 후 출력
});
print('(world) future1과 future2와 관계 없는 print문, 빨리 수행될 수록 좋음');
return [future1, future2]; // world() 함수가 실행되고 거의 곧바로 return 됨
}
void main() {
final futures = world(); // futures의 타입은 world() 함수의 return 타입인 List<Future<String>>
print('(main) future: $futures');
}
6-1번 코드와 비슷하게 동작합니다만 코드 실행 결과는 다소 다릅니다.
(world) future1과 future2와 관계 없는 print문, 빨리 수행될 수록 좋음 // 0초
(main) future: [Instance of 'Future<String>', Instance of 'Future<String>'] // 0초
(world) future1: Hello World // 3초
(world) future2: Bye Bye World // 3초
world()
함수에 이제 await
가 없으니 코드가 중간에 멈추지 않습니다. 다만 나중에 future1
의 상자와 future2
의 상자가 열렸을 때, then()
함수를 실행할 뿐이죠. 중간에 멈추지 않기 때문에 future1
과 future2
와 관계없는 print
문은 world()
함수가 실행됨과 거의 동시에 출력합니다. 또, future1
과 future2
상자 내 값의 출력까지 6초나 걸린 6-1번 코드와는 다르게 6-2번 코드는 3초밖에 안걸립니다.
혹시 6-1번 코드와 6-2번 코드의
world()
함수의 return 타입이 각각Future<List<String>>
이고,List<Future<String>>
인지 잘 모르겠는 분들이 있을 수 설명을 적습니다.
- 6-1번 코드의 경우,
await
를 사용하기 때문에world()
함수는 호출한 녀석에게 바로Future
상자를 return 해줍니다. 다만 상자가 열렸을 때 나올 타입이List<String>>
이기 때문에,world()
함수의 return 타입은 최종적으로Future<List<String>>
입니다.- 6-2번 코드의 경우,
await
를 사용하지 않았기 때문에 함수를 중간에 멈추지 않고 거의 곧바로 return 에 도달하여List
를 return 합니다. 다만, 그List
안에 담겨져 있는 원소들이Future<String>
이죠. 따라서world()
함수의 return 타입은 최종적으로List<Future<String>>
입니다.
이 설명을 보아도 잘 모르겠는 분들은 위쪽의async
/await
란 무엇인가를 다시 읽어주세요.
2번 코드와 3번 코드로 then
을 async
/ await
로 대체할 수 있지만, 6-1번 코드와 6-2번 코드로 then
을 완전히 대체하지 못한다는 것을 보았습니다. 하지만 완전히 대체하지는 못 하는 것뿐이지 거의 대부분의 경우 대체할 수 있을 것이라고 생각합니다.
await
로 코드가 지연되지 않으면서, Future<String>
이 아닌 String
을 얻을 수 없나?
이 부분도 정말 많이 괴롭힌 질문이었습니다. 제가 내린 답은 "얻을 수 없다"입니다. 정확히 말하자면 "굳이 String
을 얻으려고 할 필요가 없다"입니다.
저도 그렇고 이 글을 읽으시는 분들도 그렇고, 아마 대부분은 순수히 Dart 를 위해 공부하는 것이 아닌 Flutter 를 위해 Dart 를 공부하시는 분들이실 겁니다. Flutter 에는 Future
를 이용해 위젯을 렌더링할 수 있는 FutureBuilder
라는 위젯이 있습니다. 이 위젯을 이용하면 Future<String>
으로도 위젯을 렌더링 할 수 있습니다.
class _MyHomePageState extends State<MyHomePage> {
Future<String> future;
Future<String> helloWorld() {
return Future.delayed(Duration(seconds: 15), () {
return 'Hello World';
});
}
void initState() {
super.initState();
future = helloWorld();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: FutureBuilder<String>(
future: future,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text('스냅샷 데이터: ${snapshot.data.toString()}');
} else if (snapshot.hasData == false) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('스냅샷 에러');
} else {
return Text('혹시 몰라서 else문 추가');
}
},
),
),
);
}
}
Flutter 프로젝트 생성시 자동으로 작성되는 코드에서 _MyHomePageState
만 수정하였습니다.
맨 처음 helloWorld()
함수에서 15초 후에 'Hello World'
가 나오는 Future<String>
을 return 합니다. 이를 initState()
함수에서 future
변수에 저장합니다. 그리고 build()
함수 내에서 FutureBuilder()
를 이용하여 Future<String>
만으로 String
을 이용하여 렌더링한 것처럼 만듭니다.
Future<String>
상자에서String
이 나왔을 때 =>if (snapshot.hasData) { ... }
Future<String>
상자에서 아무것도 안나왔을 때 =>if (snapshot.hasData == false) { ... }
Future<String>
상자에서error
가 나왔을 대 =>if (snapshot.hasError) { ... }
각 경우에 원하는 위젯을 { ... }
안에서 return 해주면 됩니다. 위 코드를 빌드하면 다음과 같습니다.
글을 마치며
처음에 정리할겸 가벼운 마음으로 썼는데 생각보다 오래걸리고 힘드네요... 그래도 글을 쓰면서 제 나름대로 정리도 잘 되었고, 재미도 있었습니다. 이 글이 Flutter 를 공부하면서 Future, async / await 가 헷갈리는 분들에게 도움이 되길 바랍니다.
그리고 글 중간에 오타나 틀린 내용이 있다면 지적해주세요. 글 읽어주셔서 감사합니다. :)
Author And Source
이 문제에 관하여(Flutter/Dart 에서의 Future, async/await), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jintak0401/FlutterDart-에서의-Future-asyncawait저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)