안드로이드/Rxandroid

Rx로 EventBus를 만들기 위한 준비물

깃허브

  • 각종 라이브러리
    * RxLifeCycle 사용안하면,, 버스안에 정보가 계속 남아서 곤란하다.
    compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
    }

    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    implementation 'io.reactivex.rxjava2:rxjava:2.x.x'
    implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
    implementation 'com.trello.rxlifecycle2:rxlifecycle-android:2.2.1'
    implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.1'

RxEventBus 만들기

image

  • 먼저, RxBus클래스를 만들어야한다. 어디든지 사용할 수 있게, public class로 만든다.
    - 싱글톤 패턴으로 사용하기 위해서, getInstance()메소드가 public static( 클래스의 것)으로 생성된다.
    - 중개자로서, behaviorSubject가 사용되었다. ( Subject는 Observable과 Observer의 역할을 모두 수행할 수 있다.)
      *검색시 예제는 PublishSubject가 많다. 하지만, 구독시점에서 첫번째 아이템이 안넘어온다는 단점이 있다.
      *BehaviorSubject는 구독시점에서 구독직전의 최근 정보를 포함해서 발행하므로, Bus로 쓰기 알맞은 것 같다.
       *BehaviorSubject의 타입은 <Object>다. 모든 객체의 조상이기 때문에, String부터 시작해서 내가 정의한 모델객체도 받을 수 있다.
    - sendBus( Object ) 메소드는 object를 인자로 받아서, 중개자인 subject의 변수를 이용하여, .onNext( object)로 item을 중간에서 간직하고 있다.
    - getBus() 메소드는  sendBus-> BehaviorSubject가 간직하고 있는 item을 받아서 return해준다.
    public class RxBus {

    private static RxBus mInstance;
    private BehaviorSubject<Object> mSubject;

    private RxBus() {
    mSubject = BehaviorSubject.create();
    }

    public static RxBus getInstance() {
    if (mInstance == null) {
    mInstance = new RxBus();
    }
    return mInstance;
    }

    public void sendBus(Object object) {
    mSubject.onNext(object);
    }

    public Observable<Object> getBus() {
    return mSubject;
    }
    }

  • 이 때, 주의해야할 것은,
    (1) BehaviorSubject의 < Type > - 중간에서 간직해주는<Generic>의  item의 타입
    (2) sendBus( )의 ( parameter)   - 보내는 인자의 타입
    (3) getBus()의 return Observable< Type > - 받아서 return 해주는 타입
    3개가 모두 동일한 <Type>이어야만 한다.

  • 메인액티비티에서, 버튼 클릭시, RxBus의 인스턴스를 생성하고 ->  editText에 있는 String을  sendBus( str )로 보내고,
    메인2 액티비티에서, RxBus의 인스턴스를 생성한 뒤 ->  getBus() 로 받아서 출력시켜보자.
    * 이때, 모두 Rx 액티비티를 상속하여, compose()로 라이프사이클이 자동으로 적용되도록 해야, 버스가 계속 잘 굴러간다.
     ****** getBus()로 받을 때, if( 변수  instance of 자료형 ) 으로 알맞은 곳으로 자료를 전달했는지 확인해주자.
    //에디트텍스트의 스트링만 보내기 + onComplete는 안하더라도 onError처리를 해줘야 앱이 안죽는다.
    RxView.clicks(findViewById(R.id.btn_send))
    .compose(bindToLifecycle())
    .subscribe( e -> {

    RxBus.getInstance().sendBus(((TextView)findViewById(R.id.txt_edit)).getText().toString());

    Intent intent = new Intent(this, Main2Activity.class);
    startActivity(intent);
    }
    , Throwable::printStackTrace) ;

    RxBus.getInstance().getBus()
    .compose(bindToLifecycle())
    .subscribe(str -> {
    if (str instanceof String) { //넘어오는 자료구조확인 후 받기
    ((TextView) findViewById(R.id.txt_result)).setText(str.toString());

    } else Toast.makeText(this, "잘못된 선택입니다", Toast.LENGTH_SHORT).show();
    });

    녹화_2018_05_02_21_43_13_158

  • 메인액티비티에서, 버튼 클릭시, RxBus의 인스턴스를 생성하고 ->  sendBus(  )의 인자new Product라는 모델객체를 하나 생성해서 보내고
    메인3 액티비티에서, RxBus의 인스턴스를 생성한 뒤 ->  getBus() 로 받아서 출력시켜보자
    * 이때, 모두 Rx 액티비티를 상속하여, compose()로 라이프사이클이 자동으로 적용되도록 해야, 버스가 계속 잘 굴러간다.
     ****** getBus()로 받을 때, if( 변수 instance of 자료형 ) 으로 알맞은 곳으로 자료를 전달했는지 확인해주자.
    //Product라는 모델보내기 + onComplete는 안하더라도 onError처리를 해줘야 앱이 안죽는다.
    RxView.clicks(findViewById(R.id.btn_model))
    .compose(bindToLifecycle())
    .subscribe( e -> {

    RxBus.getInstance().sendBus( new Product(1234,"한국이름", "영어이름", "#1" ));

    Intent intent = new Intent(this, Main3Activity.class);
    startActivity(intent);
    }
    , Throwable::printStackTrace) ;

    RxBus.getInstance().getBus()
    .compose(bindToLifecycle())
    .map(object-> (Product)object) //객체를 넘길때는, map에서 캐스팅으로 형변환해주기 ㅠㅠ 발견!
    .subscribe( p -> {
    if( p instanceof Product ) { //넘어오는 자료구조확인 후 받기

    ((TextView) findViewById(R.id.txt_result)).setText("모델명 : " + p.getResId() + "\n" + "한국이름 : " + p.getmKorea() + "\n" + "영어이름 : " + p.getmEng() + "\n" + "넘버 : " + p.getmNumber() + "\n");

    }else Toast.makeText(this, "잘못된 선택입니다", Toast.LENGTH_SHORT).show();
    });

    녹화_2018_05_02_21_47_25_88


깃허브

레트로핏 사용에 앞서서 준비물

  • 파싱할 Json을 뿌려주는 주소 : http://bis.naju.go.kr:8080/json/arriveAppInfo?BUSSTOP_ID=410 
  • Json 자료를 예쁘게 펴주는 사이트 : http://json.parser.online.fr/
    image
  • Json 을 java 클래스로 만들어주는 사이트 : http://www.jsonschema2pojo.org/
    - java / json / use double numbers + include getters and setters 만 선택
    - Preview 버튼 -> Copy to Clip보드
    - 안드로이드 스튜디오에서 Bus.java 클래스를 만든 다음, 전체 코드 붙혀넣기
    - 점선 및, 따로 떨어져있는 클래스( json 내에서 키값에 다시 배열이 들어간 것들 RESULT, BUSSTOPLIST)를
       BUS클래스의 내부클래스로 옮겨주기
    image
    image

  • Rxjava2, RxAndroid, RxBinding, 람다식 사용을 위한 라이브러리 추가

  • compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
    }
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    implementation 'io.reactivex.rxjava2:rxjava:2.x.x'
    implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
    implementation 'com.trello.rxlifecycle2:rxlifecycle-android:2.2.1'
    implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.1'
  • Rxbinding사용을 위한 Activity 상속

  • public class MainActivity extends RxAppCompatActivity {
  • Retrofi2 라이브러리  / Retrofit2 - Rxjava2 연결 어댑터 / Json파싱을 위한 Gson 라이브러리 추가

  • implementation 'com.squareup.retrofit2:retrofit:2.1.0'
    implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.1.0'


Retrofit2 와 Rxjava2를 이용한 버스정류장 정보 파싱해오기

  • 파싱을 할 액티비티(여기서는 MainActivity)에서는 먼저, 사용할 editText 및 버튼, TextView의 변수를 미리 선언하고,
    retrofit2의 클라이언트에 들어갈 BASE_URL을 static final 상수로 선언해준다.
    *이 때, 파싱할 풀 url을 주석으로 적어놓자.
    image

  • 클릭시 Retrofit2 클라이언트를 이용해서 파싱해주는 함수(getBus()) 를 만든 뒤,
    RxView.clicks를 이용하여 버튼과 함수를 바인딩 한다.
    image

  • 이제 Retrofit2 클라이언트를 만들어줄, interface를 생성해야한다. *여기서도 full url을 주석으로 표시해두자.

    /*
    http://bis.naju.go.kr:8080/json/arriveAppInfo?BUSSTOP_ID=1439
    */
    위의 주소 중, 전체 메인 도메인에 해당하는 http://bis.naju.go.kr:8080/ 까지만 base_url로 삼았다.
    그 뒤는 인터페이스에서, 물음표(?)뒤에 커리를 이용해서 바꿔주는 곳이다.

    image
    full url을 살펴보면, ?(물음표) 뒤쪽의 상수(BUSSTOP_ID)가 @Query문에 해당한다.
    그 다음, 1439는 쿼리문에 대한 요청상수로서,  변하는 것이기 때문에, String busstop_id라는 변수로 두어, getData()함수의 파라미터가 되어
    호출하는 곳에서 넘어올 것이다.

  • 이제 다시 getBus()함수로 가보자.
    1. 먼저, retrofit2 클라이언트를 생성할 때, getData( )의 파라미터로 들어갈 쿼리문의 요청상수를 받아야하므로, 해당 변수를 생성해준다.
    2. 그리고 retrofit client를 생성한다.
        * 이 때, Gson컨버터와 Rxjava2콜 어댑터를 라이브러리에서 받아 설정해주게 된다.
    3. 생성된 retrofit client + 위에서 만든 retrofit2 인터페이스를 이용하여 Retrofit2  Api서비스를 생성한다.
      // 1. Retrofit 클라이언트 생성
      Retrofit client = new Retrofit.Builder()
      .baseUrl(BASE_URL)
      .addConverterFactory(GsonConverterFactory.create())
      .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
      .build();

      // 2. RestAPi 서비스 생성
      Retrofit2 service = client.create(Retrofit2.class);
    4. 마지막으로 retrofit2 서비스를 이용하여, 인터페이스 속에 있는 getData( 요청상수 변수)를 호출하면서,
        그것을 Json를 받아줄 자료형 <Bus>클래스 타입의 옵져버블에 할당한다.
      Observable<Bus> BusData = service.getData( busstop_Id );

    5. 나는 버스정보를 받아와서 5초마다 갱신시켜주도록 할 것이다. 그러기 위해선 따로 옵져버블.interval을 요청해야한다.
      rx interval에 대한 이미지 검색결과
      이 때, 애먹었던 점은, interval이라는 연산자는 파라미터로 ( 초기딜레이(0), 간격시간period(5), 단위(TimeUnit.SECONDS))를 받는데
       return값이 0부터 1, 2,,, 숫자(Observable<Long>를 내뱉는 것이다.
      - 숫자를,, 위에서 생성한 --> Bus타입의 옵져버블로 대체하기 위해서 flatmap을 썼다. **************
        이렇게 되면, 5초 간격으로 Bus타입의 옵져버블을 방출시키게 된다.
      Observable.interval(0,5, TimeUnit.SECONDS)
      .flatMap(n-> BusData) //5초마다 갱신해주는 인터벌을 넣고 리턴은 숫자니까 -> 미리 만들어준 BusData 옵져버블로 형변환

    6. 생성된 옵져버블의 쓰레드를 설정해준다.
      - 옵져버블이 옵져버에게 비동기로 stream을 흘려보내 줄 것이므로, 시작 쓰레드(subscribeOn)은 따로 만들어준다.
      * 아직 스레드 공부가 덜 됬지만, 새로운 쓰레드인 Schedulers.newThread()를 이용해서 받을 것이다.
      - UI에 자료를 뿌려줄 것 이므로, 옵져버 쓰레드(observeOn)를 UI 수정을 위해, 안드로이드 메인 쓰레드에서 작업하게 만들어 준다.
      - 항상 옵져버블에 RxLifeCylcle 라이브러리를 이용하여, 자동으로 unsubscribe하도록 .compose(bindToLifecycle()) 를 붙혀준다.
        * 만약 라이프사이클 안쓰면,, 백버튼 시에도 계속 갱신하고 있다.. 못삼...

      .subscribeOn(Schedulers.newThread())
      .observeOn(AndroidSchedulers.mainThread())
      .compose(bindToLifecycle())

    7. 이제 5초마다 방출되는 <Bus>타입의 옵져버블 BusData를 subscribe에서 방출시켜보자.
      - 친절하게도, 내가 가져올 json데이터에는 RESULTCODE가 있었다. 이것을 if문으로 equals("SUCCESS")시에  옵져버블을 뜯어 볼 것이다.
      - 주로 뜯어볼 데이터는 json속 배열을 가지는 BUSSTOPLIST이다. pojo클래스를 잘 생성했다면, 그 안의 요소를 가져오는 get함수가 만들어져 있다.
        (1)  item-> item.getBUSSTOPLIST()를  BUSSTOPLIST 전체 리스트를 받을 수 있고
        (2)  각 LIST를 각 요소( BUSSTOPLIST  -  ARRIVE , REMAIN_MIN, BUSSTOPNAME )을 가져오기 위해서
        다시한번 Observable.fromIterable***전체리스트 ->  각 리스트들을 분리해서 다룰 수 있어야한다.
        * Observable.fromIterable -> ArrayList or LIst를 쪼갬 / Observable.fromArray -> Array를 쪼갬
        (3) Observable.fromIterable에 의해 각 리스트를 다룰 수 있게 되어, subscribe를 통해  각 요소를 가져올 수 있다.

      .subscribe( item->
      {
      if (item.getRESULT().getRESULTCODE().equals("SUCCESS")) {
      Toast.makeText(this, "버스도착정보가 있음", Toast.LENGTH_SHORT).show();
      StringBuffer bf = new StringBuffer();


      Observable.fromIterable( item.getBUSSTOPLIST() ) //json속 배열을 가진 놈은 fromIterable로 쪼개어서 쓴다!!
      .subscribe(list ->
      {
      bf. append("<< "+list.getLINENAME().toString()+" >> 버스가 \n" );
      bf. append("도착까지" + list.getREMAINMIN().toString()+"분 ("+list.getREMAINSTOP().toString()+"개 정류장) 남았어요\n" );
      bf. append("현재버스 위치는 " + list.getBUSSTOPNAME().toString()+"정류장 이에요.\n" );

      ((TextView) findViewById(R.id.txt_result2)).setText(bf);
      }
      );
      } else ((TextView) findViewById(R.id.txt_result2)).setText("버스도착정보가 없음");
      }
      , e-> Toast.makeText(this, "인터넷이 연결되지 않았어요", Toast.LENGTH_SHORT).show()
      , ()->{ Toast.makeText(this, "정상적으로 버스정보를 가져왔습니다.", Toast.LENGTH_SHORT).show(); }
      );
    8. subscribe시, 인터넷이 안되는 경우를 대비해서, onError자리까지 대비해줘야한다.(앱이 꺼진다)


앞에서 안나왔던 연산자들 소개

깃허브

  • concatMap() 함수 : 먼저 들어온 데이터 순서대로 처리를 보장하는 함수
  • flatMap() 함수 : 달리 순서를 보장해주지 않습니다.
  • switchMap() 함수 : 순서를 보장하지만 중간에 발행이 되면 기존에 진행 중이던 작업을 바로 중단하고 그때 들어온 값으로 처리
  • groupBy() 함수 : 여러개의 단일 Observable을 그룹으로 묶어주는 함수
  • scan() 함수 : 실행할때마다 입력값에 맞는 중간 결과 및 최종 결과를 구독자에게 발행
  • zip() 함수 : 2개 이상의 Observable을 결합할 때 사용합니다. 만약 한쪽의 Observable에서 처리가 안된다면 모두 처리될때까지 발행을 대기
  • combineLatest() 함수 : 2개 이상의 Observable을 기반으로 Observable 각각의 값이 변경되었을 때 갱신해주는 함수
  • merge() 함수 : 2개의 옵져버블을 결합시켜서 한번에 방출시키더라도, 먼저오는 놈은 바로 발행시킨다.
  • concat() 함수 : 2개 이상의 Observable을 이어 붙여주는 함수이고, 첫번째 Observable에 onComplete 가 발생해야, 두 번째 Observable을 구독


그림을 보는 방법은,, 마지막 결과물에서 수직선을 그어서 현재 상황을 보자.
첫번재 1,1 경우, 첫번재 옵져버블은 진행중, 두번째는 완료된 상태이므로,   첫번째 옵져버블의 최신(직전)값을 가져와서 묶어준다.

rx combinelatest에 대한 이미지 검색결과

combineLatest()는 구독하는 시점에서,  A옵져버블과 B옵져버블 각각의 최신 item을 가져와서 묶어준다.

zip 한쪽의 Observable에서 처리가 안된다면, <직전값, 이전값이 아닌> 모두 처리될때까지 기다렸다가 묶어주고 발행.
combineLastest()는 말그대로, 해당 시점에서 각각의 옵져버블 중 가장 최근 것 <처리안됬다면, 직전값 >을 가져와서 묶어주고 발행


RxBinding과 combineLastest를 이용한 로그인 버튼 활성여부 결정하기 (버튼을 활성화 시키냐 안시키느냐)

생성된 버튼 변수를 이용해서,  2개의 텍스트뷰를 바인딩-> 2개의 옵져버블로 만들어,  combineLastest()를 사용하여

Button btnLogin = findViewById(R.id.btn_login); //활성화 or 비활성화 옵션을 넣기 위해서, 이번에는 버튼 변수를 따로 만든다.
btnLogin.setEnabled(false); //먼저 버튼을 비활성화 해놓는다. (조건 만족시 활성화하는 코드가 들어간다)

// id와 pass가 적히는 에디트뷰에 대해서, RxTextView.textChangeEvents를 binding한 뒤 -> 각 옵져버블<TextViewTextChangeEvent>타입으로 할당해준다.
Observable<TextViewTextChangeEvent> idObs
= RxTextView.textChangeEvents(findViewById(R.id.txt_login));
Observable<TextViewTextChangeEvent> passObs
= RxTextView.textChangeEvents(findViewById(R.id.txt_pass));

//id와 pass가 바인딩된 옵져버블에 대해서 combineLatest로 묶어준다. combineLatest는 zip과 다르게, 해당시점에서 가장 최근의 item을 각각 가져와서 합친다.
Observable.combineLatest(idObs,passObs, // 2개의 옵져버블을 받고
(idChanges,passChanges) -> { // 옵져버블고 무관한 2개의 인자 입력(생성이나 마찬가지)
boolean idCheck = idChanges.text().length() >= 8; // 첫번째 id옵져버블의 변화. text()추출 후 , 그 길이가 8이상 일 때, boolean형의 첫번째 인자에게 true를
boolean passCheck = passChanges.text().length() >= 6; // 2번째 pass옵져버블의 변화. text() 추출후 , 그 길이가 6이상 일 때, boolean형의 2번째 인자에게 true를 대입해준다.
return idCheck && passCheck; // 2개의 결과가 모두 true일 때, true를 반환할 수 있도록, boolean인자 2개를 && (and연산자)로 만들어 return해준다.
})
.subscribe( //subscribe에 넘어오는 값은 =true 혹은 false일 것이다.
checkFlag -> btnLogin.setEnabled(checkFlag) // true 로 넘어온다면, 버턴 활성화 인자에 true가 들어가서 활성화가 될 것이다.
);
  • 먼저 RxView.clicks가 아니라, 버튼 변수 생성과 id참조를 해준다. 그래야만이 .setEnabled(  ) 로 활성화여부를 결정시킬 수 있다.
  • id와 pass가 들어가는 editText에 각각 RxTextView.textchangeEvents를 id를 이용하여 바인딩 시키고
    -> 옵져버블<TextViewTextChangeEvent>에 할당해준다.
  • 2개의 옵져버블을 combineLatest( i1, i2, ( 인자 ) -> { 함수 } ) 를 이용해서,  구독시점에 각각 진행되었던 최신 텍스트 변동정보만 담아준다.
    이 때, 인자에는 옵져버블 item과 무관한 boolean 변수 2개가 들어간다.
    -> 함수내용에서는 옵져버블 item이 사용되며, .text()를 이용해서 해당 텍스트의 길이를 추출할 것이다.
  • 각각의 editText에서의 글자수가 8글자, 6글자 이상될 때, true가 되며,
  • return에는  불린1 && 불린2  의 형식을 사용하여, 모두 true일 때,  subscribe에 true가 전달되도록 해준다.
  • subscribe에서는 넘어오는 true(혹은 false)를 이용해서 버튼변수.setEnabled( checkFlag ) 형식으로 checkFlag에 true(혹은 false)가 반영되도록 한다.

- 옵져버블로 연결된 [ 텍스트 변화감지 -> button활성화 ]
   실시간으로 텍스트뷰의 텍스트변화 ---Stream으로 연결된 ---바인딩된 옵져버블의  textChangeEvents가 감지 후
   ---combineLastest()에 의해 --- 실시간정보(true/false)가 버튼(setEnabled)에 반영이 된다.


아이디 7글자 / 비번 5글자는 --> 버튼활성화 안된다.

image


비번은 6글자를 충족시켰지만, 아이디는 7글자라 버튼 활성화 안된다.

image


아이디와 비밀번호 모두 충족시 버튼이 활성화된다.

image

  1. 파파망토 2019.07.11 19:05

    유익한 내용 잘 봤습니다 감사드려요!!

앞에서 안나왔던 연산자들 소개

깃허브

  • concatMap() 함수 : 먼저 들어온 데이터 순서대로 처리를 보장하는 함수
  • flatMap() 함수 : 달리 순서를 보장해주지 않습니다.
  • switchMap() 함수 : 순서를 보장하지만 중간에 발행이 되면 기존에 진행 중이던 작업을 바로 중단하고 그때 들어온 값으로 처리
  • groupBy() 함수 : 여러개의 단일 Observable을 그룹으로 묶어주는 함수
  • scan() 함수 : 실행할때마다 입력값에 맞는 중간 결과 및 최종 결과를 구독자에게 발행
  • zip() 함수 : 2개 이상의 Observable을 결합할 때 사용합니다. 만약 한쪽의 Observable에서 처리가 안된다면 모두 처리될때까지 발행을 대기
  • combineLatest() 함수 : 2개 이상의 Observable을 기반으로 Observable 각각의 값이 변경되었을 때 갱신해주는 함수
  • merge() 함수 : 2개의 옵져버블을 결합시켜서 한번에 방출시키더라도, 먼저오는 놈은 바로 발행시킨다.
  • concat() 함수 : 2개 이상의 Observable을 이어 붙여주는 함수이고, 첫번째 Observable에 onComplete 가 발생해야, 두 번째 Observable을 구독


RxView.clicks로 버튼을 binding (옵져버블에 할당없이, 바로 subscribe)

RxView.clicks를 이용해 버튼을 옵져버블에 Binding시키고,
클릭이벤트(e)를 -> 새로운 랜덤정수를 생성해보았다.
그리고 그 랜덤정수를 subcribe를 통해, textview와 토스트메세지에 띄워보았다.

clicks(findViewById(R.id.btn_bind))
.map(event -> new Random().nextInt())
.subscribe(
rand -> {
((TextView) findViewById(R.id.textView)).setText("BIND버튼 클릭시 랜덤 정수= " + rand);
Toast.makeText(this, "Bind버튼을 눌러 랜덤정수를 생성합니다.", Toast.LENGTH_SHORT).show();
}

);


image


2개의 RxView.clicks ->  2개의 옵져버블에 할당(아직 방출x) -> merge를 통해서 2개의 옵져버블 한꺼번에 방출


Observable<String> leftObs = clicks(findViewById(R.id.btn_Left))
.map(event -> "Merge를 통해 버튼2개에 토스트를 걸었으나, 먼저 온 왼쪽클릭");
Observable<String> rightObs = clicks(findViewById(R.id.btn_Right))
.map(event -> "Merge를 통해 버튼2개에 토스트를 걸었으나, 먼저 온 오른쪽클릭");
Observable.merge(leftObs, rightObs) //merge를 이용해서 2개의 버튼-클릭이벤트를 Binding한 옵져버블을 한꺼번에 토스트 띄우도록 코드를 짠것임. 그러나 merge는 2개 중 먼저온것 방출시킴.
.subscribe(
text -> Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
);
  • left라는 왼쪽버튼과, right라는 오른쪽버튼을 각각 Rxview.clicks를 이용해 바인딩시키고,
  • 옵져버블에 각각 할당한 뒤
  • 2개의 옵져버블을 merge를 이용하여, 한꺼번에 방출시켜 토스트메세지를 띄운다.
    그러나 merge 2개를 한꺼번에 방출하여 토스트메세지를 띄우지만,
    먼저온 item을 바로 방출시키니, 메소드 작동코드만 묶여있을 뿐, 각각 따로 활동하게 된다.
    imageimage


RxTextView를 이용한 텍스트뷰/에디트뷰 바인딩 후 --> textChangeEvents  이용하기

버튼은 RxView.clicks ( fbc(R.id.)) 으로 바인딩 해서, 옵져버블에 할당할 수 있었다.

텍스트뷰나 에디트뷰는 RxTextView. textChangeEvents ( fbc(R.id.)) 으로 바인딩 해야한다.

그리고 방출되는 것은 옵져버블인 것 같다. map()을 통해  word -> word.text() 로 텍스트성분만 빼낼 수 있다.

RxTextView.textChangeEvents(findViewById(R.id.txt_edit))
.map(word->word.text()) // textChangEvents의 return은 text를 포함한 옵져버블인 것 같다. 거기서 .text()를 통해 텍스트만 추출할 수 있었다.
.subscribe(
word -> Toast.makeText(this, "적힌 글 = " + word ,Toast.LENGTH_SHORT).show()
);


imageimage

Subject 클래스들

깃허브


일단, 옵져버블과 같은 개념이긴 하나, Subject는 발행자가 될수도 있으며, 구독자가 될 수 도 있다는 것이 특징임( 그래서 Bus로 사용가능)

어떠한 Subject. create(); 로 만들고나서

subscribe( , , )자리에서 onNext를 하는 것이 아니라
subject.onNext로 데이터를 발행시키고 ---> subscribe( , , )를 통해 구독자에게 전달한다.

정보의 발생이 끝나면 subject.onComplete로 닫아준다.

private void doPublish() {
StringBuffer bf = new StringBuffer();
PublishSubject<String> subject = PublishSubject.create();
subject.onNext("A");
//1번째 구독자
subject.subscribe( item -> {
bf.append( "구독자1 에게 전달된 값 : "+ item +"\n");
((TextView) findViewById(R.id.txt_result)).setText(bf);
}
);
subject.onNext("B");
subject.onNext("C");
subject.onNext("D");
//2번째 구독자
subject.subscribe( item -> {
bf.append( "구독자2 에게 전달된 값 : "+ item +"\n");
((TextView) findViewById(R.id.txt_result)).setText(bf);
}
);
subject.onNext("E");
subject.onNext("F");
subject.onNext("G");
subject.onComplete(); // onComplete 를 써줘야 됨.( 끝이 어딘지 알때 사용 )
}

4개의 Subject들에 있어서,  A/B/C/D/E/F/G의  data를 전달할 것이며,

  • A<-->B 사이에서 1번째 구독자에게 전달
  • D<-->E 사이에서 2번째 구독자에게 전달할 때, 어떠한 차이가 있는지 살펴보자.


PublishSubject 클래스

rx publishsubject에 대한 이미지 검색결과

구독 이후에 발생한 데이터 모두를 구독자에게 전달

  • 구독자1은 B,C,D,E,F,G / 구독자2는 E,F,G  => 구독 이후 데이터를 다 받아낸다.

image


BehaviorSubject 클래스

rx behaviorsubject에 대한 이미지 검색결과

구독하기 직전의 가장최근값 + 구독이후의 모든값 을 발행한다.

  • 구독자1은 B다음에 구독을 했다. 구독직전값인 B 를 포함해서 CDEFG를 받는다.
  • 구독자2는 D다음에 구독을 했다. 구독직전값인 D 를 포함해서 EFG를 받는다.

image

ReplaySubject 클래스

rx replaysubject에 대한 이미지 검색결과

구독자가 새로 생길 때마다, 항상 데이터의 처음부터 끝까지 발행하는 것을 보장한다.

image


AsyncSubject 클래스

rx asyncsubject에 대한 이미지 검색결과

언제 구독을 했건간에 onComplete() 직전의 값만 발행한다.
(만약 onComplete하지 않으면 넘어가는 데이터가 없을 것이다.)

image

toSortedList

깃허브

rx toSortedList에 대한 이미지 검색결과

toSortedList()는 옵져버블을 < 오름차순 정렬 > 시킨다음 단 하나의 Single로 return하는 것이 특징이다.

  • 인자를 안준다면, 오름차순 정렬
  • 인자를 ( ( i1, i2) -> i1.comprateTo(i2) )로 준다면, 오름차순 정렬
  • 인자를 ( ( i1, i2) -> -  i1.comprateTo(i2) ) 로 마이너스를 달고 준다면, 내림차순 정렬된다.

인자를 주지 않은 .toSortedList와  .toSortedList ( ( i1, i2) -> i1.comprateTo(i2) ) 를 준 것과는 동일하게 나타난다.


    private void doSort() {
Observable.fromArray(intList)
.toSortedList()
.subscribe(list -> ((TextView) findViewById(R.id.txt_result)).setText(list.toString()) );
// int sort 1: |[-3, 0, 1, 3, 5, 6, 7]
//toSortedList()는 옵저버블에서 받은 다수의 데이터들을 정렬해 List 로 반환한다. 알파벳 순으로 정렬된다. toSortedList가 반환하는 것은 무조건 하나이다. 옵져버블이 아니라 Single로 리턴되는 것을 알아야한다.!!
}
    private void doSort2() {
Observable.fromArray(intList)
.toSortedList( (i1, i2) -> i1.compareTo(i2) )
.subscribe(list -> ((TextView) findViewById(R.id.txt_result)).setText(list.toString()) );
// int sort 2: |[-3, 0, 1, 3, 5, 6, 7]
}
    private void doSort3() {
Observable.fromArray(intList)
.toSortedList( (i1, i2) -> -i1.compareTo(i2) )
.subscribe(list -> ((TextView) findViewById(R.id.txt_result)).setText(list.toString()) );
// int sort 3: |[7, 6, 5, 3, 1, 0, -3]
}


imageimageimage
3번째는, 인자에 -를 달아서 역순(내림차순으로 정렬한 것)


만약 소문자 + 대문자 + 한글이 섞여있다면,

  • 대문자 > 소문자 > 한글 순으로 오름차순 된다. (대문자가 더 작은 것인가..  한글은 제일 큰 것이라 생각하자...)

imageimageimage


정렬시 Map의 활용

map()은 단순하게 데이터를 변환시킨다. 하지만 맵의 인자에 첫번째 문자를 포함시키도록 배열을 만들 수 있다.

.toMap( it ->it.toCharArray()[0])

첫글자만 배열로 나타나면 좋겠지만..ㅠㅠ 첫글자+해당item이 같이 배열을 만든다.

private void doMap() {
Observable.fromArray(strList)
.toMap( it ->it.toCharArray()[0])
.subscribe(list -> ((TextView) findViewById(R.id.txt_result2)).setText(list.toString()) );
//첫글자를 따로 빼내서, 첫글자를 포함시킨 리스트를 만든다. 첫글자만 나오게는 못하나?;;
}

image



toMultiMap

rx toSortedList에 대한 이미지 검색결과


toMultiMap은 위에 그림과 같이, 인자에 함수를 받아서, 각 item에 해당하는 함수값  = item 의 형식으로 배열을 만들어준다.
그림처럼 Single로 반환되면서, map형식으로 함수값+item값을 포함한 배열을 만들어주는 것 같다.

여기서는 함수로서, String의 length함수를 넣어주고, 그 문자열 길이값 + item으로 배열을 만든 것을 출력해보자.

private void doMultiMap() {
Observable.fromArray(strList)
.toMultimap(String::length)
.subscribe(list -> ((TextView) findViewById(R.id.txt_result2)).setText(list.toString()) );
//toMultimap은 인자로 함수를 받아--> 함수값 + 인자를 배출해준다. 각 item의 글자길이에 따른 item 배열을 구할 수 있다.
}

image

각종 조건연산자+ 변환연산자 함수들을 알아보자.

깃허브

distinct / filter / foreach / groupby / take / first / last


distinct()  :  중복제거

rx distinct에 대한 이미지 검색결과

distinct정렬하지않고, 순서대로 중복을 제외시킨다. 순서로 치면, 첫번째 나온 것만 챙기고, 차후에 나오는 중복 item들은 stream을 타지 않는 것 같다.
2,3,2,1,2,1 로  있다면 fromArray로 쪼갠 옵져버블은  --- distinct()---->  2, 3, x , 1, x , x 로 2, 3 , 1을 뽑아낸다.

private void doDistinct() {
StringBuffer bf = new StringBuffer();
Observable.fromArray(dataSet)
.distinct()
.map( result -> bf. append(result))
.subscribe(
result -> ((TextView) findViewById(R.id.txt_result)).setText(bf.toString())
);
}
  • 먼저, String을 연결시켜주는 StringBuffer와 그 변수를 이용한 .append()를 통해 결과값들을 붙힐 것이다.
  • 어떤 integer배열에 대해서 Observable.fromArray를 이용해 한 item씩 흘려보내고
  • distinct()를 쓰면, 순서대로 중복을 제외시켜서 흘려보내준다.
  • 오늘의 예제들은 StringBuffer변수를 map을 통해, 하나씩 append() 시켜줄 것이다.
  • subcribe를 통해   StringBuffer에 append된 결과들을  textView에 뿌릴 것이다.
    imageimage
    중복되는 가운데의 1인 제외되고, 하나씩 나오는 것을 확인할 수 있다.


foreach

subscribe와 유사한 기능을 하나, onNext만 호출 되고, stream에 데이터를 보내는 과정이 블로킹 되는 함수다.

private void doForeach() {
StringBuffer bf = new StringBuffer();
Observable.fromArray(dataSet)
.map( result -> bf. append(result))
.forEach( result -> ((TextView) findViewById(R.id.txt_result)).setText(bf.toString()) );
}
  • StringBuffer에다가, map을 이용해 한 item씩 붙혀놓는다.
  • foreach를 통해 subscribe처럼 onNext를 실행할 수 있다. 그 결과를 toString()으로 뿌렸다

    imageimage



filter( item -> item을 포함한 조건식 )

rxjava filter에 대한 이미지 검색결과

filter() 함수는 Observable에서 원하는 데이터만 걸러내는 역할을 한다. 형식은 filter( item ->   item이 포함된 조건식 )의 형식이다.


참고) filter와 비슷한 함수들

  • first(default) : Objservable의 첫 번째 값을 필터함, 만약 값이 없으면 기본값을 리턴
  • last(default) : Objservable의 마지막 값을 필터함, 만약 값이 없으면 기본값을 리턴
    - first와 last는 값이 없는 경우 null을 배출하는 경향이 있어서, take와 takeLast를 적절하기 운용하면 된다.
  • take(N) : 최초 N개 값을 가져옴
  • takeLast(N) : 마지막 N개 값을 가져옴
  • skip(N) : 최초 N개 값을 건너뜀
  • skipLast(N) : 마지막 N개 값을 건너뛴다.


private void doFilter() {
StringBuffer bf = new StringBuffer();

Observable.fromArray(dataSet)
.filter(item -> item%2==0)
.map( result -> bf. append(result))
.subscribe(
result -> ((TextView) findViewById(R.id.txt_result)).setText(bf.toString())
);

}

  • 필터를 사용하여, 조건식을 2의배수를 주었다. 그에 해당하는 짝수의 item만 흘러나올 것이다.
  • map을 통해, StringBuffer에 하나씩 붙혔고, 그결과를 textview에 toString으로 뿌렸다.

    imageimage



filter에다가 String 마지막에 포함하는 문자열을 골라내는 함수인, endsWith(" 문자열 " ) 을 이용한 것을 예로 보자.
배열은 아래와 같았다.

String[] objs = {"1 CIRCLE", "2 DIAMOND", "3 TRIANGLE", "4 DIAMOND", "5 CIRCLE", "6 HEXAGON"};
private void doFilter() {
StringBuffer bf = new StringBuffer();

String[] objs = {"1 CIRCLE", "2 DIAMOND", "3 TRIANGLE", "4 DIAMOND", "5 CIRCLE", "6 HEXAGON"};
Observable.fromArray(objs)
.filter(item -> item.endsWith("CIRCLE"))
.map( result -> bf. append(result +"\n"))
.subscribe(
result -> ((TextView) findViewById(R.id.txt_result)).setText(bf.toString())
);
}
  • 이번에는, 문자열 배열을 가지고 있다.
  • fromArray로 짜른다음, filter에다가 마지막단어에 CIRCLE을 가지고 있는지를 조건으로 걸었다.
  • map을 통해, StringBuffer에 하나씩 붙혔고
  • 그 결과를 Textview에 toString으로 뿌렸다.

    imageimage
    filter( item -> item.endsWith( "CIRCLE") )로 마지막에 CIRCLE이라는 단어를 가진 item들만 방출되는 것을 확인할 수 있다.



다음은, flatmap -> filter를 이용해서, 반복문없이 realm DB의 필요한 자료를 가져와 어댑터에 더해주는 코드이다.
테스트는 하지 않았지만, 이러한 방식으로 사용하면 될 것 같다.
즉, for문을 사용하지않고 flatmap과 filter로 원하는 정보를 가져오는 방식이다.

  • 모델객체를 -> flatMap으로 여러개의 옵져버블 리스트로 만든다.
  • 리스트에 filter를 걸어  각 item 중  조건만족하는 원하는 item만 가져온다
  • subscribe를 통해 onNext에서는 리스트뷰, 리사이클러 어댑터에 add + onComplete에서 갱신
/*    Observable.flatMap( realm -> Observable.fromArray( Students ) ) // Realm의 Stundent라는 모델의 객체변수를 쪼개어 옵져버블에 담는다.
.filter( students -> students.getScore > 30 ) //Studnets의 getScore필드를 조건식으로 건다. 30이 넘는 학생들만 뿌려질 것이다.
.subscribe( mAdapter.add( students) ) // 30점이 넘는 학생들만 리스트뷰or리사아클러뷰의 어댑터에 add해준다. + onComplete에서 데이터를 갱신하면 좋을듯? */


groupby ( item -> item이 들어간 조건식 )

rx groupby에 대한 이미지 검색결과


쉽게말해 fromArray에서 내려오는 여러개의 단일 Observable을 조건식에 맞추어 그룹별로 묶어주어 GroupedObservable을 만드는 함수이다.
옵져버블로 return되므로, 어떠한 값을 얻고 싶을 때는 subscribe로 방출이 되어야지 값을 얻을 수 있을 것이다.
(그룹별로 흘러나올 때, 만약 toList의 과정이 없다면 묶여나오는 것 같지는 않다..?)

형식은 filter와 같다.  groupby( item- > item이 들어간 조건식)
즉, filter 는 조건에 맞는 item만 흘려보낸다면,
groupby는 조건에 맞는 item조건에 맞지않는 item을 구분하여  그룹으로 묶어서 흘려보낸다.(.toList()를 이용해서 묶는 것 같다)
예제코드를 보면, 그룹화된 것(grouped)1. 리스트(grouped.toList()) 로 만들어서 내부에서 다시한번 subscribe로  item으로 방출하며
                     그룹화된 것(grouped)2.grouped.getKey()를 이용해서 그룹조건식에 맞는지 true/false로 확인한다.
- getkey()를 하면, 그룹화된 옵져버블의 그룹기준이 찍힌다.


private void doGroupby() {
StringBuffer bf = new StringBuffer();
Observable.fromArray(dataSet)
.groupBy(item -> item%2==0)
.subscribe(
grouped -> grouped.toList().subscribe( item-> {
bf.append(item+""+grouped.getKey());
((TextView) findViewById(R.id.txt_result)).setText(bf.toString());
}
)


);

}
  • groupby로 조건식을 걸어서, 그룹을 나눈다. ( 이때는 그룹옵져버블 상태이다.)
  • grouped된 옵져버블toList()를 이용해 리스트를 만든 뒤에, subscribe로 방출해야, 그룹으로 묶이는 것 같다.
    - 만약 map으로 변형을 먼저 시켜버리면, grouped.getkey를 subscribe에서 뽑아줄 수 없기 때문에 안했다.
       여기서 조건식에 맞춰 grouped된 것에 getkey()를 주면, true/ false로 나뉘게 된다.
  • subscribe에서, grouped(옵져버블) ->
    (1) grouped.toList()로 바꾸어 리스트로 만들어주고, 다시 subscribe하였다.
    (2) grouped.getkey()로 그룹기준(true/false)와 함께, 해당하는 그룹의 list( item )을 StringBuffer에 담아서, text에 뿌려준다.



다른 groupby의 예제를 보자.

private void doGroupby2() {
StringBuffer bf = new StringBuffer();
Integer[] array = {1,2,3,1,4,5,3,6,7};
Observable.fromArray(array)
.groupBy( item -> item ) //추정, groupby는 groupedobservable을 반환하고, i자신을 기준으로 정렬하면, 중복을 제외하고 오름차순 시킨다. groupby는 subscribe전까지는 옵져버블로 반환되므로, 그안에 값을 받으려면 getkey()를 이용해야한다.
.map( grouped -> grouped.getKey() ) // 위에서 나온 자기자신을 기준으로 그룹화하면, 그 배열에서 <같은 값들끼리> 그룹이 되어있을 것이다. 아직 subscribe를 거치지 않았으므로, groupedObservable이다. 각 옵져버블에 getkey()를 달아주면, 그 배열의 고유값들이 나오게 된다.

.subscribe( grouedKey -> {
bf.append( "그룹화 기준값 : "+ grouedKey +"\n");
((TextView) findViewById(R.id.txt_result)).setText(bf);
}
);

}

배열에서 (중복제외 오름차순으로 ) 고유한  그룹화의 기준이 되는 고유값만 가져오고  싶을 때는 아래와 같이 하면 된다.

  • groupby( i->i) 자기자신을 기준으로 쓰고,
    - 조건식x -> getkey()시 true/false가 아닌,  그룹의 기준인 자기자신이 나옴. 중복은 제외됨.
  • map으로 grouped 된 GroupedObservable을 -> getkey()한 것으로 바꾸고
  • subscribe를 통해 그룹옵져버블에서 뿌려주는 데이터를 찍어보면 된다.

    imageimage
    - 어떠한 리스트에서 고유한 값을 알아보기 좋다. 날짜가 포함되어있을 때, 기준이 되는 날짜들을 파악할 수 있다.




아래는, 자기자신으로 groupby하였을 때 나오는, 각 고유한 값들을 (unique)라 두고,
subscribe에서 다시한번, Observable.fromArray를 불러와서, filter로 unique(배열의 고유값)과 같은 것만 가져온다.
만약 고유값이 1이였으면, 배열중에 1들만 걸려서 내려올 것이다.
이렇게 filter된 것을 다시한번 subscribe해서, 고유값 - 고유값으로 필터링된 배열성분을 찍어준다.

private void doGroupby2() {
StringBuffer bf = new StringBuffer();
Integer[] array = {1,2,3,1,4,5,3,6,7};

Observable.fromArray(array)
.groupBy( item -> item )
.map( grouped -> grouped.getKey() ) //고유값 1,2,3,4,5,6,7을 내어보낼 것이다.

.subscribe( unique -> {

Observable.fromArray(array).filter( item -> item.equals( unique ) ) // 다시한번 배열각 성분 중에서, unique한 고유값을 가지고 필터링 한다.
.subscribe( filterd -> { bf.append("고유값("+unique+")으로 필터링된 값 : " + filterd + "\n");
((TextView) findViewById(R.id.txt_result)).setText(bf.toString()); }
);

});

image


만일 아래와 같은 배열이라면, 고유값에 따라 리스트가 아래와 같이 작성된다.

image



take, first, last

take는 배열 중 앞에 몇개를 취하는 것이고, 앞서 takeLast를 통해 뒤에 몇개를 취한 적도 있다.

first와 last는 default값이 들어가긴 하지만, 값과 상관없이 앞에1개, 뒤에1개를 취하는 것 같다.

private void doTake(int count) {
StringBuffer bf = new StringBuffer();
Observable.fromArray(dataSet)
.take(count)
.map( result -> bf. append(result))
.subscribe( result -> ((TextView) findViewById(R.id.txt_result)).setText(bf.toString()) );
}
private void doFirst() {
StringBuffer bf = new StringBuffer();
Observable.fromArray(dataSet)
.first(3)
.map( result -> bf. append(result))
.subscribe( result -> ((TextView) findViewById(R.id.txt_result)).setText(bf.toString()) );;
}
private void doLast() {
StringBuffer bf = new StringBuffer();
Observable.fromArray(dataSet)
.last(2)
.map( result -> bf. append(result))
.subscribe( result -> ((TextView) findViewById(R.id.txt_result)).setText(bf.toString()) );

}


take의 경우 파라미터로 3을 받아서, 앞에 3개를 취하도록 해보았다.

image

image

image

데이터의 발행자와 수신자, 그리고 그 사이에 데이터를 변형해주는 변환연산자(map, flatmap, zip)

깃허브

데이터 발행자 : Operator(just, create, defer 등을 통해 분류된다)
Observable
Subscriber
Single
Observer
Maybe

데이터 수신자( Stream이 연결되는 Observer들)
Consumer
Subject
Completable

그외에 데이터 발행자 ----> 데이터 수신자  사이의 Stream에 올라오는 데이터를 변형하는 것이 map, flatmap, zip이다.



Map 사용하기

rx map에 대한 이미지 검색결과

Map이란 Observable에서 받은 데이터를, --> Observer로 가기전에 변형해주는 함수이다.
여기서는 map()을 이용해서, fromArray로 생성한 옵져버블의 각 리스트성분들에  [   ] 괄호를 입혀주는 변형을 해볼 것이다.

private void doMap() {

Observable.fromArray(new String[]{"aaa","bbb","ccc","ddd","eee","fff"})
.map( item-> "["+item+"]" )
.subscribe(
item -> datas.add(item),
Throwable::printStackTrace,
() -> adapter.notifyDataSetChanged()
);
}
  • 이전강의에서는 fromIterable을 쓸 때, 어떠한 리스트변수를 집어넣어야했지만, fromArray는 인자에 바로 String배열을 생성해서 넣어 줄 수 있다.
    각 ArrayList의 성분들이 옵져버블--> 옵져버로 보내질 것인데, 여기서 map을 이용한다.
  • map()에는 각 item이 하나씩 stream을 타고 올 것이다. 거기에  " [ " 과 마지막 " ] "을 입혀주자
  • subscribe를 통해  onNext란에는 리스트뷰의 관리리스트에, add를 해주고, onComplete란에는 어뎁터갱신을 통해 리스트뷰에 찍히도록 하자
    imageimage


  • map()에서는 직접 인자를 -> 새롭게 변형할 수 있지만, 변형시켜주는 어떠한 람다함수도 넣을 수 있다.
    ***이 때, 람다함수 Function<T, R > 에서  T에는 들어오는 타입,  R에는 나가는 타입을 넣어줘야한다.
    ***String 성분이 들어왔다가 ->  String성분이 나가므로  아래와 같이 작성한 뒤, map() 인자로 넣어준다.


  • Function<String, String> getMap = item -> "<" + item + ">";

    map( getMap )
  • private void doMap() {

    Function<String, String> getMap = item -> "<" + item + ">";

    Observable.fromArray(new String[]{"aaa","bbb","ccc","ddd","eee","fff"})
    .map( getMap )
    .subscribe(
    item -> datas.add(item),
    Throwable::printStackTrace,
    () -> adapter.notifyDataSetChanged()
    );
    }

          imageimage


flatMap 사용하기

rx flatmap에 대한 이미지 검색결과

Map과의 차이점은 return값이 Observable이라는 것이 가장 중요하다. 그 결과 map이 하나의 item성분에 대해 하나의 변형된 item만 내놓을 수 있지만,
flatmap속 Observable.just 등에 의해 파라미터 안에서 콤마(,)을 통해 --> 하나의 item에 대해  여러개의 변형된 item을 발행할 수 있게 된다.
실제 flatmap()함수의 리턴값을 보면,  map()을 먼저 한다음 -> merge로 감싸주어서 Observable로 반환한다.

여기서는 fromArray로 만든 Observable에다가 .flatmap()을 통해  각 item -> 다시 Observable.fromArray(  변형된 item 형태를 포함하는 new String[] )을 반환한 뒤, map과 마찬가지로  add / 갱신 해줄 것이다.

private void doflatMap() {

Observable.fromArray(new String[]{"aaaa","bbbbbbb","cccccccc","ddd","ee","ffffffffff"})
.flatMap(item -> Observable.fromArray( new String[]{"name:"+item+" Byte: "+ item.getBytes().length}))
.subscribe(
item -> datas.add(item),
Throwable::printStackTrace,
() -> adapter.notifyDataSetChanged()
);
}
  • Observable.fromArray로 옵져버블을 생성한 뒤, flatmap()안에다가 item -> 각 item변형을 포함하는 Observable.fromArray를 다시 만든다
    - 새로운 옵져버블은 item.getBytes().length 를 통해, 바이트 크기도 표시하도록 해주자.
  • 마찬가지로 onNext에서는 add / onComplete에서는 갱신을 해준다.
    imageimage
    *** UI에서는 드러나지 않지만, map과 다르게 Observable.just/create 등의 오퍼레이트 ()파라미터 안에서  콤마(,)를 통해 여러개의 item을 발행할 수 있다.
    예를 들어 map ( item -> "[" + item + "]" )  까지만 가능하고, 콜론을 찍어서 하나 더 발행할 순 없다.
    근데,  flatmap 의 경우 ( item -> Observable.just ( "[" + item + "]" , "<" + item + ">" ) 형식으로  하나의 item성분에 대해 여러개의 리턴값을  발행할 수 있다.
    다만, textview에서는 마지막것만 찍힐 것이고, 로그 혹은 리스트뷰에 add한다면 2개의 아이템이 모두 발행될 것이다.


  • 만약 flatmap에 변형함수를 집어넣는 다면, 그 람다함수 Function< T, R>에서 인풋인 T는 String,  아웃풋인 R의 타입은 Observable<String>이 된다.

    Function<String, Observable<String>> getflatMap = item -> Observable.just(item + "[1]", item + "<2>");
  • private void doflatMap() {

    Function<String, Observable<String>> getflatMap = item -> Observable.just(item + "[1]", item + "<2>");

    Observable.fromArray(new String[]{"aaaa","bbbbbbb","cccccccc","ddd","ee","ffffffffff"})
    //.flatMap(item -> Observable.fromArray( new String[]{"name:"+item+" Byte: "+ item.getBytes().length}))
    .flatMap( getflatMap )
    .subscribe(
    item -> datas.add(item),
    Throwable::printStackTrace,
    () -> adapter.notifyDataSetChanged()
    );
    }
  • imageimage
    ***위와는 다르게 하나의 item( aaaa)에 대해------flatmap:옵져버블로 리턴해 2개의 아이템연속발행---> listview에 변형된 2개의 item으로 추가되었다.


flatmap을 통해  단어쪼개보기

private void doWords() {
String str = "qwer-asdf-zxcv";
Observable.just(str)
.map(s -> s.split("-")) // -를 기준으로 끊어준다.
.flatMap(Observable::fromArray) //끊어진 성분들을 fromArray를 통해서 옵져버블에 담아 리턴한다.
.take(2) //각 item 중 1,2번째 item을 취한다.
.takeLast(1) //각 item중 뒤에서 1번째(마지막) item을 취한다. ==> 앞에 2개를 선택한 뒤, 뒤에 것을 가져온다.
.subscribe(
item -> datas.add(item),
Throwable::printStackTrace,
() -> adapter.notifyDataSetChanged()
);
}
  • 하이픈(-) 조합으로 된 String을,  Rx를 이용하기 위해,  옵져버블.just에 넣는다.
  • 담긴 String을 .map()을 이용해서,  s.split("-")을 통해 쪼개는 것으로 변형한다. 3개의 String배열이 될 것이다.
  • flatmap을 통해서 3개의 배열을 Observable::fromArray를 통해 옵져버블로 변환시켜 반환한다.
    -   클래스명 :: 함수명 은  함수참조라는 lambda식이다.  3개의 String배열을  옵져버블::fromArray에 참조하면, 옵져버블로 반환된다.
  • take( 앞에서 총 몇개 item을 취할 것인가)  -> takelast( 그중에 뒤에서 몇개를 취할 것인가)를 통해 골라내고 싶은 것을 골라낸다.
  • listview를 통해 찍힌 것을 확인해본다.
    - 만약 takelast(1)이라서 마지막 1개만 취하면, textview에다 찍어두 되지만, 2개 이상이 필요한 경우에는, listview나 로그로 찍어본다.
    imageimage
    위는 qwer-asdf-zxcv  라는 단어를 옵져버블.just에 넣어---> map으로 쪼개고--->  flatmap으로 옵져버블에 담아 -->  take/takelast를 통해 필요한 것만 골라낸 것이다.


flatmap로 구구단 만들기

private void doGugudan(int dan) {
Function<Integer, Observable<String>> gugudan =
num -> Observable.range(1, 9).map(row -> num + " * " + row + " = " + num * row);


Observable.just(dan).
flatMap(gugudan).
subscribe( item -> datas.add(item),
Throwable::printStackTrace,
() -> adapter.notifyDataSetChanged()
);

}
  • 버튼클릭시, 단수를 입력 받은 뒤,
  • 먼저, flatmap에서 integer를 받아 옵져버블로 반환해줄
    람다함수를 정의해준다. Input타입은 Integer 이며,  output타입은 Observable<String>으로 해준다.
    - gugudan이라는 함수는,  입력받은 int 8이 옵져버블.just를 통해 바로방출되며, flatmap으로 올 때,
    (1) 8이라는 숫자에 대해 ->    옵져버블.range(1,9)를 통해 1부터 9까지 row라는 숫자로 순차적으로 방출되고, 그 각각의 row에 대해서
    (2) 8이라는 숫자  * row(1~9) = 숫자*row값을 하나씩 방출한다.
  • 그렇게 dan이라는 숫자8에 대해, 1부터 9까지의 변형된 구구단 계산식을 listview에 뿌려준다.

  • 간단하게 생각해보면)
    flatmap을 통해, 입력받은 8이라는 하나의 item이,  변형함수gugudan을 통해 데이터가 여러개 를 방출하는 옵져버블로 나오는데, 그 옵져버블이 range(1,9)라 1부터 9까지 9개의 아이템이 방출되는 것이다.
    imageimage



Zip사용하기

rx zip에 대한 이미지 검색결과


zip 의 특징은 2개 이상의 Observable을 결합할 때 사용합니다. 만약 한쪽의 Observable에서 처리가 안된다면 <직전값, 이전값이 아닌> 진행중인 것이 모두 처리될때까지 발행을 기다렸다가 묶어주고 발행.

형태는 Observable.zip (  옵져버블1옵져버블2, (옵1의 item, 옵2 item -> 처리 ) ).subscribe()의 형식을 가진다.

my) 이 때, 2개의 옵져버블의 item갯수가 동일해야할 것 같다.

private void doZip() {

Observable.zip( Observable.just("Chojaeseong"),
Observable.just("image.jpg"),
(item1,item2) -> "Name:"+item1 + ", Profile image:"+item2)
.subscribe( zipped -> ((TextView) findViewById(R.id.textView)).setText(zipped)
);

}
  • 1개만 방출하는 just 옵져버블 2개를 zip으로 묶고, 각 item에 대해서,  변형처리를 하였다.
  • 첫번재 옵1의 item인, Chojaeseong 과 ,   옵2의 item인, image.jpg가   묶여서 하나의 데이터로서 텍스트뷰에 찍히는 것을 확인할 수 있다.
    imageimage


옵져버블에 지정할 수 있는 스케쥴러명령어(2)

깃허브

subsctibeOn()은 observable의 작업을 시작하는 쓰레드를 선택한다. 중복지정 될 경우, 마지막 쓰레드에서 실행된다.

observeOn()은 이후에 나오는 subscribe에서 옵져버가 받아서 처리하는 쓰레드를 선택한다.


각종 스케쥴러들

  • Schedulers.computation() - 이벤트 룹에서 간단한 연산이나 콜백 처리를 위해 사용된다.
    RxComputationThreadPool라는 별도의 스레드 풀에서 돌아갑니다. 최대 cpu갯수 개의 스레드 풀이 순환하면서 실행됩니다.
  • Schedulers.immediate() - 현재 스레드에서 즉시 수행. observeOn()이 여러번 쓰였을 경우 immediate()를 선언한 바로 윗쪽의 스레드를 따라감
  • Schedulers.from(executor) - 특정 executor를 스케쥴러로 사용
  • Schedulers.io() - 동기 I/O를 별도로 처리시켜 비동기 효율을 얻기 위한 스케줄러.
    자체적인 스레드 풀 CachedThreadPool을 사용합니다. API 호출 등 네트워크를 사용한 호출 시 사용
  • Schedulers.newThread() - 새로운 스레드를 만드는 스케쥴러입니다.
  • Schedulers.trampoline() - 큐에 있는 일이 끝나면 이어서 현재 스레드에서 수행하는 스케쥴러.
  • AndroidSchedulers.mainThread() - 안드로이드의 UI 스레드에서 동작
  • HandlerScheduler.from(handler) - 특정 핸들러 handler에 의존하여 동작


private void doDeferAsync() {
Log.i(TAG, ":"+Thread.currentThread().getName()+": in Main");
Observable<String> obv = Observable.defer( () -> {

Log.i(TAG, ":"+Thread.currentThread().getName()+": defer생성시 쓰레드");
return Observable.just("Here i am.");

});

obv.subscribeOn(Schedulers.computation()) // 발행자 (Observable) Thread 를 지정 - 옵져버블이 뿌려주는 쓰레드
.observeOn(Schedulers.newThread()) // 구독자 (Observer, subscriber) Thread 를 지정 - 옵져버가 받는 곳의 쓰레드
.subscribe(
s -> Log.i(TAG, ":"+Thread.currentThread().getName()+": 구독시 옵져버의 쓰레드= "+s),
throwable -> throwable.printStackTrace(),
()-> Log.i(TAG, ":"+Thread.currentThread().getName()+": onCompleted의 쓰레드")

);
  • 먼저, defer를 통해 지연생성될 옵져버블을 just로 생성한다.
  • Main함수에서 쓰레드를 찍어보자 ->  main
  • defer에서 옵져버블이 반환될 때, 쓰레드를 찍어보자.  이것은 옵져버에서 구독시, subscribeOn을 통해 지정된 쓰레드가 찍히더라.
    defer안에서    생성되어 반환되는 옵져버블은, subscribeOn에서 지정한 옵져버블의 쓰레드가 찍히는 구나.
    --> Schedulers.computation()을 지정한 결과 : RxComputationThreadPool-1
  • subscribe안에서 onNext에 해당하는, 옵져버가 뭔가를 처리하는 쓰레드는 
    --> ObserverOn을 통해  Schedulers.newThread()의 결과 : RxNewThreadScheduler-1
  • onComplete의 쓰레드는 옵져버의 처리쓰레드와 똑같은 : RxNewThreadScheduler-1
    image



안드로이드의 일반적인 Rx처리방법

  • 옵져버블의 데이터 처리는 별도의 쓰레드에서 처리한다.
    observable. subscribeOn(Schedulers.computation())

  • 옵져버는 처리한 데이터를 UI에 데이터를 뿌려주기 위해 메인쓰레드에서 처리한다.
    .observeOn(AndroidSchedulers.mainThread() )

  • 2개의 쓰레드 지정을 끝내놓고 .subscribe한다.


Rxandroid 를 사용하기 위한 준비

깃허브

앞서 했던 것처럼, 각종 라이브러리를 추가RxAppcompatActivity상속한다.

implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.x.x'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
implementation 'com.trello.rxlifecycle2:rxlifecycle-android:2.2.1'
implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.1'

public class MainActivity extends RxAppCompatActivity {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}


Operator

Observable은 Observer 패턴을 구현한다.
Observer 패턴이란? 객체의 상태 변화를 관찰하는 옵저버 목록을 객체에 등록하고, 상태 변화가 있을때마다 메서드를 호출한다
Rxjava에서는 Observable  ==== subscribe====> Observer 를 통해서, Stream으로 연결이 먼저 되고,
데이터의 변동이 있을 때, Stream을 통해서 흘려보낸다.

Observable가운데, 정해진 일을 하는 것을 분류해 놓았는데
그것을 Operator를 이용해서 정해진 Observable을 생성한다.



Observable.just 사용하기

Just

데이터나 객체를 바로 방출하는 Oservable를 생성한다.
만약 여러개의 데이터를 집어 넣는다면, 순서대로 로그에 찍히나,  마지막 데이터만 UI에 표시된다. 단, 타입은 모두 같아야 한다.

//just 실행함수
public void doJust(){
Log.i("test","doJust");
Observable<String> obv = Observable.just("Observable.just!", "1", "2");
obv
.compose(bindToLifecycle())
.subscribe( s -> {
Log.i("just", s);
((TextView) findViewById(R.id.textView)).setText(s);
}
);

}
  • RxView.clicks를 이용해서 just로 옵져버블을 생성하는 함수를 호출한다
  • String타입의 옵져버블 변수에,  Observable.just( "String");을 이용해서 담아놓는다.
  • 바로 방출하는 옵져버블을 subscribe를 통해서 onNext에 해당하는 파라미터(1st)에 텍스트뷰에 뿌려준다.
    *** subscribe()에서 onNext에 해당하는 첫번재 파라미터에서 2개의 작업( 로그찍기 + 텍스트뷰 뿌리기)를 하고 싶다면
    위의 코드와 같이 s -> {     } 대괄호로 감싸고,    그 속에 {  A작업;   B작업;]   으로 콜론으로 끊어 주면된다.
    만약, 쉼표로 끊을 경우, 파라미터를 나눠주는 것 처럼되버리니 그러지 말 것!
    imageimage
    image



Observable.create 사용하기

Create


Create는 수동으로 옵져버 메소드(onNext, OnError, OnComplete)를 호출하는  옵저버블을 생성한다.
onNext를 여러 개 작성해놓으면 ,  그 갯수만큼 옵져버에게 데이터를 전달할 것이다.


//create 실행함수
public void doCreate() {
Log.i("test", "docreate");
Observable<String> obv = Observable.create( (ObservableEmitter<String> emitter) -> {
emitter.onNext("조재성");
emitter.onNext("김석영");
emitter.onNext("1000일");
emitter.onComplete();
}
);

obv
.compose(bindToLifecycle())
.subscribe(s -> {
Log.i("Create", s);
((TextView) findViewById(R.id.textView)).setText(s);
});
}
  • Rxjava2로 넘어오면, 사용방법이 좀 달라지긴 했으나, create안에 옵져버블과 같은 타입의 ObservableEmitter 변수를 생성하고
  • 람다식을 이용해서,   emitter ->  {   } 대괄호속에서, ;세미콜론으로 원하는 만큼의 onNext, onError, onComplete를 작성해주면 된다.
  • 이후 옵져버로 subscribe할 때는 마지막 onNext만 텍스트뷰에 노출될 것이고, 로그에는 다 찍힐 것이다.
    imageimage


Observable.defer 사용하기

Defer

defer로 생성된 옵져버블은 다른 옵져버블 Operator와 다르게 옵져버가 구독하기전까지는 옵저버블을 생성하지 않는다. 다른 오퍼레이터들은 오퍼레이터를 선언하는 순간 메모리에 할당 되지만 defer는 subscribe가 호출 될 때에 할당된다. 그리고 각각의 옵져버에게 매번 새로운 옵저버블을 생성한다.
즉, 옵져버가 subscribe() 함수를 호출할때까지 Stream의 생성을 미루면서,  호출 될 때마다  새로운 Observable이 생성하여 보낸다.
어떠한 통신을 한다면, 구독할 때까지 미뤄지기 때문에 최신 데이터를 얻을 수 있습니다.
즉 defer() 함수를 사용하면, subscribe() 함수를 호출할 때의 상황을 반영하여 Stream 생성을 지연하는 효과를 보여줍니다.

그러므로, 옵져버블 생성시, () -> retrun Observable. Operater ; 의 과정이 들어간다. 여기서 생성된 옵져버블이 지연효과를 가진체로 subscribe호출시 return되서 들어가는 것이다.

//defer 실행함수
public void doDefer() {
Log.i("test", "do defer");
Observable<String> obv = Observable.defer( () -> {
return Observable.just("defer"); // defer 안에 () -> { retrun 옵져버블.operator } 를 통해 비동기로 옵져버블을 생성해준다.
}
);

obv
.delaySubscription(3, TimeUnit.SECONDS, AndroidSchedulers.mainThread()) // UI 쓰레드에서.. 뭔가 비동기 작업해주면,, 메인쓰레드에서 하라고 명시해줘야한다.
.subscribe( s-> ((TextView) findViewById(R.id.textView)).setText(s));
}
  • defer로 생성할 경우, 그 안에서 () -> {  } 대괄호안에  지연효과를 누려 최신데이터를 반영해서 옵져버에게 전달해줄 옵져버블이 return된다.
  • 옵져버는 지연되어서 구독시점에 새로생성된 옵져버블을 구독하게 된다.
  • just에도 작동하는, 3초후 구독정보 전달 메소드(delaySubscription)는 아래와 같다.  3초 있다가 데이터를 흘려보내는 코드인데, 비동기 작업으로 UI를 건드므로 3번째 파라미터에서, 메인쓰레드를 지정해줘야한다.
    - just로 생성한 옵져버블도 3초 이후에 전달이 된다. 그러나 just로 생성한 옵져버블은 앱이 켜질 때, 이미 옵져버블이 생성되어있지만,
    - defer로 리턴될 옵져버블은 3초 이후 구독 데이터가 전달이 될 때, 그때의 최신정보를 가진 체 전달 될 것이다.

    .delaySubscription(3, TimeUnit.SECONDS, AndroidSchedulers.mainThread()) // UI 쓰레드에서.. 뭔가 비동기 작업해주면,, 메인쓰레드에서 하라고 명시해줘야한다.

    imageimage


Observable.fromIterable 사용하기

fromiterable에 대한 이미지 검색결과

fromIterable로 옵져버블을 생성할 땐, 파라미터로 특정타입의 리스트변수 를 받아 -> 각 리스트의 성분을 하나씩 쪼개서 옵져버에게 전달한다.
(Rxjava1에서는 from이라는 오퍼레이터에 바로 new String[]{   , , ,   } 로 대입이 가능했으나,,  지금은 안된다ㅠ)

//fromIterable 실행함수
public void dofromIterable(){
Log.i("test","do fromIterable");

List<String> names = new ArrayList<>();
names.add("Cho");
names.add("Jae");
names.add("Seong");

//fromIterable은 rxjava1과 다르게, new String[] { 배열 }을 바로 넣을 수 없고,, 미리 생성된 List<String> 변수만 들어가는 것 같다.
Observable<String> obv = Observable.fromIterable(names);
obv
.compose(bindToLifecycle())
.subscribe(
s -> {datas.add(s); datas.add("+1"); }, //subscribe시 onNext자리
throwable -> throwable.printStackTrace(), //subscribe시 onError? 자리
()->adapter.notifyDataSetChanged() //subscribe시 onComplete자리..
);

}
  • 먼저 간단한 listview와, 거기서 쓰일 ArrayList, ArrayAdapter<String>으로 만든다.
  • fromIterable의 파라미터로 들어갈 list를 생성한다
  • names라는 리스트에 들어가 있는 cho, jae, seong은 옵져버블안에서 하나씩 나누어서 옵져버로 전달된다.
  • subcribe를 통해 구독할 때, listview의 ArrayList에 하나씩 add해준다. (나는 구분을 위해 대괄호를 이용해서 +1을 하나 더 add해주었음)
  • 그리고 onError자리에는  throwable -> throwable.printStackTrace()  를 넣어준다
  • onComplete자리에서는 create와 다르게, 메소드를 실행시킬 수 있다. 이 때,  ()-> 메소드호출   형식으로 작성해준다.
    여기서 add가 완료된 시점이니, listview를 갱신해준다.
    +1에 의해 추가된 리스트를 확인해보면, 각 리스트의 성분들이 하나씩 add되고 갱신되고의 과정을 거친다
    (1) cho 를 add 후, 어댑터 갱신
    (2) jae 를 add 후, 어댑터 갱신
    (3) seong 을 add후, 어댑터 갱신
    imageimage


fromIterable을 정리해보자면)

어떠한 리스트가 있고 -> 그것을 fromIterable로 각 성분을 쪼개서 -> subscribe 에서 리스트뷰의  관리리스트에 add -> onComplete자리에서 갱신해서 반영 -> 반복

간략한 정리

Rxbinding2를 이용하면

1. clicks를 이용하여, 버튼 변수 및 텍스트뷰 변수를 생성없이 + 클릭리스너 없이  
   버튼id -> 텍스트뷰id만으로   subscribe를 통해 스트림으로 연결해서 보내준다.
2. 옵져버블을 이용해야할 경우, 텍스트뷰가 받아서 처리할  데이터타입은 String형이므로, 옵져버블도 String으로 생성해줘야한다.
   옵져버블에,  버튼의 clicks 이벤트를 담기 위해서는   clicks( 버튼id ) . map(   e-> String )으로 map을 통해 변형해서 담아주면 된다.
   옵져버블을 구독하는 옵져버에서는 onNext에서 받은 String을 처리해주면 된다.


RxLifeCycle을 사용하면

1. 따로 unsubscribe(구독종료)안해도 알아서 작동종료한다.

2. 확인인,  앱을 back버튼으로 종료한 상태에서 계속 돌아가는지 안돌아가는지 확인해보는 것이다.

깃허브


RxBinding 라이브러리 추가하기


Rxbinding2 공식 깃허브에 가서 최신버전을 확인한 다음, 해당 버전의 라이브러리를 gradle에 추가해준다.

implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'


Rxbingding 사용하기

버튼에 대해서 사용할 것이다.

1. Rxbingding(RxView의 clicks)를 사용하면  버튼변수생성없이 + setOnclickListener 없이 
   id참조만으로 연결되어 바로 데이터를 뿌려줄 수 있다.

  • clicks()안에 파라미터로 해당 버튼의 id를 참조해준다.
  • .subcribe를 통해, 버튼클릭된 이벤트 e 를   ( 텍스트뷰 id를 참조 )하는 곳에 setText를 바로 시킬 수 있다.
    image


imageimage



2.  String타입의 Observable에다가    
    clicks(버튼id).map( e -> String)을 통해서 이벤트 자체를 텍스트로 바꿔서 옵져버블에 싣을 수 있다.
     옵져버는 클릭 때마다, 해당 데이터를 받아서 처할 수 있다.

  • String 타입의 Observable 변수를 만든다
  • clicks().map( e -> " 텍스트 " ) ;을 통해서 클릭이벤트를  텍스트로 바꿔서 String옵져버블에 할당해준다.
    image
  • 이제 이 옵져버블을 구독할  String타입의 옵져버 변수를 만든다.
    이 옵져버의 onNext에서   스트림을 타고 받아지는 텍스트를    텍스트뷰에다가 setText시켜줄 것이다
    image

  • 옵져버블에 옵져버를 구독시켜준다.
    image


imageimage



RxLifecycle 라이브러리 추가하기

1. 공식 깃허브에서 최신버전을 구한다.

compile 'com.trello.rxlifecycle2:rxlifecycle-android:2.2.1'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.1'

이 때, 안드로이드개발자라면  android 와 components 라이브러리 2개를 추가해줘야한다.

2.  반드시 사용할 Activity에서,  RxAppCompatActivityexends 해줘야한다.

image


RxLifecycle 사용하기

Observable.interval()을 통해서 로그를 찍다가  앱이 화면뒤로 갔을 경우,,
unsubscribe를 하지 않는다면, 스트림은 유지되는 것을 확인할 수 있다.

그 이유는,,  옵져버블은 context를 가진 체 비동기로 작동하기 때문에,, 따로 구독종료를 해줘야한다.
이를 편하게 처리해주는 라이브러리가 rxlifecycle이다.


사용방법은 .compose( bindToLifeCycle()) 을 옵져버블에 달아주면 된다.

image

1. 앱이 화면에 켜져있는 상태에서,, 2개의 옵져버블이 모두 interval간격으로 로그를 찍고 있다.

image


2. 앱화면이 꺼진 상태에서는,, lifecycle을 적용한 옵져버블은 로그를 찍는 작업을 중단한다. 구독종료 된 것이다.

image

Functional vs Reactive

2018. 4. 6. 03:01

- 이 자료는 Youtube 등 을 통해 학습하고 제작되었습니다

Functional Programing

과거에서부터 생겨난 문제점은, 어떠한 데이터가 있고, 동시에 코딩하는 것이다.
image

Read는 문제가 없는데, Write시 문제가 생긴다. 

그래서 나온 방법이 동시에 못쓰게 만드는 것 Immutable이다.
요즘 나오는 Modern 랭귀지들은 동시에 못쓰는 변수 let( Closer?)을 제공한다.
image


var변수 data가 바뀌면,  return값도 바뀌게 된다. 동시 접근시 문제가 될 수 있다.
let변수 data는 바뀌지 않도록 설정되어 있지만, 함수 속의 var변수 backup이 바뀐다. 동시 접근시 문제가 될 수 있다.
아래 함수들은 혼자 돌아갈 땐, 문제가 없는데, 동시에 접근하게 된다면 문제가 될 수 있다.

이렇게 let이 아닌 것들에 대해 외부적으로 변화를 초래한다고 해서 Side-Effect라고 부른다.

image


Side-Effect를 없애기 위해서, 프로그래밍 방식을 바꾸었는데,
함수내에서 사용하는 모든 변수의 값을 파라미터로 받고,  새로운 값을 만들어서 return하는 것이다.
이러한 방식을 Pure function이라고 한다.

image


함수의 종류는 딱 3가지다.
image


이러한 3가지 종류의 함수의  output과 intput을 연결하여, 각 함수들을 섞어서 쓰는 것을 Composition이라고 한다.

각 함수들은
1. input은 없고 output만 있는 것 : 생성자(Generator) - ex> get함수
2. input은 , output이 모두 있는 것 :  Function
3. input은 있는데 output은 없는 것 : Consumer ex> set함수?
image



12번 라인에서 func함수 중간에 파라미터로 함수를 받았다.
이렇게 함수안에 함수가 들어있는 것을 High-Order function(고차 함수)라고 한다.

image


자바는 Object를 변수에 담거나, ArrayList에 담을 수 있거나, return값을 사용할 수 있다.
함수까지도 이렇게 사용할 수 있는 것을 First Class Citizen(1급 객체)라고 한다.
자바는 객체만을 이러한 방식으로 사용가능 하므로, OOP언어라고 하고
Swift는 객체 뿐만 아니라, 함수, 프로토콜까지 1급 객체로 사용할 수 있으므로 멀티 패러다임 언어라고 한다.


자바로 OOP 김밥을 만들어보자.

  1. 재료인, 단무지 / 소세지/ 시금치 CLASS를 정의
  2. 공통적인 상위 클래스인 식재료 CLASS 정의
  3. 식재료class가 반드시 가져야할 변수or메소드를 포함하는 자르기interface 정의
  4. 김밥을 만들기 위해, 조리법interface / 김말기interface정의
  5. 김밥Class정의  + 식재료class상속  + 조리법, 김말기 interface 상속
    image


Functional Programing으로 김밥을 만들어보자.

  1. 자르기 interface -> 함수func로 정의
  2. 식재료들을 자르기func의 변수로 집어넣음
  3. 자르기(식재료) 함수 자체를 변수로 집어넣어서 김말기()함수를 정의

image


OOP 와 Functionl P의 차이점은
데이터를 정의하고, 변화 과정을 명령할 것이냐의 명령형 프로그래밍이냐
행위를 정의하고 데이터를 집어넣을 것이냐 의 선언형 프로그래밍이냐 이다.

- 생각의 주체를 데이터로 줬던 것에서-->  함수로 주는 것으로 패러다임이 바뀌는 것이다.
image

Functional Programming 요약
image


Reactive

Funtional Programing 하는 가운데,  url에서 text를 받아온 다고 가정하자.
async한 작업을 수행해야한다. 그것을 간단하게 해줄 수 있는 해결책이 Reactive이다.

async한 상황에서 데이터를 어떻게 처리할 것인가에 대한 대답을
stream이라는 것으로 연결하고 그것을 흘려보내는 아이디어가 Reactive이다.
그 아이디어를 구현화 한 것(Reactive Extension)이 Rxjava, RxSwift 등이다.


데이터를 생성하는 놈을 Generator, 그 데이터를 처리하는 것을 Consumer라고 한다.

Rx에서는 그것을 Stream으로 연결시킨 상태에서, 데이터를 생성한 놈을 Observable, 처리하는 놈을 Subscriber라고 부른다. 데이터가 흘러가는 Stream사이에 Operator를 통해 데이터를 변형할 수 있다.
image


예를 들어, getText()함수에 url을 집어넣어서 어떤 데이터를 받을 때,
image

  1.  return값으로 data가 아닌 Stream을 줘버린다.
    image
  2. Stream을  먼저 연결하고 난 뒤에, Server로 가서 데이터를 가져온다.
    image
  3. 데이터가 생기면 그때서야,  Stream을 통해서 원하는 곳으로 흘려보낸다.
    image
  4. 아래는 RxSwift의 코드다.
    4번라인에서 먼저 .create()를 통해서, 바로 Stream을 생성하여 return한다.
    8번라인에서, 이후에 데이터가 생성이 되면, onNext()를 통해 Stream에다 데이터를 씌워버린다.
    15번라인처럼 사용한다, url을 주면, .subscribe()를 통해서 언젠가 들어올 데이터를 이렇게 사용하겠다 !
    image

  5. 이미지를 다운받는다고 가정해보자.
    getImageUrl()은 서버에서 이미지url을 받아서 전달해줄, Stream을 return하는 React한 함수다.
    requestDownload()은 서버에서 이미지url을 받아서 전달해줄, Stream을 return하는 React한 함수다.

    getImageUrl().을 통해서 returm되는 Stream으로 String형태의 url을 전달받는다.
    .flatmap()을 통해  전달받은 String url -> URL로 변형 -> requestDownload()에 넣어 이미지를 Stream을 통해서 전달받는다.
    image
    async하게 작동하지만, 코드상으로는 시간적으로 작성할 수 있다.

  6. 정리
    image




image

  1. 안녕하세요 2021.11.29 11:04

    자바로 oop 김밥을 만들어보자 이 내용 유튜브 주소 알 수 있을까요??

라이브러리 및 세팅하기

깃허브

1. Rxandroid와 Rxjava2 라이브러리를  gradle:모듈수준에서 추가해준다.

implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.x.x'


2. Letrolambda식(java8)을 사용하기 위해서는, 마찬가지로 gradle:모듈수준에서 android{} 안에 compileOptions{}만 주면 된다.

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}


3. 처음 사용시,, 라이브러리는 추가되었지만,,, java와 io.reactivex 패키지의 Observable/Observer가 구분이 안되었다.
자동완성을 통해 작성해보니.. Observer객체가 io.reactivex.Observer로 나오길래 새로운 import구문을 넣어줬다.
(즉, 세부항목까지.. import해서,, 바로 쓸 수 있게..)

import io.reactivex.Observable;
import io.reactivex.Observer;



간단한 실습해보기

  • 메인액티비티화면서 간단한 Textview와 button을 넣고, button에는 onClick속성으로서, 누르면 작동할 함수 subscribeNow를 지정해준다.
    image

  • 전역변수로, Log찍을 태그명 / 텍스트뷰 변수 / String타입의 Observable과 Observer를 선언해준다.
  • private static final String TAG = "MainActivity";
    private TextView Txt;
    private Observable<String> mObservable;
    private Observer<String> mObserver;


  • 보내주는 스트림을 만드는 Observable에는 .just를 이용해서 String문구 하나만 넣어준다.
    구독자로서 자료를 받을 Observer에는, new Observer로 객체를 만들어, 받은 뒤 작업이 일어나는 onNext에 서setText해주도록 한다.
    image

  • 이제, 버튼을 누를시 작동되는 메서드 subscribeNow()에서는, Observable->observer로 subscribe해주면 된다.
    image
    ***참고로, Observable의 내용은 = Observable.just();를 통해 정확하게 한번 대입해준 것만 남아있고 그것이 전달된다.
    만약, 같은 Observable변수를 사용한다고 하더라도, just().subscribe()를 동시에 사용하면서 넣어준 데이터는, 변수에 입력되지 않고 바로 바쪄나간다.


프로젝트명 : Rx_hello

imageimage

Rxandroid를 쓰는 이유

2018. 3. 24. 02:54

1. AsyncTask와 비교해서

설계적인 면 + 코드 수 + UI로직 처리 + 연쇄 API호출이 가장 큰 장점이다.
Retrofit과 Retrolambda라는 시너지좋은 라이브러리를 함께 사용한다고 가정했을 때,

AsyncTask에서는
doInBackground()에서 UserApi호출 후  user객체 return ->
onPostExecute( user객체 )에서,  user객체를 또다른 AsyncTask2를 호출해 의 doInBackground()에서 서버에 user객체가 유효한지 유효성Api 호출 ->
AsyncTask2의 onPostExecute() 유효한지 안한지에 따라 다른작업 수행의 과정을 거쳤다면,


Rxjava에서는 (Retrofit 인터페이스 생성시, Call객체 대신, Observable객체로 만들어야한다)
Retrofit으로 UserAPI호출 ->  .subscribeOn(Schedulers.io())을 통해 비동기처리 -> .flatMap( user->  유효성검사api호출)  ->
observeOn( AndroidSchedulers.mainThread() )로 UI처리를 위해 메인쓰레드로 이동 -> subscribe( 유효성확인결과 -> 유효성확인결과?  A : B)
로 아주 간단하게 해결할수 있다.
image

그외 RxBinding을 통해, 버튼입력도 간단해진다.
그외 Operator가 준비되어있어, 다양한 연산자를 쉽게 사용할 수 있다.


2. UI다루기에 있어서 Rxjava

1. 클릭의 추상화
RxView
를 통해서 xml의 버튼id를 쉽게 달고, .map()을 통해서 클릭이벤트를 다른 데이터로 바꿔서, subscribe를 통해 그 데이터를 전달할 수 있다.
image
2. Observable 병합
2개의 버튼클릭으로 각 데이터를 입력받고, 그 이벤트를 한꺼번에 처리할 때, 각 Rxview.clicks 이벤트를 Observable에 담은 뒤, .merge로 이벤트를 합쳐서 한꺼번에 처리할 수 있다.
image
3. Observable 컴바인
어떤 2개가 모두 처리가 되어야지, 특정 버튼이 활성화된다고 했을 때, 2가지 데이터가 다 처리되었는지 쉽게 확인할 수 있다.
예를들어, GooglePlaceApi를 사용해서, 건물명을 검색하고, 지번주소는 getAddress를 통해 확인이 되었을 때, 선택버튼이 활성화되게 하는 경우다.

image



Eventbus로 사용할 수 있다.

Subject라는 객체는, Observable과 observer가 모두 가능하므로, 데이터를 받고 전달해주는 Eventbus가 될 수 있다.
image

  • 중개자인 RxBus 클래스 만들기
    - 여기서 사용된 SerializedSubject는 옵져버블<->옵저버가 모두 가능한 subject객체다.
        필요하다면, SerializedSubject.hasObservers() 를 통해 Subscriber들이 있는지 boolean값으로 확인할 수 있다.
    image

  • 정보를 보내주는 Publisher(Observable)만들기
    image

  • 정보를 받는 Subscriber(Observer)만들기
    image



Realm과 같이사용하면 더 쉽게 데이터를 검색할 수 있다.

realm에 저장된 데이터를 가지고 올대, .asObservabale()로 받은 다음,  rxjava의 .filter()를 쓰면 더 쉽게 데이터를 걸러낼 수 있고, 마지막엔 subscribe()만 해주면 된다.
image



Retrofit과 같이 사용하여 다중api호출 및 결합을 쉽게할 수 있다.

image

Rx를 안드로이드에 활용하는 가장 많은 부분은 네트워크 모듈이다.

Retrofit2만을 사용해서 단순한 API호출의 비동기통신을 1회성만 하는게 아니라, 여러 비동기API통신을 처리할 수 있다.

  • Retrofit의 API통신Interface 작성시 with Rxjava2
    - Call<모델> 객체가 아니라 Observable< 모델>을 통해 받는다. (간단하면 Single / 1만개이상 데이터는 Flowable 대체가능)
    image

  • Retrofit의 단일 API를 호출할 때, with Rxjava2
    - 만약, 하나의 api만 호출할 것이라면 굳이 rxjava2를 쓸 필요가 없다. call객체를 이용해서 call.enqueue를 호출하면, 자동으로 onResponse와 onFailure함수를 만들어주고 처리해주면된다. 오히려 rxjava2가 더 복잡해진다.

    image
    image

  • Retrofit의 다중 API를 호 때, with Rxjava2
    image
    -하나의 api를 호출한 뒤, 받은 데이터에서 또다른 데이터를 추출하여 api통신을 해야한다고 가정하자.
      예를 들어, wordprees의 각 정보를 파싱해왔는데, 그속에 imageUrl이 있다. 그 imageUrl을 가지고 이미지를 불러와야할 경우, a속에 b를 꺼내와야한다.
    -여기서의 예제는 아래와 같다.
      1.  Vod 영상정보를 가져오기 위한 Vod API호출
      2. Vod API의 response속에 있는 PlayUrl를 가지고 또다른 PlayUrl API호출
      3. PlayUrl Api의 response를 이용해 Vod 재생

    # Rxjava2없이 Retrofit2만으로 다중api호출
    - call객체를 받은뒤, 또다른 타입의 call객체를 받아와서 호출하는 식이다.
    image



    #Rxjava2를 이용한 다중API 호출하기
       - flatmap{ Vod -> 함수처리(Vod.get속내용() ) return ~)을 이용하여, a속의 b 혹은 다중api호출 가능 (2중이 아니라 3중 4중도 가능하다)
    image



    #Rxjava2를 이용한 여러API호출 후 결합하기
      - 여러 API들을 불러오고, 그것을 다 합친 후, 넘겨주는 경우이다.
      1.  VoiAPi와 LiveApi를 각각 호출한다.
      2.  두 APi의 response가 모두 도착할때까지 대기한다.
      3.  두 Api의 response를 결합한다.
    image

    먼저, rxjava2없이 retrofit만으로 여러api를 호출한다면
    - 하나의 변수를 따로 만들어, 통신에 성공했을 때, ++를 통해서 카운팅 해준다음, 전부 통신이 완료되어, 최종 카운팅 되는 곳에서 2개를 모두 diplay해주게 만든다. (좋지 않은 로직)
    image


    rxjava2를 이용해서 여러 api를 통신하고, 완료후 합친다면, zip()을 이용해서 각 Single객체들의 response들을 모아 새로운 Single을 만든다.
    image



Rxandroid도입의 단점

  • 진입장벽이 높고 / 코드가 오히려 복잡해질 수 도 있으며/ android studio 2점대 버전에서는 바로 사용이 안된다.
    -> java+kotlin을 사용하거나 java8설치 + Lambda를 이용해야한다.
  • 메모리관리에 유의하여야한다.
    -> subscriber 했으면, unsubscriber


Rxandroid도입의 장점

  • 다양한 비동기 처리 가능 ( 순차적(a속에 b)으로 실행되는 비동기통신) -> .flatmap()으로 처리
  • 복수의 비동기처리를 완료후 결과값 합칠 때 -> .merge()로 처리
  • 연속 클릭 이벤트 발생으로 인한 이벤트 중복실행 제어 -> 공부해야됨
  • 콜백지옥 탈출 -> unsubscriber
  • 모든 비동기작업은 Observabale<T> 형식으로 해결
  • 다양한 쓰레드관리 가능
  • 코드가 깔끔해짐(Lambda사용시)
  • 읽기전용의 Observable도 가능



***다양한 블로그들을 검색해서 수집된 정보들입니다!

싱글톤(Singleton)패턴

2018. 3. 20. 01:12

싱글톤 패턴을 사용하는 이유

자바에서는 new생성자();로 객체를 생성할 때, JVM의 heap영역에 올려놓고 참조하며, 사용후 제거를 해야한다. 하지만 생성한 객체를 사용하지 않을 수도 있고 객체관리를 못하게 되면 성능이 떨어지게 될 것이다.

싱글톤 패턴 : 객체를 단 하나만 생성하도록 보장시켜놓고 어디서든 이 객체에 접근할 수 있도록 한다.

주로 Preference나 Application클래스를 상속받아 context를 얻어 올 때, 혹은 OAuth2.0을 사용할 때 쓴다.

싱글톤 패턴을 사용하는 방법

  1. 외부에서 접근못하고(private) 따로 놀지 못하게 클래스만의 변수(static)을 만든다
  2. 외부에서 객체를 만들어주는 생성자를 접근못하도록(private)하면서 빈칸{}으로 비워둔다.
  3. 외부에서 접근(public static)하여 객체를 반환받을 수 있도록 getInstance()메소드를 만든다.
    객체가 없을 때만 만들어서 반환, 객체가 이미 있다면 있는 것을 반환한다.
    image


이러한 방식으로 생성한다면, 여러 쓰레드에서 동시 접근할 때, 중복으로 객체를 생성할 수도 있는 문제가 생길 수 있다.
예를 들어, 스레드1에서 객체를 불러오는과정에서 if문에 들어가 새로운 객체를 생성하기 직전에, 스레드2에서도 호출되서 if문을 통과해 객체를 생성하도록 해버리는 경우이다. 이러한 문제점을 해소하기 위해 다음과 같은 방식으로 만들 수 있다.


Static holder Singleton pattern(스태틱 홀더 싱글톤 패턴)

  1. 먼저 예시와 동일하게, 생성자를 private + 빈칸{}으로 비워두어, 외부에서의 객체생성을 막는다.
  2. 객체클래스안에 이너클래스로서 static class를 만들어 클래스내부에서만 객체생성 및 객체저장하는 이너클래스로 InstanceHolder의 역할을 하게한다.
    이 홀더 클래스에서는 미리 객체를 만들어 놓고 static final로 만들어 외부에서 객체를 상수처럼 변경없이 사용한다.
  3. public static 으로 외부에서 호출시 객체를 반환하는 getInstance()함수를 정의해준다.image



결과값

싱글톤객체를 2개 생성하여서 각각 hashCode를 찍어보았는데
hashCode값이 같다 = JVM에서 같은 객체를 호출하였다.

image

+ Recent posts