본문 바로가기
삽질하는 개발자 hashblown

프론트엔드 개발자의 TIL #018

by hashblown 2019. 11. 26.

TIL #018

191126 화

 


오늘 배운 점

<Flutter>

1. Streams - asynchronous Iterable

  • Stream은 Future와 달리 단일 값이 아닌 시간이 지남에 따라 다수의 값이나 에러를 전달할 수 있다. 
Sync: int Iterator<int>
Async: Future<int> Stream<int>
  • 여기서 Iterator은 객체 지향 프로그래밍에서 배열같은 자료 구조 내부 요소를 순화(traversing)하는 객체다. 컬렉션에 저장되어 있는 요소들을 읽어오는 표준화된 방법 중의 하나다. Future와 Stream 모두 사전에 데이터가 준비가 된 경우와 오류가 발생하는 경우에 처리해야 할 작업을 정해놓는다. 
//단일 subscription
final myStream = NumberCreator().stream;

//broadcast stream
final myStream = NumberCreator()
	.stream
    .asBroadcastStream;

final subscription = myStream.listen(
  (data) { print('Data: $data'),
  },
  onError: (err) {
    print('Error!');
  },
  cancelOnError: false,
  onDone: () {
    print('Done!');
  },
);

//broadcast stream인 경우에만 가능
final subscription2 = myStream.listen(
  (data) => print('Data again: $data'),
);

  • 초당 새로운 정수를 내보내는 stream을 예로 든 코드다. 이 stream을 받아보기 위해 listen 메서드를 사용할 수 있다. 여기에 함수를 도입하면 새로운 값이 들어올 때마다 call된다. 
  • Stream은 기본적으로 단일 subscription으로 설정되어 있다. 다른 이가 사용할 때까지 자기 값을 가지고 있다가 한 번 사용되면 끝이다. 두 번 이상 시도하면 exception이 출력된다. 다수의 listener가 필요한 상황에서는 asBroadcastStream으로 stream의 종류를 설정해주어야 한다.
  • Stream에서는 onError 메서드를 추가하면 어떤 오류든 찾아서 처리할 수 있다. cancelOnError 메서드를 false로 설정해두면 오류가 있어도 subscription을 지속할 수 있게 만들 수 있다. onDone 메서드를 사용하면 stream이 데이터 전송을 마친 후에 실행할 코드를 입력할 수 있다.
subscription.pause() // 데이터의 흐름을 중지
subscription.resume() // 데이터의 흐름을 다시 시작
subscription.cancel() // 데이터의 흐름을 취소
  • subscription 자체적으로도 위와 같이 유용한 기능이 많다.
NumberCreator().stream
  .where((i) => i % 2 == 0)
  .map((i) => 'String $i')
  .listen(print);
  • Stream의 가장 큰 특징은 stream에 들어온 데이터는 마음대로 조작할 수 있다는 것이다. map을 통해 stream에서 각 값을 가져와 변환할 수 있다. map에 함수를 주면 그 함수의 반환값과 일치하는 새로운 stream을 내보내준다. 아래 예시의 경우 처음에 int 형태의 stream을 가지지만, map을 거치게 되면 string 형태의 stream으로 변환된다. print 함수가 포함된 listen 요청을 마지막에 추가하면 비동기적으로 도착할 때마다 string 형태의 stream을 출력하게 된다.
  • 짝수만 출력하고 싶은 경우 where을 통해 stream을 필터링할 수 있다. 각 요소에 boolean을 내보내는 테스트 함수를 대입하면 그 테스트를 통과하는 새로운 stream을 내보내주는 것이다.
myReduxStore.onChange
  .map((s) => MyViewModel(s))
  .distinct()
  .listen( /* update UI */ )
  • 또 다른 좋은 기능으로는 distinct가 있다. 아래 코드의 ReduxStore를 사용하는 앱을 예로 들면, onChange stream을 통해 새로운 앱의 state 객체를 내보낼 수 있다.
  • map을 통해 특정 부분에서 앱의 state 객체를 ViewModel의 stream으로 전환할 수 있다. 그리고 distinct 메서드를 사용하여 연속적으로 동일한 값을 필터링하는 stream을 얻을 수 있다. 이런 필터링은 onChange stream이 ViewModel의 데이터에 영향을 주지 않는 변화를 내보내는 경우에 유용하게 쓰일 수 있다. 그 후 새로운 ViewModel을 얻을 때마다 UI가 업데이트하는 listen을 추가할 수 있다.
class NumberCreator {
  NumberCreator() {
    Timer.periodic(Duration(seconds: 1), (t) {
      _controller.sink.add(_count);
      _count++;
    });
  }
  
  var _count = 1;
  
  final _controller = StreamController<int>();
  
  Stream<int> get Stream => _controller.stream;
}
  • 나만의 stream을 만들 수도 있다. 위 코드를 보면 Timer를 사용하여 매초 _count를 증가시킨다. StreamController를 통해 완전히 새로운 stream을 만들어내서 stream의 양쪽 끝, 즉 데이터가 도착하는 지점(stream end, 지금까지 다룬 stream은 이 지점과 관련되어 있다)과 추가되는 지점(sink)에 접근할 수 있다. Timer가 울리면 controller의 sink에 최종 _count를 추가하고, 다른 객체들이 이를 사용(subscribe)할 수 있도록 public 속성을 가진 controller의 stream을 노출시킨다.
StreamBuilder<String>(
  stream: NumberCreator().stream
    .map((i) => 'String $i'),
  builder: (context, snapshot) {
    if(snapshot.connectionState == ConnectionState.waiting) {
      return Text('No data yet.');
    } else if (snapshot.connectionState == ConnectionState.done) {
      return Text('Done!');
    } else if (sanpshot.hasError) {
      return Text('Error!');
    } else {
      return Text(sanshot.data ?? '');
    }
  }
)
  • Flutter에서 위젯을 만들 때 이를 어떻게 활용할까? FutureBuilder와 유사한 형태의 StreamBuilder를 사용하면 된다. stream을 주고, builder 메서드를 사용하면 stream에 새로운 값이 도착할 때마다 자식 위젯을 빌드한다. snapshot은 역시 비동기 형태인데, connectionState를 통해 stream이 아직 데이터를 보내지 않았는지, 완전히 전송을 마쳤는지 확인할 수 있다. hasError로 에러가 있는지 확인하고, data를 처리할 수 있다.

https://dart.dev/tutorials/language/streams


내일 배울 점

<Flutter>

1. async / await

2. 쇼핑몰 화면 구현


더보기
- 계절학기 수강신청을 했다.. 종강하고도 할 일이 차곡차곡!

 

댓글