본문 바로가기
Flutter

[Flutter] Widget(7) - FutureBuilder

by 아마도개발자 2023. 12. 13.
FutureBuilder
  • FutureBuilder 위젯은 Future와의 상호 작용에 대해 최신 스냅샷을 기반으로 자체 build되는 위젯이다.
  • 주로 API 통신 등 데이터를 비동기적으로 받아와 화면에 그려주는 경우에 자주 쓰인다.

 

Future 관리
  • future 상태는 State.initState, State.didUpdateWidget 또는 State.didChangeDependencies가 일어나는 것보다 더 빠르게 확보되어야 한다.
  • FutureBuilder를 구성할 때 State.build 또는 StatelessWidget.build 메서드 호출 중에 생성되어서는 안된다.
  • 만약 FutureBuilder와 future가 동시에 생성된다면, FutureBuilder의 상위 요소가 rebuild될 때마다 비동기 작업이 다시 시작되게 된다.
  • 일반적인 가이드라인에 따르면 모든 빌드 method가 모든 프레임마다 호출된다고 가정하고, 호출들을 최적화하여 처리하는 것이 좋다.

 

동작 방식
  • FutureBuilder는 비동기 작업의 완료를 기반으로 위젯을 다시 빌드하며, 이는 State.setState의 완료시점에 예정된다.
  • 이러한 위젯 재구성은 비동기 작업의 완료와는 별개로 이루어진다. 즉, 위젯의 리빌딩 타이밍은 비동기 작업의 완료와 직접적으로 연관되지 않는다.
  • 이미 완료된 Future를 제공하면 FutureBuilder는 ConnectionState.waiting 상태의 단일 프레임을 생성하며, 이후에는 즉시 ConnectionState.done상태의 스냅샷을 수신한다. 이러한 동작은 비동기 작업의 완료와 위젯의 재구성 간의 비동기적 특성에 의해 발생한다.

 

builder 콜백의 비동기 스냅샷 동작

 

1. 성공적으로 완료된 경우: Future가 데이터를 성공적으로 가져온 경우, 초기데이터가 null 일 때 builder는 아래와 같은 스냅샷을 수신한다

 

  • AsyncSnapshot<String>.withData(ConnectionState.waiting, null)
  • AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')

 

2. 에러가 발생한 경우: Future가 에러를 반환한 경우, 초기 데이터가 null일 때 builder는 아래와 같은 스냅샷을 수신한다

  • AsyncSnapshot<String>.withData(ConnectionState.waiting, null)
  • AsyncSnapshot<String>.withError(ConnectionState.done, 'some error', someStackTrace)

 

initialData를 지정하여 초기 스냅샷의 데이터를 제어할 수 있다. 이는 builderFuture가 완료되기 전에 호출될 때 기본 null 값 대신 지정된 데이터를 가진 스냅샷이 전달되도록 보장하는 데 사용된다. 

 

 

FutureBuilder 예제

 

1. 데이터를 로딩하는 동안 로딩 스피너를 표시한다.

2. Future가 성공으로 완료 되면 성공 아이콘과 텍스트를 표시한다.

3. Future가 오류로 완료되면 오류 아이콘과 텍스트를 표시한다.

 

 

  • 예제 코드
import 'package:flutter/material.dart';

/// Flutter code sample for [FutureBuilder].

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

class FutureBuilderExampleApp extends StatelessWidget {
  const FutureBuilderExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: FutureBuilderExample(),
    );
  }
}

class FutureBuilderExample extends StatefulWidget {
  const FutureBuilderExample({super.key});

  @override
  State<FutureBuilderExample> createState() => _FutureBuilderExampleState();
}

class _FutureBuilderExampleState extends State<FutureBuilderExample> {
  final Future<String> _calculation = Future<String>.delayed(
    const Duration(seconds: 2),
        () => 'Data Loaded',
  );

  @override
  Widget build(BuildContext context) {
    return DefaultTextStyle(
      style: Theme.of(context).textTheme.displayMedium!,
      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(
                width: 60,
                height: 60,
                child: CircularProgressIndicator(),
              ),
              Padding(
                padding: EdgeInsets.only(top: 16),
                child: Text('Awaiting result...'),
              ),
            ];
          }
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: children,
            ),
          );
        },
      ),
    );
  }
}

 

  • 실행 화면

 

Future Loading

 

 

Future Complete