플러터에서 비동기 작업을 처리할 때는 두 가지 주요 도구, Future와 void를 활용하게 됩니다.
각각 어떤 역할을 하고 언제 사용해야 하는지 헷갈리는 경우가 많죠.
오늘은 플러터에서 Future와 void를 사용하는 경우와 이유를 명확하게 알아보고, 실제 코드 예시와 함께 이해를 돕겠습니다.
1. Future: 값을 반환하는 비동기 작업 처리
Future는 비동기 작업의 결과 값을 보유하고 제공하는 역할을 합니다.
마치 우편함처럼, 작업이 완료되면 결과 값을 담아 기다리고 있다가, await 키워드를 통해 꺼내 사용할 수 있도록 해줍니다.
Future를 사용하는 경우
- 비동기 작업의 결과 값을 사용해야 할 때: 네트워크 통신으로 데이터를 불러오거나, 로컬 파일을 읽어오는 경우처럼 작업 결과를 활용해야 하는 상황에서 Future를 사용합니다.
- 비동기 작업 진행 상황을 추적해야 할 때: Future 객체는 .then() 또는 .catchError() 메서드를 통해 작업 진행 상황이나 오류 상황을 처리할 수 있도록 합니다. 예를 들어, 로딩 UI를 표시하거나, 오류 메시지를 출력하는 등의 작업이 가능합니다.
- 비동기 작업 완료 후 후속 작업을 수행해야 할 때: await 키워드를 통해 Future 객체의 결과 값을 받은 후, 그 값을 기반으로 다음 단계의 작업을 수행할 수 있습니다.
- 예를 들어, 네크워크에서 데이터를 가져오거나, 저장소에서 파일을 로드하거나, 시간이 걸리는 복잡한 계산을 수앵하는 메서드등이 있습니다.
예시
Future<String> fetchData() async {
// 비동기 작업을 수행하여 문자열 결과를 반환합니다.
final response = await http.get(Uri.parse('https://www.example.com'));
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('Failed to fetch data');
}
}
void main() async {
// fetchData() 함수를 호출하고 결과 값을 변수에 저장합니다.
final data = await fetchData();
// data 변수를 사용하여 후속 작업을 수행합니다.
print(data);
// ...
}
2. void: 값을 반환하지 않고 단순히 작업만 수행
void는 반환 값이 없는 타입으로, 비동기 작업 자체만 수행하고 결과 값은 제공하지 않습니다. 마치 작업을 완료하는 데 집중하는 도구라고 생각하면 됩니다.
void를 사용하는 경우
- 비동기 작업의 결과 값이 중요하지 않은 경우: 데이터 저장, 파일 삭제, 로컬 설정 변경 등 작업 자체만 수행하고 결과 값을 사용하지 않는 경우 void를 사용합니다.
- 간단하고 가벼운 비동기 작업을 수행할 때: 로딩 애니메이션 표시, UI 업데이트 등 간단한 작업을 수행할 때 void를 사용하면 코드를 간결하게 만들 수 있습니다.
- 후속 작업이 필요하지 않은 경우: 비동기 작업이 완료된 후 별도의 후속 작업을 수행할 필요가 없는 경우 void를 사용합니다.
- 예를들어, 사용자가 상호 작용을 처리하거나, 환경설정을 설정하거나, 특정결과를 반화하지 않고 시간이 걸리는 복잡한 계산을 수행하는 메서드 등이 있습니다.
예시
void saveData() async {
// 비동기 작업을 수행하고 결과 값을 반환하지 않습니다.
final data = {'name': 'Flutter', 'age': 2};
await SharedPreferences.getInstance().then((prefs) {
prefs.setString('data', jsonEncode(data));
});
}
void main() {
// saveData() 함수를 호출하여 비동기 작업을 수행합니다.
saveData();
print('Data saved');
}
Future vs void, 어떤 것을 선택해야 할까요?
1. 상황에 맞는 선택 가이드
비동기 작업의 결과 값을 사용해야 하는가? | O | X | 네트워크 통신, 파일 입출력, 데이터베이스 작업 등 결과 값을 활용해야 하는 경우 Future를 사용합니다. |
비동기 작업 진행 상황을 추적해야 하는가? | O | X | 로딩 UI 표시, 진행률 업데이트 등 작업 진행 상황을 모니터링해야 하는 경우 Future를 사용합니다. |
비동기 작업 완료 후 후속 작업을 수행해야 하는가? | O | X | 작업 결과를 기반으로 다음 단계의 작업을 진행해야 하는 경우 Future를 사용합니다. |
비동기 작업의 결과 값이 중요하지 않은가? | X | O | 데이터 저장, 파일 삭제, 설정 변경 등 작업 자체만 수행하고 결과 값을 사용하지 않는 경우 void를 사용합니다. |
간단하고 가벼운 비동기 작업을 수행하는가? | X | O | 로딩 애니메이션 표시, UI 업데이트 등 간단한 작업을 수행하는 경우 void를 사용하면 코드를 간결하게 만들 수 있습니다. |
후속 작업이 필요하지 않은가? | X | O | 비동기 작업이 완료된 후 별도의 후속 작업을 수행할 필요가 없는 경우 void를 사용합니다. |
2. 실제 코드 예시
Future 사용 예시
Future<String> fetchData() async {
final response = await http.get(Uri.parse('https://www.example.com'));
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('Failed to fetch data');
}
}
void main() async {
final data = await fetchData();
print(data);
}
void 사용 예시
void saveData() async {
final data = {'name': 'Flutter', 'age': 2};
await SharedPreferences.getInstance().then((prefs) {
prefs.setString('data', jsonEncode(data));
});
}
void main() {
saveData();
print('Data saved');
}
3. Future: 다양한 활용법
3.1 여러 Future 객체 동시 처리
Future.wait() 함수를 사용하면 여러 개의 Future 객체를 동시에 처리하고, 모든 작업이 완료된 후 결과 값들을 배열 형태로 받아 처리할 수 있습니다.
Future<List<String>> fetchData() async {
// 두 개의 비동기 작업을 동시에 수행합니다.
final data1 = await http.get(Uri.parse('https://www.example.com/data1'));
final data2 = await http.get(Uri.parse('https://www.example.com/data2'));
// 두 작업의 결과 값을 배열에 저장합니다.
final results = [data1.body, data2.body];
return results;
}
3.2 Future 체인
then() 메서드를 통해 다음 Future 객체를 연결하여 이전 작업의 결과 값을 다음 작업에 전달하고 활용할 수 있습니다. 마치 작업 흐름을 체인처럼 이어나가는 방식입니다.
Future<String> fetchData() async {
// 비동기 작업을 수행하고 문자열 결과를 반환합니다.
final response = await http.get(Uri.parse('https://www.example.com'));
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('Failed to fetch data');
}
}
void main() async {
// fetchData() 함수를 호출하고 결과 값을 변수에 저장합니다.
final data = await fetchData();
// data 변수를 사용하여 다음 작업을 수행합니다.
final processedData = await processData(data);
print(processedData);
}
3.3 Future 오류 처리
catchError() 메서드를 사용하여 비동기 작업 중 발생하는 오류를 처리하고 예외 상황을 관리할 수 있습니다.
Future<String> fetchData() async {
try {
// 비동기 작업을 수행하고 문자열 결과를 반환합니다.
final response = await http.get(Uri.parse('https://www.example.com'));
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('Failed to fetch data');
}
} catch (error) {
// 오류가 발생하면 에러 메시지를 출력합니다.
print(error.toString());
return '';
}
}
4. void: 고급 기능 활용
4.1 Completer
Completer 객체를 사용하면 비동기 작업의 진행 상황을 직접 제어하고, 결과 값을 명시적으로 제공할 수 있습니다.
마치 직접 컨트롤러를 잡고 작업을 조율하는 것과 같습니다.
void saveData() async {
// Completer 객체를 생성합니다.
final completer = Completer<void>();
// 비동기 작업을 수행합니다.
final data = {'name': 'Flutter', 'age': 2};
await SharedPreferences.getInstance().then((prefs) {
prefs.setString('data', jsonEncode(data));
completer.complete(); // 작업 완료를 알립니다.
});
// 작업 완료를 기다립니다.
await completer.future;
}
void main() {
// saveData() 함수를 호출하고 작업 완료를 기다립니다.
saveData();
print('Data saved');
}
4.2 Stream
Stream 객체는 비동기 작업에서 발생하는 데이터를 실시간으로 스트리밍 방식으로 전달합니다.
마치 끊임없이 흐르는 강물처럼 데이터를 수신하고 처리할 수 있습니다.
Stream<String> fetchData() async {
// 비동기 작업을 수행하고 데이터를 스트리밍합니다.
final response = await http.get(Uri.parse('https://www.example.com'));
if (response.statusCode == 200) {
return response.body.split('\n').map((line) => line.trim()).where((line) => line.isNotEmpty()).asyncMap((line) async {
// 각 라인을 처리하고 결과를 스트리밍합니다.
final processedData = await processData(line);
return processedData;
});
} else {
throw Exception('Failed to fetch data');
}
}
void main() async {
// fetchData() 함수를 호출하고 스트림을 구독합니다.
final subscription = fetchData().listen((data) {
print(data); // 각 데이터를 수신하고 처리합니다.
}, onError: (error) {
print(error.toString()); // 오류 발생 시 처리합니다.
}, onDone: () {
print('Stream completed'); // 스트림 완료 시 처리합니다.
});
// 나중에 스트림 구독을 취소할 수 있습니다.
await subscription.cancel();
}
5. async와 await 키워드
비동기 작업을 처리할 때 async와 await 키워드를 함께 사용하면 코드 가독성을 높이고 효율적으로 작업을 처리할 수 있습니다.
- async: 함수를 비동기 함수로 선언합니다.
- await: 비동기 작업이 완료될 때까지 기다립니다.
Future<String> fetchData() async {
// 비동기 작업을 수행하고 문자열 결과를 반환합니다.
final response = await http.get(Uri.parse('https://www.example.com'));
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('Failed to fetch data');
}
}
void main() async {
// fetchData() 함수를 호출하고 결과 값을 변수에 저장합니다.
final data = await fetchData();
// data 변수를 사용하여 후속 작업을 수행합니다.
print(data);
}
6. 실제 개발에서의 활용 사례
- 네트워크 통신: 서버로부터 데이터를 요청하고 결과를 처리합니다. (예: API 호출, 데이터 불러오기)
- 파일 입출력: 로컬 파일을 읽고 쓰거나, 외부 저장공간에 파일을 저장합니다.
- 데이터베이스 작업: 데이터베이스에 데이터를 저장하고 조회합니다.
- UI 업데이트: UI를 동적으로 업데이트하고 애니메이션을 실행합니다.
- 백그라운드 작업: 사용자와 상호 작용하지 않고 수행되는 작업을 처리합니다.
7. 주의 사항 및 권장 사항
- 비동기 작업 오류 처리: try-catch 또는 catchError()를 사용하여 오류를 처리하고 예외 상황을 관리합니다.
- 성능 최적화: Future.wait() 또는 Stream과 같은 도구를 사용하여 여러 비동기 작업을 효율적으로 처리합니다.
- 코드 가독성 유지: 명확한 변수명, 주석 및 코드 구조를 사용하여 코드를 이해하기 쉽게 만듭니다.
- 테스트 코드 작성: 테스트 코드를 작성하여 코드의 정확성과 안정성을 검증합니다.
8. 마무리
플러터에서 Future와 void는 비동기 작업을 처리하는 데 필수적인 도구입니다.
상황에 맞는 도구를 선택하고 올바르게 사용하여 효율적이고 안정적인 코드를 작성하시기 바랍니다.
본 블로그 포스팅이 플러터의 비동기 프로그래밍 개념을 이해하고 활용하는 데 도움이 되었기를 바랍니다.
궁금한 점이나 더 알아보고 싶은 내용이 있다면 언제든지 질문해주세요!
Starting Google Play App Distribution! "Tester Share" for Recruiting 20 Testers for a Closed Test.
'Flutter' 카테고리의 다른 글
플러터에서 Generics 쉽게 이해하기 (0) | 2024.08.04 |
---|---|
플러터에서 콜백 함수란? 이해하고 활용하기 (0) | 2024.08.01 |
플러터에서 Event Loop 구현하기 (0) | 2024.07.31 |
플러터에서 싱글톤 패턴 만드는 방법과 이유 (0) | 2024.07.31 |
플러터에서 비동기 처리: Return, then, 그리고 try-catch 사용법 (0) | 2024.07.31 |