flutter

Flutter Stream

paulaner80 2019. 3. 20. 11:18
반응형

https://medium.com/flutter-community/reactive-programming-streams-bloc-6f0d2bd2d248


스트림이란 무엇입니까?



스트림 의 개념을 쉽게 시각화하려면 한 쪽으로만 입력가능한 2 개의 끝이있는 파이프를 생각해보세요.

파이프에 무엇인가를 삽입하면 파이프 안쪽으로 흐르고 다른 쪽 끝으로 나가게됩니다.


플러터에서 파이프는 Stream이라고 합니다.

Stream을 제어하기 위해서 보통 StreamController를 사용합니다.

스트림으로 무언가를 입력하기 위해서 StreamController는 sink 프라퍼티를 통해서 접근할 수 있는 StreamSink라고 부르는 "입구"를 노출합니다.

스트림의 출구는 stream 프라퍼티를 통해 StreamController에 의해 노출되어집니다.


StreamController를 통하지 않고도 가능하지만 이 글에서는 StreamController만 사용합니다.


스트림에 의해 전달 될 수있는 것은 무엇입니까?


값, 이벤트, 객체, 컬렉션, 맵, 오류 또는 심지어 다른 스트림에서 모든 유형의 데이터 가 스트림에 의해 전달 될 수 있습니다.


어떤 것이 스트림에 의해 전달된다는 것을 어떻게 알 수 있습니까?


Stream에 의해 무엇인가가 전달되었다는 통지를 받길 원한다면 StreamController의 stream 속성을 듣기 만하면됩니다.


listener를 정의하면 StreamSubscription 객체가 수신됩니다.

이것은 StreamSubscription 객체를 통해 이루어지며 스트림 의 레벨에서 어떤 일이 발생했다는 통지를 받게됩니다.

적어도 하나의 listener가 활성화 되자마자,

활성화된 StreamSubscription 객체 를 알리기 위해 Stream은 이벤트 를 생성하기 시작합니다:


일부 데이터는 스트림에서 나가고, 일부 오류가 스트림으로 전송 되고,  스트림이 닫힐 때 StreamSubscription 객체는 

리스닝(stop listening)을 멈추거나, 일시정지(pause)하거나 다시시작(resuem)합니다.



스트림은 단순한 파이프입니까?


아니요, 스트림 은 나가기 전에 내부에서 흐르는 데이터를 처리 할 수도 있습니다.


Stream 내부의 데이터 처리를 제어하기 위해 StreamTransformer를 사용합니다.


스트림 내에서 흐르는 데이터를 " 캡처 "하는 기능

데이터에 뭔가하기

이 변환의 결과는 역시 스트림입니다.


이 문장을 통해 여러 StreamTransformer 를 순차적으로 사용할 수 있다는 것을 바로 이해할 것입니다.


StreamTransformer 는 다음과 같이 모든 유형의 처리를 수행하는 데 사용될 수 있습니다.


filtering (필터링) : 모든 유형의 조건에 따라 데이터를 필터링하고,

regrouping(재 그룹화) : 데이터 재 그룹화,

수정 : 모든 유형의 수정을 데이터에 적용하려면,

데이터를 다른 스트림에 주입하고,

버퍼링,

처리 : 데이터를 기반으로 모든 종류의 작업 / 작업 수행,

...



스트림 유형


스트림 에는 두 가지 유형이 있습니다.


단일 구독 스트림 (Single-subscription Streams) 

이 유형의 스트림 은 해당 스트림 의 전체 수명 동안 단일 수신기 만 허용합니다.

(처음 구독이 취소된후에라도 Stream을 청취하는 것은 불가능합니다.)


브로드캐스트 스트림 (Broadcast Streams)

이 두 번째 유형의 스트림 은 모든 수 의 청취자를 허용 합니다

  (아무때나 브로드캐스트 스트림에 청취자를 추가하는 것이 가능합니다. 새로운 청취자는 스트림을 청취를 시작한 순간에 이벤트를 수신합니다.)


1. 기본예제


이 예제는 입력되는 데이터를 단순히 출력하는 "단일 구독" 스트림을 보여줍니다.

데이터 유형은 중요하지 않습니다.


import 'dart:async';

main(List<String> args) {
//Single-Subscription 스트림 컨트롤러 초기화
final StreamController ctrl = StreamController();

//단일 청취자 초기화
final StreamSubscription subscription =
ctrl.stream.listen((data) => print('$data'));

ctrl.sink.add('my name');

//StreamController 릴리즈.
ctrl.close();
}



[출력]


my name

1234

{a: element A, b: element B}

123.45







2. StreamTransformer


이 두 번째 예제는 정수 값을 전달하고 짝수 만 인쇄하는 " 브로드 캐스트 " 스트림을 보여줍니다.

이를 위해 우리는 값 을 필터링하고  오직 짝수 번만 통과하도록하는 StreamTransformer 를 적용합니다.



import 'dart:async';

main(List<String> args) {
//broadcast 스트림컨트롤러 초기화
final StreamController ctrl = StreamController<int>.broadcast();

//짝수만 필터링함.
final StreamSubscription subscription = ctrl.stream
.where((value) => value % 2 == 0)
.listen((value) => print('$value'));

//데이터를 스트림에 넣기
for (int i = 1; i <= 10; i++) {
ctrl.sink.add(i);
}

ctrl.close();
}






RxDart


요즘엔 RxDart패키지에 관한 이야기가 있어야합니다.

RxDart 패키지는 Dart Streams API를 ReactiveX 표준을 준수하도록 확장한 ReactiveX API의 Dart 구현입니다.


Google에서 정의하지 않았으므로 다른 어휘를 사용합니다. 

다음 표는 Dart와 RxDart 간의 상관 관계를 보여줍니다.


Dart              <--->  RxDart

Stream            <---> Observable

StreamController  <---> Subject


방금 언급 한 것처럼 RxDart는 Dart Streams API를 확장 하고

StreamController 의 3 가지 주요 변형을 제공합니다.


PublishSubject 

    PublishSubject 는 일반적인 브로드캐스트 StreamController 입니다.

    한 가지 다른점이 있는데 스트림 은 Stream이 아닌 Observable을 반환합니다.


하나의 예외를 제외하고는 일반적인 방송 StreamController와 정확히 같습니다. stream은 Stream 대신 Observable을 반환합니다.


이 Subject는 데이터, 오류 및 완료 이벤트를 수신기로 보낼 수있게합니다.


PublishSubject는 Rx Subject 계약을 이행하기 위해 기본적으로 브로드 캐스트 (일명 핫) 컨트롤러입니다. 이것은 피사체의 스트림을 여러 번들을 수 있음을 의미합니다.


BehaviorSubject

    BehaviorSubject는 스트림이 아닌 Observable 을 반환하는 브로드캐스트 StreamController 입니다.



ReplaySubject

    ReplaySubject 는 스트림이 아닌 Observable 을 반환하는 브로드 캐스트 StreamController 입니다.



참고 자료 : 더 이상 필요하지 않은 자원을 항상 해제하는 것은 매우 좋은 습관입니다.



이 성명은 다음에 적용됩니다.


StreamSubscription - 더 이상 스트림 을 청취 할 필요가없는 경우 구독을 취소(cancel) 하십시오.

StreamController - 더 이상 StreamController 가 필요하지 않으면 닫습니다. (close)

RavDart Subject 에도 동일하게 적용됩니다. BehaviourSubject , PublishSubject ...가 더 이상 필요하지 않으면 닫습니다.(close)



스트림으로 출력되는 데이터를 기반으로하는 위젯만들기


Flutter는 StreamBuilder 라는 매우 편리한 StatefulWidget을 제공합니다.

StreamBuilder 는 스트림을 수신하고 일부 데이터가 Stream을 나갈 때마다 자동으로 다시 작성되어 빌더 콜백을 호출합니다.

다음은 StreamBuilder 를 사용하는 방법입니다 .


StreamBuilder<T>(

   key: …optional, the unique ID of this Widget…

   stream: …the stream to listen to…

   initialData: …any initial data, in case the stream would initially be empty…

   builder: (BuildContext context, AsyncSnapshot<T> snapshot){

      if (snapshot.hasData){

         return …the Widget to be built based on snapshot.data

      }

      return …the Widget to be built if no data is available

   },

)


다음 예제에서는 기본 " 카운터 "응용 프로그램을 모방했지만 

Stream을 사용 하고 setState를 사용하지 않습니다.


페이지를 StatefullWidget으로 만든이유는 dispose 메소드에서 StreamController를 릴리즈 해야하기 때문입니다.


[전체소스]

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

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CounterPage(),
);
}
}

class CounterPage extends StatefulWidget {
@override
CounterPageState createState() => CounterPageState();
}

class CounterPageState extends State<CounterPage> {
int _counter = 0;
final StreamController<int> _streamController = StreamController<int>();

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

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Stream version of the Counter App")),
body: Center(
//스트림을 청취하고 있다가 새로운 값이 이 스트림으로부터 나오면 그 값으로 텍스트가 업데이트 됩니다.
child: StreamBuilder<int>(
stream: _streamController.stream,
initialData: _counter,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Text("Yout hit me ${snapshot.data} times");
},
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_streamController.sink.add(++_counter);
},
),
);
}
}





리액티브 프로그래밍이란 무엇입니까?

리액티브 프로그래밍은 비동기 데이터 스트림을 사용한 프로그래밍입니다.


즉, 이벤트 (예 : 탭), 변수 변경, 메시지, 요청 작성 등과 같은 모든 것이 변경되거나 발생할 수있는 모든 사항이 전달되고 데이터 스트림에 의해 트리거됩니다.

리액티브 프로그래밍으로 하면

앱은 비동기가됩니다.

앱은 스트림 및 청취자의 개념을 중심으로 구조화되어집니다.

어딘가에서 어떤 일이 발생하면 (이벤트, 변수의 변경 ...) 통지가 스트림으로 보내집니다.

" 누군가 "가 해당 스트림을 청취하면 해당 응용 프로그램의 위치와 상관없이 알리고 적절한 조치를 취 합니다 .


더 이상 구성 요소간에 긴밀한 결합이 없습니다.

즉, 위젯 이 스트림에 무엇인가를 보내면 그 위젯 은 더 이상 알 필요가 없습니다 

그 위젯은 자신의 일만 케어한면됩니다.

이것은 통제가 없는 것처럼 보이지만 그러지 않습니다.


이로써

특정 활동에만 책임이있는 애플리케이션의 일부를 구축 할 수있는 기회와 

좀 더 완벽한 테스트 커버리지를 허용하기 위해 일부 컴포넌트의 동작을 쉽게 모의하기 위해,

구성 요소를 다른 응용 프로그램이나 다른 응용 프로그램에서 쉽게 재사용 할 수 있습니다.

응용 프로그램을 재 설계하고 너무 많은 리펙토링없이 구성 요소를 한 위치에서 다른 위치로 이동할 수있게하려면,


BLoC 패턴

BLoC 패턴 은 Google의 Paolo Soares 와 Cong Hui 가 디자인했으며 DartConf 2018 ( 2018 년 1 월 23-24 일)에 처음 발표되었습니다

BLoC stands for Business Logic Component.


비즈니스 로직 은 다음을 필요로합니다.


하나 또는 여러 BLoC 로 이동,

비지니스로직은 가능하면 프레젠테이션 레이어에서 제거해야합니다. UI 컴포넌트는 비지니스가 아니로 UI만 걱정하면됩니다.

입력 ( Sink )과 출력 ( Stream )하는데 Streams을 사용해야 합니다.

플랫폼 독립성 유지,

환경에 독립적 이다.


BLoC 패턴은 처음에는 웹 애플리케이션, 모바일 애플리케이션, 백엔드와 같이 플랫폼과 독립적으로 동일한 코드를 재사용 할 수 있도록 설계되었습니다.


그것은 실제로 무엇을 의미합니까?


BLoC 패턴은 위에서 언급 한 개념을 사용합니다 : 스트림 .



위젯은 Sinks를 통해 BLoC로 이벤트 를 보내며,

위젯은 스트림을 통해 BLoC에 의해 통지되며,

BLoC에 의해 구현되는 비즈니스 로직은 그다지 관심사가 아닙니다.

이 성명을 통해 우리는 큰 이익을 직접 볼 수 있습니다.



비즈니스 로직을 UI에서 분리함으로써

* 우리는 응용 프로그램에 미치는 영향을 최소화하면서 언제든지 비즈니스 로직을 변경할 수 있습니다.

* 비즈니스 로직에 영향을주지 않고 UI를 변경할 수 있습니다.

* 비즈니스 로직을 테스트하는 것이 훨씬 쉬워졌습니다.



이 BLoC 패턴을 카운터 애플리케이션 샘플에 어떻게 적용합니까?


 카운터 애플리케이션에 BLoC 패턴을 적용하기


흠..이후로는... flutter_bloc라는 자기가 만든 라이브러리를 사용하는 듯...