관리 메뉴

IT Story

Dart Stream 실시간 데이터를 효율적으로 다루는 방법 본문

Programming/Dart

Dart Stream 실시간 데이터를 효율적으로 다루는 방법

#root 2025. 4. 7. 15:27

Dart에서 Stream은 비동기적으로 여러 개의 값을 순차적으로 제공하는 데이터 소스입니다. 🔁 예를 들어, 센서 데이터 수집 📈, 사용자 이벤트 처리 🖱️, 채팅 메시지 수신 💬 등 실시간 데이터 흐름을 다룰 때 필수적인 개념입니다.

이번 포스트에서는 Stream의 기본 개념부터 실전 예제, Flutter에서의 StreamBuilder 사용법까지 완전 정복해봅니다. 💪

 

🗂️ 목차

1️⃣ Stream이란 ❓
2️⃣ Stream의 생성과 기본 구조 🔧
3️⃣ Stream 구독 (📡 listen)
4️⃣ StreamController로 스트림 만들기 🛠️
5️⃣ 비동기 for 루프와 ⏳ await for
6️⃣ Flutter에서의 StreamBuilder 🏗️
7️⃣ 스트림 에러 처리 및 종료 처리 ❗
8️⃣ 자주 하는 실수 및 팁 💡
9️⃣ 📌 결론 및 핵심 요약


 

1️⃣ Stream이란 ❓

Stream은 여러 개의 이벤트 또는 데이터를 순차적으로 🌀 비동기적으로 처리할 수 있게 해주는 객체입니다. Future가 단일 비동기 값을 다룬다면, Stream은 복수의 비동기 값을 다룹니다.

✅ 사용 예:

  • 서버로부터 오는 실시간 채팅 메시지 💬
  • 버튼 클릭 같은 사용자 인터랙션 🖱️
  • 주기적으로 업데이트되는 센서 데이터 📊

 

2️⃣ Stream의 생성과 기본 구조 🔧

📄 Stream 생성 방법

Stream<int> timedCounter(int max) async* {
  for (int i = 0; i < max; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i; // 하나씩 값을 스트림에 추가
  }
}

이 코드는 1초 ⏱️ 간격으로 0부터 max - 1까지 값을 방출하는 스트림입니다.


 

3️⃣ Stream 구독 (📡 listen)

스트림의 데이터를 받기 위해서는 listen()을 사용해 구독해야 합니다. 📻

void main() {
  Stream<int> stream = timedCounter(5);

  stream.listen((value) {
    print("📨 받은 값: \$value");
  });
}

출력:

📨 받은 값: 0
📨 받은 값: 1
...
📨 받은 값: 4

 

4️⃣ StreamController로 스트림 만들기 🛠️

개발자가 직접 값을 스트림에 넣고 제어하려면 StreamController를 사용합니다. 🧰

import 'dart:async';

final controller = StreamController<String>();

void main() {
  controller.stream.listen((data) => print("📩 이벤트 수신: \$data"));

  controller.sink.add("✉️ 첫 번째 메시지");
  controller.sink.add("✉️ 두 번째 메시지");
  controller.close();
}

StreamController는 특히 UI 이벤트 처리 🎛️나 사용자 정의 비동기 로직에 유용합니다.


 

5️⃣ 비동기 for 루프와 ⏳ await for

Dart에서는 스트림을 await for로 처리할 수 있습니다. 이는 listen()의 대안이며, 더 직관적인 흐름 제어가 가능합니다. 🧭

Stream<int> numbers() async* {
  for (int i = 0; i < 3; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

void main() async {
  await for (var n in numbers()) {
    print("🔢 숫자 수신: \$n");
  }
}

 

6️⃣ Flutter에서의 StreamBuilder 🏗️

Flutter에서는 StreamBuilder를 사용해 스트림 데이터를 UI와 연결할 수 있습니다. 📱🔄

class CounterWidget extends StatelessWidget {
  final Stream<int> counterStream = timedCounter(5);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: counterStream,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        } else if (snapshot.hasError) {
          return Text('❌ 에러 발생: \${snapshot.error}');
        } else if (snapshot.hasData) {
          return Text('📦 값: \${snapshot.data}');
        } else {
          return Text('🕳️ 데이터 없음');
        }
      },
    );
  }
}

 

7️⃣ 스트림 에러 처리 및 종료 처리 ❗

listen()에서 다음과 같이 에러 및 완료 처리를 할 수 있습니다:

stream.listen(
  (data) => print("📥 데이터 수신: \$data"),
  onError: (error) => print("❌ 에러: \$error"),
  onDone: () => print("🔚 스트림 종료"),
);

await for 사용 시 try-catch로 에러를 처리합니다:

try {
  await for (var value in stream) {
    print("📥 값: \$value");
  }
} catch (e) {
  print("🚨 에러 발생: \$e");
}

 

8️⃣ 자주 하는 실수 및 팁 💡

❗ StreamController를 닫지 않음

  • controller.close()를 하지 않으면 🧠 메모리 누수 발생 가능

❗ Stream을 여러 번 listen할 경우

  • 기본 Stream은 single-subscription → 여러 번 구독하면 에러 발생 🛑
  • asBroadcastStream()으로 변경 가능 📡
stream.asBroadcastStream();

 

9️⃣ 📌 결론 및 핵심 요약

Dart에서 Stream은 실시간 데이터를 비동기적으로 다룰 때 꼭 필요한 도구입니다. UI 이벤트 🎛️, 센서 📟, 네트워크 수신 🌐 등 다양한 상황에 유용하며, StreamBuilder, await for, StreamController 등의 도구를 통해 간결하게 구현할 수 있습니다.

✨ 핵심 요약:

  • Stream은 여러 값의 🌀 비동기 전달을 위해 사용
  • listen() 또는 await for로 스트림 수신
  • Flutter에서는 StreamBuilder로 UI 연동 💻🧱
  • StreamController로 커스텀 스트림 생성 가능 🔨
  • 종료 처리 🔚, 에러 처리 ❌ 필수 ⚠️

 


이 글이 Dart에서 스트림을 활용하는 데 큰 도움이 되었기를 바랍니다. 😊 댓글이나 피드백 언제든지 환영합니다! 💬