Foggy day

[Dart] stream 사용법 - 2, StreamController single listen 본문

Flutter/Dart 문법

[Dart] stream 사용법 - 2, StreamController single listen

jinhan38 2023. 6. 2. 00:13

 

이번 포스팅에서는 StreamController에 대해 알아보겠습니다.

 

StreamController는 stream을 구독할 수 있는 클래스입니다.

이전 포스팅에서 Stream을 사용해 데이터의 흐름을 관찰할 수 있다고 했습니다. 그리고 StreamController는 이러한 Stream을 편하게 관리할 수 있도록 도와줍니다. 

 

 

1. StreamController 생성

2. Stream listen

3. button widget

4. add 

5. addStream

6. onListen, onResume, onPause, onCancel

7. close

 

 


최종 영상

 

 

 

 

1. StreamController 생성

StreamController를 생성할 때 Stream의 데이터 타입을 설정해 줄 수 있습니다. 예제에서는 int 타입으로 선언했습니다. onListen, onResume, onPause, onCancel 함수를 선택적으로 구현할 수도 있습니다. 그런데 StreamController를 이러한 방식으로 사용하면 한 개의 listener만 추가할 수 있습니다. 여러 listen 함수를 추가하고 싶다면 다른 방식으로 구현해야 합니다. 그 방법은 다음 포스팅에서 다루겠습니다. 

  final streamController = StreamController<int>(
    onListen: () {
      print('onListen');
    },
    onResume: () {
      print('onResume');
    },
    onPause: () {
      print('onPause');
    },
    onCancel: () {
      print('onCancel');
    },
  );

 

 

 

 

2. Stream listen

initState안에서 stream listen 함수를 구현했습니다. listen 함수는 stream을 통해 새로운 값이 추가될 때마다 호출됩니다. 예제에서는 추가된 값(event)를 value라는 int 타입의 변수에 더해주고 있습니다. 

  int value = 0;
  
  @override
  void initState() {
    streamController.stream.listen((event) {
      print('listen : $event');
      if (mounted) {
        setState(() {
          value += event as int;
        });
      }
    }).onDone(() {
      // streamController.close()를 호출하면 진입
      print('done : $value');
    });
    super.initState();
  }
  
  
    @override
  void dispose() {
    streamController.close();
    super.dispose();
  }

 

 

 

 

3. button widget

예제에서 공통으로 사용할 버튼을 하나 만들었습니다. 

  Widget _button({required String text, required Function() onPressed}) {
    return Expanded(
      child: Container(
        height: 50,
        margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
        child: ElevatedButton(
          style: ElevatedButton.styleFrom(
            textStyle: const TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          onPressed: () {
            onPressed();
          },
          child: Text(text, textAlign: TextAlign.center),
        ),
      ),
    );
  }

 

 

 

 

4. add 

StreamController에 데이터를 추가해보겠습니다. 데이터를 추가하면 2번에서 구현한 listen 함수가 호출됩니다. add 방법은 생성한 streamController의 add 함수에 추가하고자 하는 값을 넣으면 됩니다. 이때 add 함수에 int가 아닌 다른 타입을 넣는다면 오류가 발생합니다. 왜냐하면 StreamController를 생성할 때 int 타입만 사용할 수 있도록 선언했기 때문입니다. 

  _button(
    onPressed: () => streamController.add(1),
    text: "add",
  ),

 

 

 

 

5. addStream

addStream은 하나에 데이터를 add하는 것이 아니라 Stream 타입을 추가할 수 있습니다. 예제에서는  0.3초마다 i값을 1씩 증가시키는 Stream<int>를 리턴하는 countStream 함수를 만들었습니다. countStream 함수를 addStream 함수로 추가해 주면 countStream 함수가 실행되면서 listen 함수에 값이 전달됩니다. 

Stream에 대한 더 자세한 설명은 앞선 글을 참조해주시기 바랍니다.

https://jinhan38.tistory.com/152

  _button(
    onPressed: () => streamController
        .addStream(countStream(5)),
    text: "addStream",
  )
  
  
  ....
  
  Stream<int> countStream(int to) async* {
    for (int i = 1; i <= to; i++) {
      await Future.delayed(const Duration(milliseconds: 300));
      yield i;
    }
  }

 

 

 

 

6. onListen, onResume, onPause, onCancel

StreamController를 생성할 때 onListen, onResume, onPause, onCancel 함수들을 구현했었습니다. streamController을 사용해 해당 함수를 호출하면 구현부의 함수가 호출됩니다. 예를 들어 다른 위젯에서 동일한 streamController를 사용하고, 기능을 pause 시키거나 resume 하고자 할 때 streamController에서 해당하는 함수를 호출하면 생성할 때 구현한 함수가 호출됩니다.

  _button(
    onPressed: () => streamController.onListen?.call(),
    text: "listen",
  ),
  _button(
    onPressed: () => streamController.onPause?.call(),
    text: "pause",
  ),
  _button(
    onPressed: () => streamController.onResume?.call(),
    text: "resume",
  ),

 

 

 

 

7. Close

close는 말 그대로 stream을 닫는 함수입니다. close를 호출한 후에 add를 한다면 오류가 발생합니다.

  _button(
    onPressed: () => streamController.close(),
    text: "close",
  ),

 

 

 

 

Full code

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_widget_group/screen/stream/stream_second_screen.dart';

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

  @override
  State<StreamControllerScreen> createState() => _StreamControllerScreenState();
}

class _StreamControllerScreenState extends State<StreamControllerScreen> {
  int value = 0;

  final streamController = StreamController<int>(
    onListen: () {
      print('onListen');
    },
    onResume: () {
      print('onResume');
    },
    onPause: () {
      print('onPause');
    },
    onCancel: () {
      print('onCancel');
    },
  );

  @override
  void initState() {
    streamController.stream.listen((event) {
      print('listen : $event');
      if (mounted) {
        setState(() {
          value += event as int;
        });
      }
    }).onDone(() {
      // streamController.close()를 호출하면 진입
      print('done : $value');
    });
    super.initState();
  }

  @override
  void dispose() {
    streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("SteamControllerScreen"),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          const SizedBox(height: 30),
          Text(
            "value : ${value.toString()}",
            style: const TextStyle(fontSize: 26),
          ),
          const SizedBox(height: 30),
          Expanded(
            child: SingleChildScrollView(
              child: Column(
                children: [
                  Row(
                    children: [
                      _button(
                        onPressed: () => streamController.add(1),
                        text: "add",
                      ),
                      _button(
                        onPressed: () => streamController.onListen?.call(),
                        text: "listen",
                      ),
                    ],
                  ),
                  Row(
                    children: [
                      _button(
                        onPressed: () => streamController.onPause?.call(),
                        text: "pause",
                      ),
                      _button(
                        onPressed: () => streamController.onResume?.call(),
                        text: "resume",
                      ),
                    ],
                  ),
                  Row(
                    children: [
                      _button(
                        onPressed: () => streamController
                            .addStream(countStream(5)),
                        text: "addStream",
                      ),
                      // listen이 있다면 true,
                      // listen이 없거나 close를 호출한 후라면 false
                      _button(
                        onPressed: () => setState(() {}),
                        text: "hasListener : ${streamController.hasListener}",
                      ),
                    ],
                  ),
                  Row(
                    children: [
                      _button(
                        onPressed: () => streamController.close(),
                        text: "close",
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _button({required String text, required Function() onPressed}) {
    return Expanded(
      child: Container(
        height: 50,
        margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
        child: ElevatedButton(
          style: ElevatedButton.styleFrom(
            textStyle: const TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          onPressed: () {
            onPressed();
          },
          child: Text(text, textAlign: TextAlign.center),
        ),
      ),
    );
  }

  Stream<int> countStream(int to) async* {
    for (int i = 1; i <= to; i++) {
      await Future.delayed(const Duration(milliseconds: 300));
      yield i;
    }
  }
}