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> 라는 상자가 있습니다. 이 상자는 지금은 닫혀있습니다. 하지만 이 상자를 준 함수가 말합니다.
"지금은 그 상자는 닫혀있지만, 나중에 열리면 interror 가 나올 거야. 두 경우 모두를 대비해 줘."
이 상자를 받은 변수는 상자로부터 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초 후에 futureint로 바뀌는 것이 아닙니다. future는 계속해서 Future<int> 입니다. 그렇기 때문에 future가 100으로 바뀌는 것이 아닙니다.
그렇다면 어떻게 그 Future<int> 에서 나오는 값을 다루느냐. then 함수로 다룰 수 있습니다. 위 코드에서 future.then(...) 을 통해 다루고 있습니다. then 내부에는 또다른 함수가 들어있으며 이 함수로 Future<int> 로부터 나오는 값을 다룰 수 있는 것입니다. then 내부 함수에서 valFuture<int> 상자가 열렸을 때 나오는 값이 들어갈 것이므로 val: 100 이라고 출력됩니다.


위 코드를 수행했을 때 다음과 같은 출력이 됩니다.

기다리는 중
val: 100

"어째서 val: 100이 아니라 기다리는 중 이 먼저 출력되는거지?" 라고 생각하실 분들이 있을 것입니다. 이 부분이 제가 위에서 Future는 비동기를 위해 있다는 이유입니다. 비동기와 동기가 무엇인지 간단히 설명드리자면 다음과 같습니다.

  • 동기란 C언어, C++과 같이 모든 동작을 차례대로 완료 후 수행하는 것
  • 비동기란 어떤 동작이 완료가 되지 않아도 다음 동작을 수행하는 것

비동기처리는 보통 디스크로부터 읽거나 쓸 때, 네트워크 통신처럼 다소 오랜 시간이 필요한 경우에 유용합니다. 해당 동작들이 완료될 때까지 기다리지 않고 다른 동작을 수행할 수 있기 때문이죠.

그러면 다시 돌아와서 Future비동기를 위해서 존재합니다. 위 코드에서 main 함수의 가장 마지막 줄에 print가 있습니다만 100줄의 코드였다고 가정해봅시다. 동기적으로 처리했을 경우 Future<int> 에서 값이 나올 때까지 100줄의 코드는 동작하지 않고 정지해있을 것입니다. 이 100줄의 코드에 Future<int>로부터 나올 값이 전혀 필요없다면 정말 심한 낭비일 것입니다.

이러한 낭비를 막고자 비동기적으로 처리하는 것입니다. Future<int> 에서 값이 나오지 않아도 계속해서 동작을 수행할 수 있도록 말이죠. 그렇기 때문에 위 코드에서 3초가 지나 futurethen이 동작하기 전에 계속해서 코드를 수행하기 때문에 가장 밑의 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 함수와 마찬가지로 내부의 함수가 수행될 것입니다. 위 코드의 경우 errorFuture<int> 상자가 열렸을 때 나올 Error! 가 들어갈 것이므로 error: Error! 가 출력 될 것입니다.


위 코드를 수행했을 때 다음과 같은 출력이 됩니다.

기다리는 중
error: Error!

async / await 란 무엇인가

async / await 또한 Dart 의 비동기 처리를 위한 것으로 Future 를 조금 더 용이하게 다루기 위한 키워드입니다. 어떤 역할을 하는지 보기 전에 중요한 원칙을 이야기 하겠습니다.

  1. await 키워드를 사용한 함수는 무조건 async 함수이어야 합니다.
  2. 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 / awaitFuture 을 조금 더 용이하게 사용하도록 도와줍니다.


async / await 는 어떻게 동작하지?

그러면 async / await 가 어떻게 2번 코드와 3번 코드가 동일한 동작을 하도록 만들어주는지 설명하겠습니다.

3번 코드

Future<ProcessedData> createDate() async {
  final id = await _loadFromDisk();
  final data = await _fetchNetworkData(id);
  return ProcessedData(data);
}
  1. createData() 함수가 실행될 때 가장먼저 첫 번째 줄의 await 키워드가 붙은 _loadFromDisk() 를 수행할 것입니다. 그러면 해당 함수를 실행하면서 '아, await 를 보니 이 함수를 수행하려면 뭔가 다른 것들이 필요하겠군.' 이라고 생각하며 일단 이 함수의 수행을 멈춥니다.
  2. 그리고 함수를 호출한 곳에 Future<ProcessedData> 를 return 합니다. 그리고는 "이 함수는 비동기적인 처리가 좀 필요해. 일단 ProcessedData 가 담길 상자 Future<ProcessedData> 를 미리 줄게. 나중에 이 함수가 모두 완료되면 ProcessedData 가 나올거야." 라고 말합니다.
  3. 해당 함수는 await 가 붙은 _loadFromDisk() 함수가 끝날 때까지 다음 동작을 수행하지 않고 가만히 기다립니다. 마치 상자에서 값이 나와야 then 함수를 수행할 수 있었던 것과 같이 말이죠.
  4. _loadFromDisk() 함수의 수행이 완료되면 그 다음 줄을 수행합니다. 이번에도 await 가 붙은 함수네요. 마찬가지로 _fetchNetworkData(id) 가 수행완료 될 때까지 함수를 더 이상 진행하지 않고 기다립니다.
  5. 2번 째 줄도 다 마치면 이제 return ProcessedData(data) 가 수행됩니다. 이 return 을 통해 createData() 를 호출한 녀석이 받은 상자에서 ProcessedData 가 나오게 됩니다.


이런 순서로 동작을 합니다. await 를 만나면 해당 동작을 완료되기 전까지 멈추어서 기다리기 때문에 then(...) 함수 처럼 동작을 하며 전체적인 코드는 훨씬 가독성이 좋습니다. 그리고 위 순서에서 2번 때문에 async 함수의 return 타입은 무조건 Future 이어야 합니다. await 를 사용하였을 테니 async 함수일 테고, await 를 사용하였기 때문에, 함수의 return 까지 가기 이전에 미리 Future 를 반환합니다. 따라서 async 함수는 무조건 Future 를 반환해야 하는 것입니다.

정리하자면 아래와 같습니다.

  1. await 를 만나면 함수를 잠시 멈추고 함수를 호출한 곳에 Future 를 return 합니다.
  2. await 가 붙은 동작이 완료되기 전까지 함수를 더 이상 진행하지 않습니다.
  3. return 을 통해 1번에서 주었던 Future 에서 return 값이 나오게 합니다.

그리고 다시 한 번 강조하지만 1번에서 받은 return 값인 Future 가 3번에서 return 할 값으로 바뀌는 것이 아닙니다! 이 점 꼭 기억해주세요.


언제 await 를 쓰는거지?

위에서 asyncawait 키워드를 사용한 함수라면 무조건 붙여야 한다고 했습니다. 그렇다면 언제 await 를 써야 할까요? 저는 다음과 같은 고민을 했습니다.

  1. async 함수를 호출할 때 await 를 붙이는 건가?
  2. 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 WorldhelloWorld() 함수에서 출력된 것입니다. 어째서 future 의 출력순서가 첫 번째인지, Instance of 'Future<String>' 으로 출력 되는지 설명하겠습니다.

  1. 출력 순서가 첫 번째인 이유는 await 키워드를 사용하지 않았으며 then() 함수를 사용하지 않았으므로 비동기적으로 처리되기 때문에 3초가 되기 전에 print(future) 가 수행됩니다.
  2. 값이 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 / awaitthen 을 사용하지 않아도 되지만, 완전히 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() 함수의 마지막 printawait 때문에 future1future2 와도 관계없는데도 불구하고, 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() 함수를 실행할 뿐이죠. 중간에 멈추지 않기 때문에 future1future2 와 관계없는 print 문은 world() 함수가 실행됨과 거의 동시에 출력합니다. 또, future1future2 상자 내 값의 출력까지 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번 코드로 thenasync / 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 가 헷갈리는 분들에게 도움이 되길 바랍니다.


그리고 글 중간에 오타나 틀린 내용이 있다면 지적해주세요. 글 읽어주셔서 감사합니다. :)

좋은 웹페이지 즐겨찾기