[Flutter] FutureBuilder를 사용해서 비동기 처리에 따라 표시되는 위젯을 구성하자.

2021. 2. 24. 10:43Programming/Flutter

반응형

Async/await 키워드를 사용하여 비동기를 처리하는 건, 직관적으로 코드를 작성하는 데 몹시 중요하다. 그렇다면 Widget을 생성할 때, 초기 상태로 불러올 데이터가 비동기적인 처리를 거쳐야 할 때는 어떻게 해야할까? 여기서 FutureBuilder가 등장한다. 간단하게 설명해서 FutureBuilder는 비동기적인 처리를 진행하고, 결과에 따라 표시할 Widget을 반환해준다.

FutureBuilder에 대한 자세한 명세는 공식 문서를 참조하도록 하자.

class Sample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<SharedPreferences>(
      future: SharedPreference.getInstance(),
      builder: (BuildContext context, AsyncSnapshot<SharedPreferences> snapshot) {
        String result;

        if (snapshot.hasData == false) {
          result = "Loading...";
        } else if (snapshot.data.getString("sessionKey")) {
          result = "You has session key.";
        } else {
          result = "You has not session key.";
        }

        return Center(
          child: Text(result);
        );
      }
    )
  }
}

위는 Flutter의 SharedPreferences 패키지를 사용해서, SharedPreferences에 sessionKey값을 불러오는 코드다. SharedPreferences 패키지는 getInstance() 함수를 호출해서 인스턴스를 받아오는 팩토리 디자인을 사용하고 있으며, 이 과정에서 디스크에 접근하기 때문에 비동기로 처리된다.

예제 코드를 통해 FutureBuilder<T>에서 <T>는 비동기로 처리할 타입이 된다는 건 쉽게 눈치챌 수 있을 것이다. builder에서는 AsyncSnapshot<T>인자를 통해서 <T>에 넘겨진 타입이 비동기로 처리되는 동안의 상태값을 처리해준다.

FutureBuilder가 어떻게 상태를 처리하는지 확인하려면, FutureBuilderinitState()를 확인해보면 쉽게 알 수 있다.

@override
void initState() {
  super.initState();
  _snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
  _subscribe();
}

void _subscribe() {
  if (widget.future != null) {
    final Object callbackIdentity = Object();
    _activeCallbackIdentity = callbackIdentity;
    widget.future.then<void>((T data) { // 2
      if (_activeCallbackIdentity == callbackIdentity) {
        setState(() {
          _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
        });
      }
    }, onError: (Object error) {
      if (_activeCallbackIdentity == callbackIdentity) {
        setState(() {
          _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error);
        });
      }
    });
    _snapshot = _snapshot.inState(ConnectionState.waiting); // 1
  }
}

위의 코드는 _FutureBuilderState<T>initState()_subscribe()이다. initState()에서는 초기값 widget.initialData으로 상태값 _snapshot을 초기화한다. FutureBuilder<SharedPreferences>를 선언할 때 initialData를 넘겨주지 않았기 때문에 여기서는 null로 초기화되며, AsyncSnapshotdatanull로 초기화됐기 때문에 AsyncSnapshot.hasData의 값은 false로 초기화된다.

이후 _subscribe()함수 내부에서 widget.future에 대한 처리방식을 확인할 수 있다. 크게 복잡한 건 없고, 처리 순서를 확인하면서 따라가면 쉽게 이해가 가능하다.

  1. 상태값 _snapshotConnectionStatewaiting으로 초기화해준다. 비동기 처리가 완료되기를 기다리고 있다는 중이라는 의미이다.
  2. widget.future.then을 사용하여 비동기 처리를 진행한다. 처리가 완료될 경우 상태값 _snapshot에 완료된 데이터와 함께 새로운 AsyncSnapshot의 객체를 할당하며, _snapshot의 내부 상태값 ConnectionStatedone으로 설정한다. 비동기 처리 중 에러가 발생한 경우에는, AsyncSnapshot.withError를 호출하여 데이터 대신 에러를 설정해준다.

이제 FutureBuilder에서 비동기처리에 따라서 위젯을 표시하는 방식에 대해 이해하고, 꼭 FutureBuilder를 사용하지 않고StatefulWidget에서 비동기 처리를 통한 화면 구성을 어떻게 할 수 있는지도 이해할 수 있을 것이다. 다만 FutureBuilder에는 에러 처리와 자원 할당 해제와 같은 세세한 내용이 포함되어있으므로, 가급적이면 FutureBuilder를 사용하여 비동기 처리에 따른 분기 처리를 해주도록 하자. :)

반응형