플러터에서 async와 await는 왜 꼭 필요할까?
오늘은 Flutter에서 비동기 작업을 처리하는 방법에 대해 깊이 알아보았다. 비동기 작업이란, 시간이 오래 걸리는 작업을 메인 스레드를 차단하지 않고 처리하는 방식이다. 네트워크 요청이나 파일 처리처럼 시간이 많이 걸리는 작업을 동기적으로 처리하면, 앱이 멈추거나 느려질 수 있다. Flutter에서는 이러한 문제를 해결하기 위해 async와 await를 사용한다. 이번 블로그에서는 async와 await의 개념부터 그 활용 예시까지 설명해보려고 한다.
비동기 작업이란?
앱을 개발할 때, 우리는 서버에서 데이터를 받아오거나 사용자가 입력한 정보를 저장하는 등의 작업을 처리해야 한다. 이러한 작업은 시간이 걸리기 때문에, 동기적으로 처리하면 앱의 메인 스레드가 차단되어 화면이 멈추거나 "앱이 응답하지 않음" 오류가 발생할 수 있다. 이때, 비동기 작업을 사용하면 이러한 문제를 해결할 수 있다.
비동기 작업은 말 그대로, 작업이 완료되기를 기다리는 동안 다른 작업이 계속해서 실행될 수 있는 방식을 말한다. 이를 통해 앱의 반응성을 유지할 수 있다. Flutter에서는 이러한 비동기 작업을 async와 await 키워드를 통해 손쉽게 처리할 수 있다.
async와 await의 개념
async와 await는 비동기 작업을 처리할 때 필수적으로 사용된다.
- async: 이 키워드는 함수가 비동기적으로 동작한다는 것을 선언한다. async가 붙은 함수는 자동으로 Future 객체를 반환한다.
- await: async로 선언된 함수 내부에서 사용되며, 비동기 작업이 완료될 때까지 기다린다는 의미다. 이 키워드를 사용하면 마치 동기적인 코드처럼 비동기 작업의 결과를 처리할 수 있다.
코드 예시를 통해 살펴보자:
Future fetchData() async {
print('Fetching data...');
var response = await http.get('https://api.example.com/data');
print('Data fetched: ${response.body}');
print('Done fetching data.');
}
위 코드에서 await는 네트워크 요청이 완료될 때까지 기다리고, 그 후 데이터를 처리한다. 중요한 점은 이 동안 UI 스레드는 차단되지 않으므로 앱이 여전히 원활하게 작동한다는 것이다.
왜 async와 await를 사용하는가?
Flutter에서 async와 await를 사용하는 이유는 간단하다. 앱의 반응성 유지와 코드의 가독성 때문이다. 비동기 작업을 then이나 콜백으로 처리할 수 있지만, 코드가 복잡해지고 가독성이 떨어질 수 있다. 반면 async와 await는 동기 코드처럼 깔끔하게 작성할 수 있어, 작업의 흐름을 쉽게 이해할 수 있다.
또한, 비동기 작업이 완료될 때까지 기다리는 동안 UI를 자유롭게 업데이트할 수 있어 사용자 경험이 훨씬 향상된다. 예를 들어, 데이터를 로딩하는 동안 로딩 스피너를 보여주거나, 작업이 완료된 후에는 그 결과를 즉시 화면에 반영할 수 있다.
await가 필요한 상황들
그렇다면 await를 사용하는 상황은 언제일까? 대표적인 비동기 작업들을 몇 가지 소개해본다.
1. 네트워크 요청 (API 호출): 서버와 통신하는 작업은 대표적인 비동기 작업이다. 네트워크 속도나 서버 상태에 따라 응답 시간이 달라질 수 있기 때문에, 네트워크 요청을 비동기적으로 처리해야 한다.
Future fetchData() async {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
if (response.statusCode == 200) {
print('Data fetched: ${response.body}');
} else {
print('Failed to load data');
}
}
2. 데이터베이스 작업: 로컬 데이터베이스와 상호작용하는 작업도 시간이 걸릴 수 있다. 이때도 비동기 처리가 필요하다.
Future<void> getDatabaseData() async {
final data = await database.query('SELECT * FROM users');
print('Fetched data: $data');
}
3. 파일 읽기/쓰기: 파일 시스템에서 데이터를 읽거나 쓰는 작업은 성능에 큰 영향을 줄 수 있다. 따라서 파일 입출력도 비동기적으로 처리하는 것이 좋다.
Future<void> readFile() async {
final file = File('example.txt');
final contents = await file.readAsString();
print('File Contents: $contents');
}
4. Firebase와의 연동: Firebase와 같은 클라우드 백엔드와의 통신 역시 비동기 작업이다. 데이터를 읽어오거나 저장하는 작업은 async로 처리해야 한다.
Future<void> fetchFirestoreData() async {
var snapshot = await FirebaseFirestore.instance.collection('users').get();
for (var doc in snapshot.docs) {
print(doc['name']);
}
}
5. 지연된 작업: 예를 들어, 특정 작업을 일정 시간 후에 실행하고 싶다면 Future.delayed를 사용할 수 있다.
Future<void> delayedTask() async { await Future.delayed(Duration(seconds: 2)); print('Task completed after 2 seconds'); }
오늘 살펴본 async와 await는 Flutter에서 비동기 작업을 처리할 때 없어서는 안 될 중요한 도구다. 이를 통해 앱의 반응성을 유지하면서도 복잡한 비동기 작업을 간결하게 처리할 수 있다. 특히 네트워크 요청, 데이터베이스 작업, 파일 입출력 등 시간이 오래 걸리는 작업에서 큰 도움이 된다.
Flutter 앱을 개발할 때는 항상 비동기 작업을 염두에 두고, UI가 차단되지 않도록 신경 써야 한다. async와 await를 적절하게 활용하면 성능과 사용자 경험을 모두 만족시키는 앱을 만들 수 있을 것이다.