함수를 통해서 어떤 값을 리턴할 때 우리는 기본적으로 즉각적으로 값은 반환하는 상황을 주로 사용하였습니다.
하지만 애플리케이션 외부로부터 데이터를 전달받는 상황이라면 함수가 실행되고 나서 네트워크 문제등에 의해서 값을 바로 출력할 수 없는 경우가 발생합니다. 이 때 Future Builder를 이용해서 값을 받을 수 있습니다. Future Builder를 이용하면서 Future 타입의 데이터를 다루게 되는데 말 그대로 미래의 어느 한 시점(in the future)에 얻게 되는 데이터를 의미합니다.
다음의 소스코드를 통해서 값이 출력되는 과정을 살펴보도록 하겠습니다.
우리가 흔히 사용하는 함수는 함수가 호출되는 즉시 값을 반환하고 반환된 값을 변수에 저장한 뒤 화면에 출력하는 방법으로 사용됩니다. (getNumberNow함수)
하지만 외부로부터 네트워크를 통해 데이터가 전달되는 경우 함수가 호출된 후 값을 얻기까지 시간이 소요될 수 있으며, 이를 모사하기 위해서 getNumber라는 함수를 만들었습니다.
앞선 getNumberNow 함수는 반환자가 int 형식인데 반해 getNumber 함수의 경우 리턴타입이 Future<int> 타입입니다. 같은 int 타입이지만 늦게 받는다는 의미입니다.
두 함수의 차이점은 async와 await를 함수에 사용하였다는 점입니다. 함수명 바로 뒤에 async가 붙었으며 함수가 시작될 때 await가 명시되어 있습니다. 비동기 함수이며, 함수 실행이 완료될 때까지 멈춰 기다린다는 의미입니다.
getNumber 함수는 4초간 기다린 뒤 1이라는 값을 리턴하게 됩니다.
화면에서 출력하는 방법도 서로 다릅니다. getNumberNow()의 경우 Text 위젯을 감싸서 바로 호출하면 되지만, getNumber()함수수의 경우 FutureBuilder라는 별도의 위젯을 사용해야 합니다.
두 개의 파라미터를 설정해줘야 하는데 future는 데이터를 받을 Future 타입의 인스턴스(여기서는 getNumber())를 의미하며, builder는 context와 snapshot을 반환하여 각 상황에 맞게 화면을 구성할 수 있습니다. snpashot에는 future 인스턴스로부터 받은 데이터가 저장되는데, 이 때 저장된 데이터가 있으면 그 값을 출력하고 그렇지 않으면 진행표시아이콘이 출력되도록 하였습니다.
앱을 실행해보면 처음 시작하자마자 getNumberNow 함수를 출력한 결과(7)은 화면에 바로 출력되는데 반해 getNumber함수는 로딩 인디케이터를 보여주다가 4초 후에 결과가 출력됨을 확인할 수 있습니다.
floatingActionButton을 클릭하면 setState를 호출해 앱을 리빌드하도록 하였는데 이 때 getNumber()함수도 다시 호출이 되어 4초간 로딩 후 다시 값이 출력됩니다.
이는 향후 계속적으로 자료 요청을 하게되어 문제 발생 소지가 있습니다. 따라서 getNumber 함수를 바로 future에 지정하는 것이 아니라 이용하여 별도의 인스턴스를 만든 뒤 initState를 이용해서 인스턴스 초기화를 수행하여 리빌드 문제를 방지할 수 있습니다.
앱을 실행하면 세 개의 수가 출력되는데 가장 처음은 일반 함수를 이용한 것이고, 두 번째는 FutureBuilder를 이용하였으나 future 인스턴스로 getNumber 함수를 직접 호출하는 경우 세번째는 FutureBuilder를 이용하고 앞서 초기화된 futureData 인스턴스를 future의 파라메터로 전달한 경우입니다.
floatingActionButton을 클릭하였을 때 두 번째 값은 다시 실행되면서 새로운 값을 출력하지만 아래에 세번째 값은 변화가 없는 것을 확인할 수 있습니다.
이에 대한 최종 코드는 아래와 같습니다.
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Future Builder'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late Future<int?> futureData;
@override
void initState(){
super.initState();
futureData = getNumber();
}
int getNumberNow() {
return 7;
}
Future<int?> getNumber() async {
await Future.delayed(const Duration(seconds: 4));
Random random = Random();
return random.nextInt(100);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment : MainAxisAlignment.center,
children: [
Text('${getNumberNow()}',
style : const TextStyle(fontSize:20),),
const SizedBox(height : 20),
FutureBuilder<int?>(
future: getNumber(),
builder : (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
case ConnectionState.done:
default :
if (snapshot.hasData) {
int data = snapshot.data!;
return Text('$data',
style : const TextStyle(fontSize:20),);
} else if(snapshot.hasError) {
return Text('${snapshot.error}',
style : const TextStyle(fontSize:20),);
} else {
return const Text('No Data',
style : TextStyle(fontSize:20),);
}
}
}
),
const SizedBox(height : 20),
FutureBuilder<int?>(
future: futureData,
builder : (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
case ConnectionState.done:
default :
if (snapshot.hasData) {
int data = snapshot.data!;
return Text('$data',
style : const TextStyle(fontSize:20),);
} else if(snapshot.hasError) {
return Text('${snapshot.error}',
style : const TextStyle(fontSize:20),);
} else {
return const Text('No Data',
style : TextStyle(fontSize:20),);
}
}
}
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState((){}),
tooltip: 'refresh',
child: const Icon(Icons.refresh),
),
);
}
}
'프로그래밍 > Flutter & Dart' 카테고리의 다른 글
[Flutter] for와 forEach (0) | 2023.01.05 |
---|---|
[Flutter] Cascade Operator (0) | 2023.01.05 |
[Flutter] 보이지 않는 위젯(layout widget) (0) | 2022.12.31 |
[Flutter] const, final의 차이점(feat. immutable, stateless vs stateful 차이점) (0) | 2022.12.30 |
[Flutter] Go Router를 이용해서 페이지 이동하기 (0) | 2022.12.29 |
댓글