플러터 강좌: 14.7 Stream과 StreamBuilder

플러터는 비동기 프로그래밍을 지원하는 강력한 프레임워크입니다. 그 중에서도 Stream과 StreamBuilder는 데이터의 흐름을 다루기 위해 필수적인 요소입니다. 이 글에서는 Stream과 StreamBuilder의 개념, 사용법 및 실제 예제를 통해 이 두 개념을 깊이 있게 살펴보겠습니다.

1. Stream이란?

Stream은 비동기로 데이터를 전달하는 반응형 프로그래밍의 개념입니다. 데이터가 발생할 때마다 이를 받아서 처리할 수 있습니다. 예를 들어, 서버에서 지속적으로 데이터를 받아오거나, 사용자 입력을 실시간으로 감지하여 처리하는 등의 상황에서 Stream을 사용할 수 있습니다.

1.1 Stream의 기본 개념

Stream은 일련의 비동기 이벤트를 발생시키며, 이러한 이벤트를 소비하는 ‘리스너’가 존재합니다. Stream은 여러 가지 유형이 있으며, 가장 일반적인 두 가지는 단일 값만 발생시키는 Future와 여러 값을 발생시키는 Stream입니다.

1.2 Stream의 주요 특징

  • 데이터 흐름: Stream은 데이터가 생성될 때마다 이를 전달합니다.
  • 비동기 처리: Stream은 비동기적으로 데이터를 처리하여 UI를 더 부드럽게 유지합니다.
  • 리스너: Stream은 데이터를 소비할 리스너를 필요로 합니다.
  • 다양한 소스: Stream은 여러 데이터 소스로부터 데이터를 수신할 수 있습니다. 예를 들어, 웹 소켓, HTTP 요청 또는 사용자 입력 등입니다.

2. StreamBuilder란?

StreamBuilder는 Flutter의 위젯으로, Stream의 데이터를 UI에 반영하기 위해 사용됩니다. StreamBuilder는 Stream과 연결되어 있으며, 데이터가 새롭게 발생할 때마다 자동으로 UI를 업데이트합니다.

2.1 StreamBuilder의 구조

StreamBuilder는 일반적으로 다음과 같은 형태로 사용됩니다:

StreamBuilder(
  stream: yourStream,
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    // 데이터를 처리하고 UI를 구성합니다.
  },
);

여기서 yourStream는 데이터가 생성될 위치이며, 이 Stream에 대한 리스너가 자동으로 생성됩니다. builder 매개 변수는 Snapshot을 받아서 UI를 생성하는 함수입니다.

2.2 AsyncSnapshot

StreamBuilder의 builder 함수에서 받는 AsyncSnapshot 객체는 Stream의 상태 및 데이터를 포함합니다. 이를 통해 데이터의 로딩 상태, 오류 발생 여부 등을 쉽게 관리할 수 있습니다.

3. Stream과 StreamBuilder의 사용 예제

이제 실제 코드를 통해 Stream과 StreamBuilder의 사용법을 살펴보겠습니다. 이 예제에서는 주기적으로 현재 시간을 스트림으로 전송하고, StreamBuilder를 통해 이를 표시하는 간단한 애플리케이션을 만들어보겠습니다.

3.1 Stream 생성하기

먼저, 시간을 주기적으로 보내주는 Stream을 생성해 보겠습니다.

Stream<DateTime> getTimeStream() async* {
  while (true) {
    yield DateTime.now();
    await Future.delayed(Duration(seconds: 1));
  }
}

위 코드는 DateTime 객체를 1초마다 생성하는 Stream을 반환합니다. async* 키워드를 사용하여 비동기 함수를 정의하고 yield를 통해 값을 발생시킵니다.

3.2 StreamBuilder로 UI 구성하기

이제 StreamBuilder를 사용하여 위에서 생성한 Stream을 UI에 표시해보겠습니다.

class TimeDisplay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<DateTime>(
      stream: getTimeStream(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Text('Loading...');
        } else if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        } else {
          return Text('Current time: ${snapshot.data}');
        }
      },
    );
  }
}

위 코드는 StreamBuilder를 사용하여 시간을 표시하는 예제입니다. 로딩 중일 때는 ‘Loading…’이라는 텍스트를 출력하고, 오류가 발생하면 오류 메시지를 출력하며, 데이터가 성공적으로 수신되면 현재 시간을 보여줍니다.

4. Stream과 StreamBuilder의 활용 사례

Stream과 StreamBuilder는 다양한 곳에서 활용될 수 있습니다. 다음은 이를 활용할 수 있는 몇 가지 예시입니다:

4.1 실시간 데이터 통신

예를 들어, 챗 애플리케이션에서는 사용자가 보내는 메시지를 실시간으로 받아와야 합니다. 이때 Stream을 사용하여 새 메시지를 수신하고 StreamBuilder를 사용하여 UI를 업데이트할 수 있습니다.

4.2 주기적인 데이터 업데이트

주식 가격이나 날씨 정보와 같이 주기적으로 업데이트되는 데이터를 가져올 때 Stream을 사용할 수 있습니다. 이를 통해 사용자에게 항상 최신 정보를 제공할 수 있습니다.

4.3 사용자 이벤트 처리

사용자의 입력이나 동작을 감지하여 실시간 피드백을 제공하는 앱에서도 Stream을 활용할 수 있습니다. 예를 들어, 사용자가 폼을 작성할 때, 각 필드의 유효성을 실시간으로 검사하고 피드백을 주는 경우입니다.

5. Stream과 StreamBuilder의 장단점

5.1 장점

  • 비동기 처리: 데이터를 비동기로 처리하여 UI의 반응성을 높입니다.
  • 실시간 업데이트: 데이터가 변경될 때마다 UI를 자동으로 업데이트합니다.
  • 복잡한 데이터 흐름 관리: 여러 데이터 소스와 복잡한 데이터 흐름을 쉽게 관리할 수 있습니다.

5.2 단점

  • 복잡성: 초보자에게는 비동기 프로그래밍이 복잡하게 느껴질 수 있습니다.
  • 자원 소모: 불필요한 Stream을 유지하면 시스템 자원을 낭비할 수 있습니다.
  • 오류 관리: 비동기 작업에서 발생할 수 있는 오류를 일관되게 처리해야 합니다.

6. 결론

이 글에서는 플러터의 Stream과 StreamBuilder에 대해 자세히 알아보았습니다. Stream은 데이터를 비동기로 전달하는 강력한 도구이며, StreamBuilder는 이를 UI에 쉽게 적용할 수 있도록 도와주는 위젯입니다. 실제 예제를 통해 이 두 개념을 이해하는 데 도움이 되었기를 바랍니다. 비동기 프로그래밍은 처음에는 다소 어려울 수 있지만, 다양한 활용 사례를 통해 점점 익숙해질 수 있습니다. 플러터를 통한 재밌고 유용한 애플리케이션 개발에 이 두 개념을 적극 활용해보세요!

7. 참고 자료