⭐️ 개발/플러터

[이론] FutureBuilder 와 StreamBuilder

짱구러버 2022. 12. 20. 22:46
728x90

들어가기 앞서...

비동기 통신을 하면서 FutureBuilder 와 StreamBuilder 를 사용하게되는데, 무엇인지와 사용법을 알아보자!


본문으로...

FutureBuilder 배우기

1.  기본 세팅!

import 'dart:math';

import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final textStyle = TextStyle(
    fontSize: 16.0,
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: FutureBuilder(
          builder: (context, snapshot) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Text(
                  'FutureBuilder',
                  style: textStyle.copyWith(
                    fontWeight: FontWeight.w700,
                    fontSize: 20.0,
                  ),
                ),
                Text(
                  'ConState: ${snapshot.connectionState}',
                  style: textStyle,
                ),
                Text(
                  'Data: ${snapshot.data}',
                  style: textStyle,
                ),
                Text(
                  'Error: ${snapshot.error}',
                  style: textStyle,
                ),
              ],
            );
          },
        ),
      ),
    );
  }

  Future<int> getNumber() async {
    await Future.delayed(Duration(seconds: 3));
    final random = Random();

    return random.nextInt(100);
  }
}

 

1) ConState: ConnectionState.none?

FutureBuilder(future: )가 없으면 none 으로 출력된다.

FutureBuilder(
	future: getNumber(),
)

 

connectionState 가 박뀔때마다 builder 함수가 새로 불린다는것이다. 그렇기 떄문에 우리가 setState()함수를 사용해서 build 함수를 실행 하지 않고도! 화면의 변화를 FutureBuilder 가 자동으로 캐치해준다.

 

2) 여기서 setState 함수 버튼을 만든다면?

ElevatedButton(
	onPressed: (){
	    setState(){}
    },
    child: Text('setState'),
)

저 버튼을 누르면 SetState 함수가 실행되면서 빨간색 영역인 최상단의 build 함수가 재실행된다. 그렇다면 최상단의 build 함수가 호출되고, 그 안의 FutureBuilder 의 build 함수가 다시 실행이 될것이다.

그렇다면 setState 함수를 누르면 그전에 있던 데이터가 null 로 되고, 그 상태에서 data 를 가져올까?

아니다! FutureBuilder 는 이전 데이터를 기억하는 캐싱데이터를 가져온다. FutureBuilder 가 실행이 되고, 기존값을 기억한다음 바뀐값으로 저장을 한다.

 

 

 

 

저번에 FutureBUilder 를 사용하면서, 유저에게 별로 좋지 않은 부분으로 코딩한 부분이있다.

바로 waiting 이라고 한다면? 로딩바를 보여주기!

if(snapshot.connectionState == ConnectionState.waiting) {
	return Center(child: CircularProgressIndicator());
}

 

결과)

 

이렇게 진행을 한다면 유저는 이 앱이 굉장히 느리다고 생각을 할 것이다.

하지만, 이렇게 로딩바를 보여줘야할때가 있다!

그럴때는 밑의 if 문을 사용하면된다. 그러면 처음 build 함수를 실행할때만 로딩인디케이터가 돌아가고, setState 가 실행되었을때는 로딩인디케이터가 돌아가지 않는것을 볼 수 있다.

if(!snapshot.hasData){
	return Center(
    	child: CircularProgressIndicator();
    )
}

 

 

에러를 출력해보자!

Future<int> getNumber() async {
	...
	throw Exception('에러가 발생했습니다.');
    
    ...
}

 

 

connectionState.done

 

실제로 데이터를 잘받으면 !

Data : 랜덤숫자

Error: null

 

에러가 난다면!

Data: null

Error : 에러 문구

 

이것을 활용해서 작성가능하다.

if(snapshot.hasData){
	// 데이터가 있을때 위젯 렝더링

}

if(snapshot.hasError){
	// 에러가 났을때 위젯 렌더링
}

// 로딩 중일때 위젯 렌더링

 


StreamBuilder 에 대해 배우자!

class _HomeScreenState extends State<HomeScreen> {
...
        child: StreamBuilder(
          stream: streamNumbers(),
        ),  
      ),
    );
  }

  Stream<int> streamNumbers() async* {
    for (int i = 0; i < 10; i ++) {
      await Future.delayed(Duration(seconds: 1));

      yield i;
    }
  }
}

watting  : Stream 을 기다리는 상태 

active : Stream에서 계속 값을 받고 있을때!(Stream 이 완전히 끝나기 전 상태)

done : Stream이 완전히 끝난 상태

 

StreamBuilder 또한 캐싱된 데이터를 가져온다. setState 한다고 해서 data 값이 null 로 가지 않는다.

그리고 함수를 dispose() 해줘야하는데, Future, Stream은 자동으로 닫아주는 장점이있다. 

결과) 

 

참고!

FutureBuilder 와 Streambuilder 는 제너릭을 넣어줄수 있다. 그 타입은 snapshot 에 들어가는 데이터 타입을 넣어주면된다.

child: StreamBuilder<int>(
	stream: streamNumbers(),
    builder: (context, AsyncSnapshot<int> snapshot) {
	    ...
    }
)


child: FutureBUilder<String>(
	future: getNumber(),
    builder: (context, AsyncSnapshot<String> snapshot) {
    	...
    }
)


Future<int> getNumber() async {
	...   
}

Stream<int> streamNumbers() async* {
	...
}

 

에러를 던져보자!

5일때 에러 출력

Stream<int> streamNumbers() async* {
    for (int i = 0; i < 10; i ++) {
      if(i ==5) {
        throw Exception('i = 5');
      }

      await Future.delayed(Duration(seconds: 1));

      yield i;
    }

 

 

정리

FutureBuilder 

예 ) 갤러리에서 이미지나 비디오를 가져올때, 일회성 api 통신시 사용

StreamBuilder

예 ) 음악 재생, 타이머 재생, 지도 위치 업데이트 데이터를 여러번을 걸쳐 통신시 사용

 

둘다 공통적으로 error, data 를 받는다.

builder 의 snapshot 통해 현재 builder 의 상태를 알아 조건에 맞게 분기처리 가능하다.

 

 

출처

 

FutureBuilder class - widgets library - Dart API

Widget that builds itself based on the latest snapshot of interaction with a Future. The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.bui

api.flutter.dev

 

 

StreamBuilder class - widgets library - Dart API

Widget that builds itself based on the latest snapshot of interaction with a Stream. Widget rebuilding is scheduled by each interaction, using State.setState, but is otherwise decoupled from the timing of the stream. The builder is called at the discretion

api.flutter.dev

 


끝으로...

  1. FutureBuilder 와 StreamBuilder 를 알았다.

 

728x90