RxDart는 Dart의 Stream API를 기반으로 설계되어 있다.
RxDart의 Subject는 내부적으로 StreamController를 사용하고 있어
RxDart를 사용하다보면 Stream 확장판 쓰는 느낌..
기존 Stream API와 연계하여 사용하기 너무 편하고 StreamBuilder로 매끄럽게 UI 렌더링 로직으로 이어진다.
Rx의 강력함을 설명할 겸, Stream과 RxDart의 미묘한 차이를 파악할 겸
로그인 기능을 각각 Stream API와 RxDart로 구현해보면서 비교해보고자 한다.
구현하고자 하는 기능은 간단하다. 아이디와 비밀번호를 입력 받고,
각각의 유효성 검사를 진행하여 로그인 버튼을 활성화 여부를 결정하게 만드는 기능이다.
1. LoginBloc (기본 Stream 버전)
import 'dart:async';
class LoginBloc {
final _idController = StreamController<String>.broadcast();
final _passwordController = StreamController<String>.broadcast();
Stream<String> get idStream => _idController.stream;
Stream<String> get passwordStream => _passwordController.stream;
void setId(String id) {
_idController.sink.add(id);
}
void setPassword(String password) {
_passwordController.sink.add(password);
}
bool _validateId(String id) {
return id.isNotEmpty && id.length > 3;
}
bool _validatePassword(String password) {
return password.isNotEmpty && password.length > 5;
}
Stream<bool> get isValid {
late String latestId;
late String latestPassword;
bool hasId = false;
bool hasPassword = false;
return Stream<bool>.multi((controller) {
idStream.listen((id) {
latestId = id;
hasId = true;
if (hasPassword) {
controller
.add(_validateId(latestId) && _validatePassword(latestPassword));
}
});
passwordStream.listen((password) {
latestPassword = password;
hasPassword = true;
if (hasId) {
controller
.add(_validateId(latestId) && _validatePassword(latestPassword));
}
});
});
}
void dispose() {
_idController.close();
_passwordController.close();
}
}
몇 가지 주요한 부분들을 정리해보자면,
1. 여러 곳에서 하나의 StreamController에 구독을 가능하게 하기 위해 StreamController<String>.broadcast()
2. Setter: 아이디와 비밀번호 값을 입력받는 StreamController.sink.add()
3. Getter: 아이디와 비밀번호 값을 방출하는 Stream 반환
4. ID, PW 각각의 유효성 검사 메서드(단순 문자열 길이 체크)
5. ID, PW의 유효성 검사를 모두 진행하여 검사 결과를 방출하는 Stream 반환 메서드
여기서 가장 많은 분량을 차지하는 코드는 유효성 검사 Stream이다.
Stream<T>.multi의 콜백 메서드 내부에서 idStream, passwordStream을 구독하여
입력값이 있을 때마다 유효성 검사를 진행 한 후 그 결과를 방출한다.
2. LoginBloc (RxDart 버전)
import 'package:rxdart/rxdart.dart';
class LoginBloc {
final _idSubject = BehaviorSubject<String>();
final _passwordSubject = BehaviorSubject<String>();
Stream<String> get idStream => _idSubject.stream;
Stream<String> get passwordStream => _passwordSubject.stream;
void setId(String id) {
_idSubject.add(id);
}
void setPassword(String password) {
_passwordSubject.add(password);
}
bool _validateId(String id) {
return id.isNotEmpty && id.length > 3;
}
bool _validatePassword(String password) {
return password.isNotEmpty && password.length > 5;
}
Stream<bool> get isValid =>
Rx.combineLatest2(idStream, passwordStream, (id, password) {
return _validateId(id) && _validatePassword(password);
});
void dispose() {
_idSubject.close();
_passwordSubject.close();
}
}
몇 가지 주요 변화를 살펴보면,
1. StreamController 대신 BehaviorSubject
2. isValid 메서드 안에서 호출되는 Rx.combineLatest2 라는 스태틱 메서드
Stream API로 직접 구현했던 isValid 메서드의 실행문과 현저한 코드 양과 가독성의 차이를 볼 수 있다.
이거시 Rx의 강력함..
combineLatest는 Rx에서 지원하는 기능으로 두 Stream이 모두 이벤트를 방출할 경우에만 이벤트가 발생된다.
자세한 설명은 문서를 통해..
https://reactivex.io/documentation/operators/combinelatest.html
ReactiveX - CombineLatest operator
RxJS implements this operator as combineLatest. It may take a variable number of individual Observables (as well as the combining function) as parameters, or a single Array of Observables (as well as the combining function). Sample Code /* Have staggering
reactivex.io
여하튼 이렇게 Stream 의 흐름들을 효과적으로 조합하고 관리하고 제어할 수 있도록 돕는 이 부분이 Rx를 쓰는 이유.
반응형 프로그래밍 짱짱맨..!