Flutter - #22. FutureBuilder

Pearl Lee·2021년 6월 22일
2

Flutter Widget

목록 보기
21/50

내가 보려고 쓰는 Flutter 일기
출처1 : https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
출처2 : https://api.flutter.dev/flutter/dart-async/Future-class.html






FutureBuilder

오늘 배워볼 것은 FutureBuilder
몇 번 보긴 했는데 쓰임새를 알기란 너무도 어려워... 지금도 확실하게는 잘 모르는듯

일단 Future라는 것은, 미래 특정 시점에 이용 가능한 잠재적인(아직 확정되지 않은) 값이나 오류를 표현할 때 쓰이는 객체이다.
즉, 실행이 언제 완료될 지 모르는 값을 받는 객체이다.

언제 완료될지도 모르는거 뭐 언제쓰나?

인터넷 접속해서 데이터를 가져다가 가공해서 쓰던지 그림을 가져와서 빵 띄우고 싶다던가 할때 쓴다. 그림 하나 가져와서 액자에 걸어야지~ 하고 그림을 기다리는데, 우선 그림이 도착해야 액자로 싸서 걸든 땅바닥에 두든 할 수 있다. 그림을 넣는다면 Container 위젯을 주로 쓸텐데, Container 위젯 그려내기 전에 그림이 먼저 도착해야 하는 것이다.

그런데 그림이 오다가 사고가 나서 못오게 될 수도 있다. 그럴 경우에는 어떻게 하는가? '그림이 없습니다' 하고 표시해야 한다.

처리에 시간이 좀 걸리는 함수의 결과값을 기다린다던지, 데이터베이스의 데이터를 조회해서 화면에 띄우는 등에도 사용할 수 있다.

FutureBuilder는 이러한 Future와의 상호작용에서 생긴 snapshot중 가장 최근 것을 바탕으로 위젯을 만든다고 한다. 뭐래는겨

해석을 이따위로 할바에야 코드를 보고 이해하는게 낫겠다.












코드 예시로 알아보자

이번에도 어김없이 공식 페이지의 코드를 가져다가 돌려보았다.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

/// This is the main application widget.
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {

  final Future<String> _calculation = Future<String>.delayed(
    const Duration(seconds: 5),
        () => 'Data Loaded',
  );

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Test FutureBuilder'),
      ),
      body: DefaultTextStyle(
        style: Theme.of(context).textTheme.headline2!,
        textAlign: TextAlign.center,

        child: FutureBuilder<String>(
          future: _calculation, // a previously-obtained Future<String> or null
          builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
            List<Widget> children;

            if (snapshot.hasData) {
              children = <Widget>[
                const Icon(
                  Icons.check_circle_outline,
                  color: Colors.green,
                  size: 60,
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 16),
                  child: Text('Result: ${snapshot.data}'),
                )
              ];
            } else if (snapshot.hasError) {
              children = <Widget>[
                const Icon(
                  Icons.error_outline,
                  color: Colors.red,
                  size: 60,
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 16),
                  child: Text('Error: ${snapshot.error}'),
                )
              ];
            } else {
              children = const <Widget>[
                SizedBox(
                  child: CircularProgressIndicator(),
                  width: 60,
                  height: 60,
                ),
                Padding(
                  padding: EdgeInsets.only(top: 16),
                  child: Text('Awaiting result...'),
                )
              ];
            }
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: children,
              ),
            );
          },
        ),
      ),
    );
  }
}

처음 코드를 보고 너무 당황하였지만... 차근차근 이해해보자.



Future.delayed

  final Future<String> _calculation = Future<String>.delayed(
    const Duration(seconds: 5),
        () => 'Data Loaded',
  );
  //아래처럼 써도 된다.
  final Future<String> _calculation = Future<String>.delayed(
      const Duration(seconds: 5), 
      () {
    return 'Data Loaded';
  });
  

이 생성자는 Duration과 함수 하나를 받는데, Duration에 설정한 시간이 지나면 두 번째 인자로 들어간 함수를 실행한다.






FutureBuilder.future

child: FutureBuilder<String>(
	// a previously-obtained Future<String> or null
          future: _calculation, 

future에는 비동기로 동작하는 함수를 넣는다. null 넣어도 되는데, 이 경우에는 initialData 속성에 설정한 값이 builder로 전달된다고 한다.
즉, future에 설정된 함수 결과값을 받아서 위젯을 띄워주는 역할을 builder가 하는 것이다. 맞겠지..?






FutureBuilder.builder

builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
            List<Widget> children;

            if (snapshot.hasData) {
              children = <Widget>[
                const Icon(
                  Icons.check_circle_outline,
                  color: Colors.green,
                  size: 60,
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 16),
                  child: Text('Result: ${snapshot.data}'),
                )
              ];
            } else if (snapshot.hasError) {
              children = <Widget>[
                const Icon(
                  Icons.error_outline,
                  color: Colors.red,
                  size: 60,
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 16),
                  child: Text('Error: ${snapshot.error}'),
                )
              ];
            } else {
              children = const <Widget>[
                SizedBox(
                  child: CircularProgressIndicator(),
                  width: 60,
                  height: 60,
                ),
                Padding(
                  padding: EdgeInsets.only(top: 16),
                  child: Text('Awaiting result...'),
                )
              ];
            }
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: children,
              ),
            );
          },
        ),




일단 최종적으로 위젯을 화면에 띄워주는 코드는 맨 아래의 요 코드이다.

return Center();




builder에 설정하는 함수 인자로 들어가는 AsyncSnapshot은 뭘까..?

공식 페이지 말로는 이렇다는데, 비동기 함수의 결과값을 받는다고 생각하면 될 것 같다. 마지막에 한 번만 받는게 아니라, 최근 결과값을 계속 받고 있는 것이다.

그 아래로는 조건 문인데,

if (snapshot.hasData)

future에 설정했던 비동기 함수(_calculation)가 다 실행되어 결과값이 왔다! 그러면 children에 위젯을 두개 넣는다. 초록색 체크표시와, 결과 값으로 온 문자열을 넣은 Text 위젯을 띄운다.


else if (snapshot.hasError)

결과값이 오다가 문제가 생겼다. 그렇다면 에러 아이콘과 에러 문구를 children에 넣어준다.



그 외의 경우라면? 아직 비동기함수가 실행중이거나 데이터가 오고있는 중이다! 그렇다면 CircularProgressIndicator() 위젯과 '결과를 기다리는 중' 문구를 children에 넣어준다.

이렇게 변화하는 children 위젯이 계속 화면에 띄워지고 있는 것이다.





마지막으로 실행화면은 아래와 같다.







전반적으로 내 마음대로 해석한 거 같긴 한데;; 대충은 이해한 것 같다.

오늘의 일기는 여기까지!

profile
안 되면 되게 하라

0개의 댓글