Flutter을 사용하여 앱을 만들다보면 가끔 Future 함수를 사용하여 외부와 통신할 때 하나의 FutureBuilder 내에서 여러개의 Future 함수를 사용할 때가 있다. 이럴 때 Future.wait를 사용하게 되는데 구글링을 통해 찾아볼 때 이를 실제로 활용하는 방법에 대한 소개가 많이 없어서 고생한 경험이 있었다. 이번 포스팅은 내가 사용한 기술, 내가 깨달은 방법을 잊지 않도록 적게 되어 문맥, 문법이 이상할 수도 있지만 이 방법은 실무에서도 유용하게 쓸 수 있을거라는 생각이 든다.
우선 구글링을 통해서 찾아본 방법들에 대해 이야기 해보면 main함수(혹은 다른 함수)에서 Future를 호출하는 예제를 보여줄 때 Future함수가 단순 리턴 함수를 사용하여 보여주는 경우가 많았다. 아래 예제를 본다면 알 수 있을거다
void main() {
final List list;
list = await Future.wait([
example1();
example2();
example3();
]);
}
Future<String> example1() {
//Future처리
return 'success process1';
}
Future<String> example2() {
//Future처리
return 'success process2';
}
Future<String> example3() {
//Future처리
return 'success process3';
}
하지만 필자는 비동기식 처리를 할 때 이런식으로 처리하는 것보다는 실제 화면에 FutureBuilder을 걸어두고 화면에 외부의 데이터와 통신할 때 자주 사용하여 사실상 의미가 없는 예제 였다.
이제는 본 필자가 찾은 방법을 알려주겠다.
우선 예제를 먼저 보자
import 'package:flutter/material.dart';
class ExampleFutureWait extends StatelessWidget {
const ExampleFutureWait({super.key});
Future<String> fetchData1() async {
await Future.delayed(const Duration(seconds: 2));
return '데이터 1';
}
Future<DataModel> getData() async {
List<DataModel> dataInstence = [];
final coreUrl = Uri.parse('당신의 API URL');
final response = await http.get(coreUrl);
if (response.statusCode == 200) {
final Map<String, dynamic> data = jsonDecode(response.body);
final List<dynamic> dataSet = data['호출 키 필드(ex: jvak)'];
// "jvak" 필드가 있는 경우에는 아래 코드를 사용합니다.
for (var data in dataSet) {
movieInstence.add(DataModule.fromJson(data));
}
// "jvak" 필드가 없는 경우에는 데이터를 직접 파싱합니다.
final DataModel dataModel = DataModel.fromJson(data);
return dataInstence;
} else {
print('''
error[jvapApiService - response error] \nresponseCode = ${response.statusCode}
''');
}
throw Error();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([
fetchData1(),
getData(),
]),
builder: (context, snapshot) {
if (snapshot.hasData) {
// Future 함수의 반환타입이 String이었으니 String으로 형변환을 해준다.
final fetchData = snapshot.data![0] as String;
// Future 함수의 반환타입이 List<DataModule>이니 List<DataModule>로 형변환을 해준다.
final getData = snapshot.data![1] as DataModule;
return Container(
child: Column(
children: [
Text(fetchData),
Text(getData.first.title),
],
),
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else {
return const CircularProgressIndicator();
}
},
);
}
}
코드의 설명을 먼저 하면 fetchData1() 함수는 2초의 딜레이 후에 '데이터 1'이라는 문자열을 반환하는 비동기 작업을 수행한다.
다음으로, getData() 함수는 HTTP 요청을 통해 데이터를 가져오는 비동기 작업을 수행하고, 이 함수에서는 API의 URL을 구성하여 http.get() 메서드를 사용하여 데이터를 요청한다.
만약 응답 상태 코드가 200인 경우, 응답 데이터를 JSON 형식으로 파싱하고 데이터 모델로 변환하고 데이터 모델은 '호출 키 필드' 값을 기준으로 생성된다. 그렇지 않은 경우에는 데이터를 직접 파싱하고 이렇게 가져온 데이터 모델을 반환하게 된다.
build() 메서드에서는 FutureBuilder 위젯을 사용하여 비동기 작업의 결과에 따라 화면을 구성하고 Future.wait() 메서드를 사용하여 fetchData1()과 getData() 함수를 동시에 실행하며, 두 작업이 모두 완료될 때까지 기다린다.
snapshot 객체를 통해 비동기 작업의 상태와 결과에 접근할 수 있는데, snapshot.hasData를 사용하여 데이터의 유무를 확인하고, 결과를 화면에 표시할 수 있다.
가져온 데이터는 배열 형태로 반환되므로, 결과를 적절한 형태로 형변환하여 사용한다.
예제에서는 fetchData 변수에는 snapshot.data[0]을, getData 변수에는 snapshot.data[1]을 할당하고, 이후에는 이 변수들을 활용하여 화면을 구성한다.
만약 데이터가 없는 경우나 데이터를 기다리는 중인 경우에는 로딩 인디케이터를 표시한다.
이 코드에서 관심있게 봐야 하는것은 이부분이다.
final fetchData = snapshot.data![0] as String;
final getData = snapshot.data![1] as DataModule;
fetchData는 String타입을 반환하고 getData는 DataModule를 반환하므로 반환 타입에 맞게 snapshot.data[index] 를 형변환 해주어 내부의 변수를 사용할 수 있다.
[Flutter]Flutter와 Firebase를 활용하여 버스 좌석 예약 앱 만들기 (0) | 2024.05.06 |
---|---|
[Flutter] 앱 만들기 기초: Scaffold 활용 방법 (2) | 2024.04.04 |
[Flutter] Flexible 위젯 사용법 (0) | 2023.10.05 |
[Flutter] DataModel을 사용하여 api 데이터 화면에 보여주기 (3) (0) | 2023.10.01 |
[Flutter] DataModel을 사용하여 api 데이터 화면에 보여주기 (2) (0) | 2023.10.01 |