본문 바로가기
Flutter for Beginners

Dart의 Stream (비동기 데이터 스트림)

by Andrew's Akashic Records 2025. 3. 4.
728x90

Dart

Dart의 Stream (비동기 데이터 스트림)

Dart에서 Stream은 비동기 데이터의 연속적인 흐름을 처리하는 기능입니다.

  • Future는 단일 비동기 결과를 반환하지만,
  • Stream은 여러 개의 비동기 데이터를 순차적으로 전달할 수 있습니다.

Stream이 필요한 경우

  • 네트워크 요청을 통해 실시간 데이터 수신 (예: WebSocket)
  • 센서 데이터 처리 (예: GPS, 가속도 센서)
  • 버튼 클릭 이벤트 감지
  • 파일 다운로드 진행 상황 모니터링
  • 오디오/비디오 스트리밍 데이터 처리

1. Stream 기본 개념

  • 비동기적으로 여러 개의 데이터를 순차적으로 처리
  • 이벤트 기반으로 동작하며 데이터가 발생할 때마다 수신
  • listen()을 사용하여 구독(subscribe)
  • 한 번만 사용할 수도 있고, 여러 구독자(multicast)와 공유 가능
  • await for를 사용하여 데이터를 동기 방식으로 처리 가능

2. Stream 생성 및 데이터 수신

2.1. 기본적인 Stream 생성

Stream<int> numberStream() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i; // 1초마다 숫자 전송
  }
}

void main() async {
  print("스트림 시작...");

  await for (int value in numberStream()) {
    print("받은 값: $value");
  }

  print("스트림 종료!");
}
스트림 시작...
(1초 후) 받은 값: 1
(1초 후) 받은 값: 2
(1초 후) 받은 값: 3
(1초 후) 받은 값: 4
(1초 후) 받은 값: 5
스트림 종료!
  • yield를 사용하면 데이터를 순차적으로 전송 가능
  • await for를 사용하면 Stream을 동기 방식처럼 처리 가능

2.2. listen()을 사용하여 Stream 수신

listen()을 사용하면 비동기적으로 Stream을 수신 가능합니다.

void main() {
  Stream<int> stream = Stream.periodic(Duration(seconds: 1), (count) => count).take(5);

  stream.listen(
    (data) => print("받은 값: $data"),
    onDone: () => print("스트림 종료!"),
  );
}
(1초 후) 받은 값: 0
(1초 후) 받은 값: 1
(1초 후) 받은 값: 2
(1초 후) 받은 값: 3
(1초 후) 받은 값: 4
스트림 종료!
  • listen()을 사용하면 Stream이 비동기적으로 실행되며, onDone을 통해 종료 감지 가능

3. StreamController를 사용한 동적 Stream 생성

StreamController를 사용하면 스트림을 동적으로 생성하고 데이터를 실시간으로 추가할 수 있습니다.

3.1. 기본 StreamController 사용

import 'dart:async';

void main() {
  StreamController<String> controller = StreamController();

  // 리스너 추가 (데이터 수신)
  controller.stream.listen(
    (data) => print("받은 데이터: $data"),
    onDone: () => print("스트림 종료!"),
  );

  // 데이터 추가 (스트림에 이벤트 전송)
  controller.sink.add("Hello");
  controller.sink.add("Dart Stream");
  controller.sink.add("Goodbye");

  // 스트림 종료
  controller.close();
}
받은 데이터: Hello
받은 데이터: Dart Stream
받은 데이터: Goodbye
스트림 종료!
  • StreamController를 사용하면 스트림 데이터를 동적으로 추가 가능 
  • .sink.add()를 사용하여 실시간으로 데이터를 추가할 수 있음
  • .close()를 호출하면 스트림이 종료됨

3.2. broadcast 스트림 (여러 구독자 지원)

일반 StreamController는 단일 구독자만 허용하지만, broadcast 스트림을 사용하면 여러 구독자가 동일한 스트림을 사용할 수 있습니다.

import 'dart:async';

void main() {
  StreamController<int> controller = StreamController.broadcast();

  // 여러 구독자 추가
  controller.stream.listen((data) => print("구독자 1: $data"));
  controller.stream.listen((data) => print("구독자 2: $data"));

  // 데이터 추가
  for (int i = 1; i <= 3; i++) {
    controller.sink.add(i);
  }

  controller.close();
}
구독자 1: 1
구독자 2: 1
구독자 1: 2
구독자 2: 2
구독자 1: 3
구독자 2: 3
  • broadcast 스트림을 사용하면 여러 개의 listen()을 등록할 수 있음
  • 여러 개의 구독자가 동시에 스트림을 수신 가능

4. Stream 변환 및 필터링

4.1. map()을 사용한 데이터 변환

void main() {
  Stream<int> numbers = Stream.periodic(Duration(seconds: 1), (x) => x + 1).take(5);

  numbers.map((num) => "숫자: $num").listen(print);
}
숫자: 1
숫자: 2
숫자: 3
숫자: 4
숫자: 5
  • map()을 사용하여 스트림 데이터를 변환 가능

4.2. where()을 사용한 데이터 필터링

void main() {
  Stream<int> numbers = Stream.periodic(Duration(seconds: 1), (x) => x + 1).take(5);

  numbers.where((num) => num.isEven).listen(print);
}
2
4
  • 짝수만 필터링하여 출력 가능

4.3. take()와 skip()을 사용하여 데이터 제어

void main() {
  Stream<int> numbers = Stream.periodic(Duration(seconds: 1), (x) => x + 1);

  numbers.take(3).listen((data) => print("처음 3개: $data"));
  numbers.skip(3).take(3).listen((data) => print("3개 건너뛰고: $data"));
}
  • take(n): 처음 n개의 데이터만 가져옴
  • skip(n): 처음 n개의 데이터를 건너뜀

5. Stream과 Future 비교

특징 Future Stream
반환 값 한 번만 반환 여러 개 반환 가능
비동기 작업 단일 결과 지속적인 데이터 흐름
사용 방식 .then() 또는 await .listen() 또는 await for
예제 API 응답 WebSocket, 이벤트 리스너
  • Future는 한 번만 실행되는 비동기 작업에 적합
  • Stream은 연속적인 데이터 처리에 적합

Stream 요약

기능 사용법
스트림 생성 Stream.periodic(), Stream.fromIterable(), StreamController()
데이터 수신 listen(), await for
데이터 변환 map(), where(), take(), skip()
여러 구독 지원 StreamController.broadcast()
스트림 수동 종료 controller.close()

 

728x90