본문 바로가기
Flutter 독학으로 기본기 익히기

08. 실시간 데이터를 UI에 반영하는 StreamBuilder 알아보기

by daco2020 2025. 5. 9.

오늘 배울 것

오늘은 StreamBuilder의 개념과 FutureBuilder와의 차이점. 그리고 StreamBuilder의 기본 구조와 각 속성의 의미에 대해 알아보겠습니다. 

마지막으로 타이머 예제를 통한 실시간 데이터 UI 갱신을 직접 실습해보겠습니다.

 

 

 

StreamBuilder란?

StreamBuilder는 지속적으로 변하는 데이터를 실시간으로 UI에 반영할 수 있게 해주는 비동기 위젯입니다. 

 

FutureBuilder가 "한 번 받아오는 비동기 데이터"에 적합하다면 StreamBuilder는 "여러 번 지속적으로 들어오는 데이터" 에 적합합니다. 예를 들어, 실시간 채팅 앱이나 센서 데이터, 데이터 스트림과 같은 실시간 데이터 처리에 사용할 수 있습니다.

 

 

 

FutureBuilder vs StreamBuilder

지난 시간에 배운 FutureBuilder 와 특징을 비교해보면 다음 표와 같습니다.

구분 FutureBuilder StreamBuilder
데이터 횟수 한 번만 여러 번, 지속적으로
사용 용도 API 요청, 파일 읽기 센서 값, 채팅, 실시간 알림, 주가 스트리밍 등
동작 방식 한 번 실행되고 종료 스트림을 계속 구독하고 새 데이터 올 때마다 rebuild
리턴 타입 Future<T> Stream<T>

 

 

 

 

StreamBuilder 구조

StreamBuilder<T>(
  stream: someStream, // 구독할 Stream
  builder: (context, AsyncSnapshot<T> snapshot) {
    // snapshot: 현재 상태 + 데이터
    ...
  },
);

 

1. `StreamBuilder<T>`에서 T는 Stream이 내보내는 데이터 타입을 뜻합니다.

 

- Stream<int>라면 StreamBuilder<int>
- Stream<String>이면 StreamBuilder<String>
- Stream<List<User>>라면 StreamBuilder<List<User>>

 

이렇게 타입을 지정해줘야 snapshot.data를 쓸 때 타입 추론이 돼서 오류가 줄어듭니다.

 

 

 

2. `stream: someStream` 은 비동기 데이터를 지속적으로 받아오는 Stream 객체가 들어갑니다. 아래는 예시 Stream 객체입니다.

 

- Stream.periodic(...) → 일정 주기로 데이터 발행
- FirebaseFirestore.instance.collection(...).snapshots() → Firestore 실시간
- WebSocketChannel.stream → 소켓 통신
- StreamController.stream → 직접 만든 스트림

StreamBuilder는 이 stream을 "구독"해서 새 데이터가 들어올 때마다 UI를 자동으로 갱신합니다.

 

 

 

3. `snapshot` 안에는 현재 Stream 상태와 데이터가 들어 있습니다. 새 데이터가 오면 builder는 다시 호출돼서 UI를 다시 빌드해줍니다.


*잠깐! snapshot은 뭔가요?

더보기

snapshot은 Future나 Stream의 현재 상태와 데이터를 담고 있는 객체를 말합니다.

 

FutureBuilder나 StreamBuilder를 사용할 때 builder 함수는 항상 (context, snapshot) 인자를 받습니다. 이때, snapshot 객체는 아래처럼 다양한 상태 정보가 들어 있습니다.

 

snapshot 상태 정보 설명
connectionState 현재 연결 상태 (none, waiting, active, done)
hasData 데이터가 있는지 여부 (true or false)
data 실제로 받은 데이터
hasError 에러 발생 여부
error 에러가 있다면 그 에러 내용

 

 

 

 

실습 예제: 1초마다 숫자가 증가하는 타이머

아래 코드를 main.dart 파일에 붙여넣기 하고 앱을 실행해주세요.

import 'dart:async';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: MyHomePage());
  }
}

class MyHomePage extends StatelessWidget {
  // 1초마다 숫자를 증가시키는 Stream 생성
  Stream<int> numberStream() {
    return Stream.periodic(Duration(seconds: 1), (count) => count);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('StreamBuilder 예제')),
      body: Center(
        child: StreamBuilder<int>(
          stream: numberStream(), // 여기서 스트림을 구독
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return Text("스트림 연결 중...");
            } else if (snapshot.hasError) {
              return Text("에러 발생: ${snapshot.error}");
            } else if (snapshot.hasData) {
              return Text('숫자: ${snapshot.data}', style: TextStyle(fontSize: 30));
            } else {
              return Text("데이터 없음");
            }
          },
        ),
      ),
    );
  }
}

 

numberStream() 함수는 Stream.periodic을 이용하여 1초마다 count를 증가시켜서 스트림으로 보냅니다. StreamBuilder는 이 데이터를 받아서 실시간으로 화면의 숫자를 업데이트합니다.

 

이처럼 StreamBuilder를 이용하면 별도의 상태관리 없이 간결한 코드로 실시간 UI 갱신을 구현할 수 있습니다. 

 

 

 

Recap

- StreamBuilder는 실시간 데이터 UI 처리에 적합한 비동기 위젯입니다. 
- FutureBuilder는 한 번의 비동기 작업, StreamBuilder는 지속적인 데이터를 받아올 때 사용합니다.
- StreamBuilder<T>에서 T는 스트림이 내보내는 데이터 타입이고 stream은 구독할 스트림 객체입니다. 
- StreamBuilder를 사용하면 별도의 상태관리 없이도 실시간 UI 업데이트가 가능합니다.