본문 바로가기
Mobile Programming

Flutter - FutureBuilder 사용법 + API 서버 http 연동

by 맑은안개 2022. 3. 18.

들어가며..

  앞서 본 블로그에서 Jsonplaceholder에서 제공하는 API를 http패키지를 사용하여 연동하였다.
Flutter는 앞서 살펴본 API응답객체인 Future에 대한 대응 Widget으로, FutureBuilder를 제공한다.
  이번 장에서는 FutureBuilder사용법에 대해 간단히 살펴보고자 한다. 앞서 진행한 소스는 첨부하지 않으므로 아래 관련 포스트에서 먼저 확인하길 바란다.

관련 포스트

2022.03.17 - [Flutter] - Flutter(Dart) - Model 객체 Json 매핑 쉽게 하기(@JsonSerializable)

2022.03.18 - [Flutter] - Flutter(Dart) - http 패키지 사용법 및 유닛테스트

FutureBuilder

Future객체는 비동기처리 결과를 처리하는 객체로서, snapshot 정보를 통해 비동기 처리 결과를 얻을 수 있다.

FutureBuilder 생성자 구조

  const FutureBuilder({
    Key? key,
    this.future,
    this.initialData,
    required this.builder,
  }) : assert(builder != null),
       super(key: key);
  • builderAsyncWidgetBuilder<T>클래스로서, AsyncSnapshot 객체를 제공한다. 이 스냅샷은 Future객체에 대한 처리 과정, 결과를 제공한다. ( 스냅샷으로부터 connectionState, 데이터 존재 유무를 확인 할 수 있다. 이는 아래에서 살펴본다. )
  • futureFutureBuilder가 처리할 Future객체를 바인딩한다. 여기서는 List post 객체를 얻어오는 함수를 바인딩한다.

📃 lib/view/homepage.dart

class HomePage extends StatefulWidget {
  const HomePage({Key? key, required this.fakePostApi}) : super(key: key);

  final FakePostApi fakePostApi;

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late Future<List<Post>> _allPosts;

  @override
  void initState() {
    super.initState();
    _allPosts = widget.fakePostApi.fetchAllPosts();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FutureBuilder'),
        centerTitle: true,
      ),
      body: _FetchAllPosts(context),
    );
  }
  • HomePage는 생성자 파라미터로 API매니저 객체를 받는다.
  • FakePostApi에 모든 포스트정보를 조회하는 fetchAllPosts함수를 initState에서 호출한다.

❗ Widget의 build 함수는 이벤트 트리거등에 의해 수시로 rebuilding되므로 일회성 호출 함수가 존재해서는 안된다.

_FetchAllPosts

  Widget _FetchAllPosts(context) {
    return FutureBuilder(
        future: _allPosts,
        builder: ((context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            // 요청으로 부터 아직 응답이 없는 경우
          } else if (snapshot.connectionState == ConnectionState.done) {
            if (snapshot.hasError) {
              // 에러가 발생한 경우
            } else { // hasData 
              // 정상적으로 데이터를 수신한 경우
            }
          } else {
            // Future 객체가 null 인 경우
          }
        }));
  }
  • future에 위 initState에서 호출하여 얻는 Future객체를 바인딩한다.
  • snapshot은 위와 같이 3개의 상태를 갖는다.
  • snapshot.hasData에서 정상적으로 얻어온 포스트 리스트 데이터를 출력한다.
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.connectionState == ConnectionState.done) {
            if (snapshot.hasError) {
              return Center(
                child: Text(snapshot.error.toString()),
              );
            } else { // hasData
              List<Post> _posts = snapshot.data as List<Post>;
              return ListView.separated( 
                  itemCount: _posts.length,
                  itemBuilder: ((context, index) {
                    Post _post = _posts[index];
                    return ListTile(
                      title: Text(
                        _post.title,
                        maxLines: 1,
                      ),
                      contentPadding: const EdgeInsets.fromLTRB(15, 5, 15, 0),
                      subtitle: Text(_post.body, maxLines: 2),
                    );
                  }),
                  separatorBuilder: (context, index) => const Divider(
                        thickness: 0.3,
                      ));
            }
          } else {
            return const Center(
              child: Text('No data found'),
            );
          }

Result

반응형