SundayBird / project

0 stars 0 forks source link

[2019.02.17] Counter Application with ReactiveX #9

Closed getogrand closed 5 years ago

getogrand commented 5 years ago

현재 숫자와 up, down 버튼이 있는 간단한 카운터 어플리케이션을 ReactiveX를 이용해 만들어 봅시다.

image

getogrand commented 5 years ago

공부하면서 이해가 안 가는 부분이 있네요.

ReactiveX 공식 홈 Introduction 문서에 아래와 같이 써있는데요.

It is sometimes called “functional reactive programming” but this is a misnomer. ReactiveX may be functional, and it may be reactive, but “functional reactive programming” is a different animal. One main point of difference is that functional reactive programming operates on values that change continuously over time, while ReactiveX operates on discrete values that are emitted over time. (See Conal Elliott’s work for more-precise information on functional reactive programming.)

continuously한 값의 변경을 여러번 다루는 것과 discrete한 값의 변경을 여러번 다루는 것이 어떻게 다른지 잘 이해가 안갑니다. 혹시 아시는 분 계실까요?

kyunooh commented 5 years ago
<button id="btn-increase">increase</button>
<p id="count">0</p>
<button id="btn-decrease">decrease</button>

<script src="https://npmcdn.com/@reactivex/rxjs@5.0.0-beta.8/dist/global/Rx.umd.js"></script>
<script>

let count = 0;

const updateCount = (count) => {
    console.log(count);
    document.getElementById("count").innerText = count
};

Rx.Observable.fromEvent(document.getElementById('btn-increase'), 'click')
    .map(() => count++)
    .subscribe((x) => updateCount(x));
Rx.Observable.fromEvent(document.getElementById('btn-decrease'), 'click')
    .map(() => count--)
    .subscribe((x) => updateCount(x));

</script>
EJSohn commented 5 years ago
import (
    "bufio"
    "fmt"
    "os"

    "github.com/reactivex/rxgo/handlers"
    "github.com/reactivex/rxgo/observable"
)

func main() {
    num := 0
    reader := bufio.NewReader(os.Stdin)

    onNext := handlers.NextFunc(func(item interface{}) {
        if text, ok := item.(string); ok {
            switch {
            case text == "u\n":
                num += 1
            case text == "d\n":
                num -= 1
            default:
                fmt.Println("Input has to be 'u' or 'd'")
                return
            }
        }

        fmt.Printf("Current num: %d\n", num)
    })

    for {
        fmt.Print("type> ")

        sub := observable.Start(func() interface{} {
            text, err := reader.ReadString('\n')
            if err != nil {
                return err
            }
            return text
        }).Subscribe(onNext)

        <-sub
    }
}
pjhjohn commented 5 years ago
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="counterValue"
            type="long"/>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/counter"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            android:autoSizeMaxTextSize="120sp"
            android:autoSizeMinTextSize="20sp"
            android:text="@{ String.valueOf(counterValue) }"
            android:textSize="120sp"
            android:textStyle="bold"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/panel_barrier"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            tools:text="100"/>

        <Button
            android:id="@+id/counter_down"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@string/counter_down"
            android:textSize="18sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/counter_up"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.5"/>

        <Button
            android:id="@+id/counter_up"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@string/counter_up"
            android:textSize="18sp"
            app:layout_constraintStart_toEndOf="@+id/counter_down"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.5"/>

        <androidx.constraintlayout.widget.Barrier
            android:id="@+id/panel_barrier"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:barrierDirection="top"
            app:constraint_referenced_ids="counter_down,counter_up"
            tools:layout_editor_absoluteY="731dp"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main).apply {
    // 1. View.setOnClickListener
    counterValue = 0

    counterDown.setOnClickListener {
        counterValue -= 1
    }
    counterUp.setOnClickListener {
        counterValue += 1
    }

    // 2. RxAndroid
    counterValue = 0

    val minusPublishSubject: PublishSubject<Long> = PublishSubject.create<Long>()
    counterDown.setOnClickListener {
        minusPublishSubject.onNext(-1L)
    }
    val plusObservable: Observable<Long> = Observable.create<Long> { emitter ->
        counterUp.setOnClickListener {
            emitter.onNext(1L)
        }
    }.doOnDispose { counterUp.setOnClickListener(null) }

    disposable = Observable.merge(minusPublishSubject, plusObservable)
        .subscribeOn(Schedulers.computation())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({ counterValue += it }, Throwable::printStackTrace, {})
}
joshuakimDwan commented 5 years ago
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>RxJS</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
</head>
<body>
  <span id="currNum">0</span>
  <button id="increaseBtn">Increase</button>
  <button id="decreaseBtn">Decrease</button>

  <script>
    document.addEventListener("DOMContentLoaded", () => {
      rxjs.fromEvent(document.getElementById("increaseBtn"), "click").subscribe(() => {
        const currNumEle = document.getElementById("currNum");
        const currNum = parseInt(currNumEle.innerText);
        currNumEle.innerText = currNum + 1;
      });

      rxjs.fromEvent(document.getElementById("decreaseBtn"), "click").subscribe(() => {
        const currNumEle = document.getElementById("currNum");
        const currNum = parseInt(currNumEle.innerText);
        currNumEle.innerText = currNum - 1;
      });
    });
  </script>
</body>
</html>
pjhjohn commented 5 years ago

Regex Utility WebSites

Visites ReactiveX Sites / Documents

pjhjohn commented 5 years ago

Remote & Local Caching

fromRemoteCache =
    Api.fetchItems() // Single for most of HTTP API Call
        .subscribeOn(Schedulers.io())
        .toObservable() // Single => Observable
        .publish() // Observable => PublishSubject
        .refCount(); // Dispose if refCount goes 0
fromLocalCache = 
    Observable.fromCallable(LocalStorage::loadItems)
        .subscribeOn(Schedulers.io())
        .takeUntil(fromRemoteCache) // Stream halts if [fromRemoteCache] emits any item
return Observable.merge(fromRemoteCache, fromLocalCache);

// .share() is equal to .publish().refCount() for multicast
// fromRemoteCache.subscribe(Observer1) // refCount: 1 (Initialize data source)
// fromRemoteCache.subscribe(Observer2) // refCount: 2 (Use initialized data)
// fromRemoteCache.subscribe(Observer3) // refCount: 3 (Use initialized data)
// fromRemoteCache.unSubscribe(Observer1) // refCount: 2
// fromRemoteCache.unSubscribe(Observer2) // refCount: 1
// fromRemoteCache.unSubscribe(Observer3) // refCount: 0 (dispose observable defined in .doOnDispose{...})
// fromRemoteCache.subscribe(Observer1) // refCount: 1 (Initialize data source)
// fromRemoteCache.subscribe(Observer2) // refCount: 2 (Use initialized data)
// fromRemoteCache.unSubscribe(Observer1) // refCount: 1
// fromRemoteCache.unSubscribe(Observer2) // refCount: 0 (dispose observable defined in .doOnDispose{...})

Observable.publish