Skip to content

ksu3101/Arch-ReduxMvvm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

30 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Redux Based android architecture

이 ν”„λ‘œμ νŠΈλŠ” Redux λ₯Ό 기반으둜 ν•œ μ•ˆλ“œλ‘œμ΄λ“œ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ 아킀텍쳐λ₯Ό 정리 ν•œ ν”„λ‘œμ νŠΈ 이닀. Android architecture blueprintλ₯Ό 기반으둜 λ§Œλ“€μ–΄μ‘Œλ‹€.

μ‚¬μš©λœ 도ꡬ 및 라이브러리λ₯΄ κ°„λ‹¨ν•˜κ²Œ 정리 ν•˜λ©΄ λ‹€μŒκ³Ό κ°™λ‹€. (계속 μˆ˜μ •λ  수 있음)

  • Architecture
    • androidx
    • MVVM
    • Databinding
    • Koin (DI)
  • Event handler
    • Rx2.x
  • Network
    • retrofit 2.x
    • moshi
    • glide
  • Testing
    • jUnit
    • mockito (λŒ€μ²΄ ν•  수 μžˆλŠ” 쒋은 ν…ŒμŠ€νŒ… 도ꡬ가 μžˆλŠ”μ§€?)
    • Koin-testing

Redux

기본적인 Redux 에 λŒ€ν•œ μ„€λͺ…은 링크λ₯Ό μ°Έκ³  ν•˜λ©΄ λœλ‹€.

Android architecture

Android μ—μ„œ redux λ₯Ό 기반으둜 ν•œ uni-directional data flow(UDA) λ₯Ό κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„œλŠ” reactive stream 을 이용 ν•˜μ—¬ 단방ν–₯ μŠ€νŠΈλ¦Όμ„ 톡해 μƒμ„±λ˜λŠ” 객체 data λ₯Ό κ΅¬λ…ν•˜λŠ” λ°©λ²•μœΌλ‘œ κ΅¬ν˜„ ν•œλ‹€.

redux 기반 ꡬ쑰 μ—μ„œλŠ” μž₯, 단점이 쑴재 ν•˜λ‹€. κ°€μ‘΄ java 둜 κ΅¬ν˜„λ˜μ—ˆλ˜ μ½”λ“œ μ—μ„œλŠ” λ°˜λ³΅λ˜λŠ” μ½”λ“œκ°€ λ„ˆλ¬΄ λ§Žμ•„ κ³¨μΉ˜κ°€ μ•„νŒ μ§€λ§Œ κ·Έλ‚˜λ§ˆ kotlin 으둜 적용 ν•˜λ©΄μ„œ 반볡 μ½”λ“œλ₯Ό 많이 μ€„μ΄κΈ°λŠ” ν–ˆλ‹€. ν•˜μ§€λ§Œ κ·ΈλŸΌμ—λ„ λ¬Έμ œλŠ” μ—¬μ „νžˆ 쑴재 ν•œλ‹€. μ •λ¦¬λœ μž₯-단점은 μ•„λž˜μ™€ κ°™λ‹€.

  • μž₯점
    • use-case둜 μ •μ˜λœ 데이터 흐름을 비동기, λ™κΈ°λ‘œ κ΅¬ν˜„ν•˜μ—¬ 적용 ν•  수 μžˆλ‹€.
    • λΉ„μ¦ˆλ‹ˆμŠ€ μ½”λ“œμ™€ λ·° μ½”λ“œ μ™„μ „νžˆ 뢄리 되고 dependency λ₯Ό 정리 ν•˜μ—¬ μ½”λ“œ 가독성을 λ†’μ—¬ μœ μ§€, λ³΄μˆ˜κ°€ μ‰¬μ›Œμ§„λ‹€. (이건 MVVM 의 μž₯점)
  • 단점
    • Reduxꡬ쑰 λ₯Ό μ•Œμ•„μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— λŸ¬λ‹ μ»€λΈŒκ°€ λ†’λ‹€.
    • ν…ŒμŠ€νŒ… μ†ŒμŠ€λ₯Ό μž‘μ„± ν•˜λŠ”λ° λ°˜λ³΅λ˜λŠ” μ½”λ“œκ°€ 쑴재 ν•œλ‹€. (이 뢀뢄은 μ’€ 더 정리가 ν•„μš”ν•  λ“― ν•˜λ‹€)
    • Store ν•œκ°œμ•  쑴재 ν•˜λŠ” 단일 State ꡬ쑰에 μ—¬λŸ¬κ°œμ˜ Domain 을 κ°€μ§ˆ 수 μžˆλŠ” μ•ˆλ“œλ‘œμ΄λ“œ μ•± ꡬ쑰상 μ‹€μ œ Redux μ½”λ“œμ™€ λ‹¬λΌμ§ˆ 수 밖에 μ—†λ‹€.

android redux base1

기본적인 κ΅¬μ‘°λŠ” Redux λ₯Ό μ°Έκ³  ν•˜μ˜€μ§€λ§Œ ν™”λ©΄μƒμ—μ„œ λ³΄μ—¬μ§€λŠ” μƒνƒœλ₯Ό μ΅œμ†Œν™” ν•˜μ—¬λ„ νŒμ—… λ‹€μ΄μ–Όλ‘œκ·Έ, ν† μŠ€νŠΈ λ“± domain μ—μ„œ λ²—μ–΄λ‚œ 곡톡 ν™”λ©΄ λ³€κ²½ 등이 μžˆμ–΄ state, reducer λ“€μ˜ ꡬ쑰가 μ•½κ°„ λ‹€λ₯΄λ‹€.

μžμ„Έν•œ 흐름은 μ•„λž˜ 이미지와 μ„€λͺ…을 μ°Έκ³  ν•˜μž.

android redux base2

1. Action

interface Action

μ•ˆλ“œλ‘œμ΄λ“œμ—μ„œ μ •μ˜ 된 Action 은 각쒅 이벀트 와 νŠΉμ • Action 을 핸듀링 ν•˜κ³  λ‚œ λ’€ λ‹€μ‹œ 핸듀링 ν•˜κΈ° μœ„ν•΄μ„œ Dispatch 된 Success, Failed Action λ“± 이 쑴재 ν•œλ‹€.

Action μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ Action 은 Immutable Data class λ‘œμ„œ Action 을 미듀웨어, λ¦¬λ“€μ„œ μ—μ„œ 핸듀링 ν•˜κΈ° μœ„ν•œ λΆˆλ³€ 데이터듀을 담을 수 μžˆλ‹€. Action λ‹¨μˆœνžˆ 뷰에 λŒ€ν•œ μ—…λ°μ΄νŠΈλ₯Ό μš”κ΅¬ ν•˜λŠ” 트리거 μ΄λ²€νŠΈκ°€ 될 μˆ˜λ„ 있고, 데이터λ₯Ό λ‹΄μ•„ λ„€νŠΈμ›Œν¬ API λ₯Ό μ΄μš©ν•˜κ±°λ‚˜ Database μ—μ„œ 데이터λ₯Ό κ°€μ Έ μ˜€λŠ” λ“± 비동기 μž‘μ—…μ„ μš”μ²­ ν•  μˆ˜λ„ μžˆλ‹€.

1.1 Common Action

μ•ˆλ“œλ‘œμ΄λ“œμ—μ„œ κ³΅ν†΅μ μœΌλ‘œ μ‚¬μš© λ˜λŠ” 뷰듀에 λŒ€ν•œ Action λ“€ 이닀.

  • Toast
  • Dialog
  • Snackbar
  • κ·Έ μ™Έ μ»€μŠ€ν…€ λ©”μ‹œμ§€ λ·° λ“±...

1.2 Domain Action

도메인에 νŠΉν™”λ˜μ–΄ 각 use-case λ₯Ό μ •μ˜ν•œ Action λ“€ 이닀.

2. State

interface State

Store μ—μ„œλŠ” 단 ν•œκ°œμ˜ State 만 을 κ°€μ§ˆ 수 μžˆλ‹€. ν•˜μ§€λ§Œ 곡톡 state λ‚˜ 각 도메인별 state κ°€ 증가 ν•˜κ²Œ λœλ‹€λ©΄ Store 의 State μ—” λͺ¨λ‘ μ €μž₯ ν•  수 μ—†λ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— State λŠ” AppState λΌλŠ” ν•œκ°œμ˜ State λ₯Ό Store 에 μ €μž₯ ν•˜λ˜, AppState 의 내뢀에 곡톡, 도메인 State λ₯Ό κ°–κ²Œ ν•΄ μ€€λ‹€.

ν•˜μ§€λ§Œ λ¬Έμ œκ°€ μžˆλ‹€λ©΄ 각 도메인 λ“± μ—μ„œ μžμ‹ μ˜ State λ₯Ό ꡬ독 ν• λ•Œλ₯Ό μ œμ™Έν•  λ•Œ getCurrentState() 을 ν†΅ν•΄μ„œ State λ₯Ό 얻을 λ•Œ 이닀. get ν•˜λ € ν•  λ•Œ μ–΄λ–€ State λ₯Ό 얻어와야 ν•˜λŠ”μ§€ λͺ¨λ₯΄λ―€λ‘œ AppState μ—μ„œ ν•„μš”ν•œ State λ₯Ό κΊΌλ‚΄μ˜¬ 수 μžˆλŠ” μ΅œμ†Œν•œμ˜ 정보가 ν•„μš”ν•˜λ‹€.

  • AppState λ‚΄λΆ€μ—μ„œ sub domain Stateλ₯Ό 관리 ν•˜λŠ” 방법
  • Array, List :
    • κ°„λ‹¨ν•œ ꡬ쑰 μ΄μ§€λ§Œ, State κ°€ ν•˜λ‚˜λΌλ„ κ°±μ‹  되면 Array λ‚˜ List λ₯Ό λ‹€μ‹œ 생성해야 ν•œλ‹€.
  • Map(String, State) : Key String 으둜 State 의 class name λ“± μœ λ‹ˆν¬ ν•œ 값을 기반으둜 State λ₯Ό μ €μž₯ ν•˜κ³  κ°±μ‹ ν•˜κ²Œ ν•œλ‹€.

2.1 Common State

μ•ˆλ“œλ‘œμ΄λ“œ κ³΅ν†΅μ μœΌλ‘œ μ‚¬μš© λ˜λŠ” 뷰에 λŒ€ν•œ μƒνƒœλ“€ 이닀. μœ„ Action 을 ν†΅ν•΄μ„œ 보여쀄 λ©”μ‹œμ§€ 및 데이터λ₯Ό λ°›μ•„ ViewModel λ“± μ—μ„œ 핸듀링 ν•  수 있게 ν•œλ‹€.

2.2 Domain State

도메인에 νŠΉν™”λ˜μ–΄ 각 use-case 에 λŒ€μ‘ λ˜λŠ” λ·° λ³€ν™”λ₯Ό μƒνƒœ 데이터 둜 μ •μ˜ ν•œ State 이닀.

3. Middleware

typealias Dispatcher = (Action) -> Unit

interface MiddleWare<S: State> {
  fun create(store: Store<S>, next: Dispatcher): Dispatcher
}

Action 을 Reducer 에 전달 ν•˜κΈ° μœ„ν•΄ 쀑간에 κ°œμž…ν•˜μ—¬ 데이터λ₯Ό μ œμ–΄ ν•œλ‹€.

μžμ„Έν•˜κ²Œ 보면, Store λ₯Ό 톡해 Dispatch 된 Action 을 μ œμ–΄ ν•˜μ—¬ μƒˆλ‘œμš΄ Action(Success, Failed) 을 μƒμ„±ν•˜μ—¬ Reducer 에 전달 ν•œλ‹€. κΌ­ 생성할 ν•„μš”λŠ” 없이 μ€‘κ°„μ—μ„œ Action κ³Ό κ΄€λ ¨λœ μž‘μ—… ν›„ ν•΄λ‹Ή Action 을 κ·ΈλŒ€λ‘œ λ¦¬λ“€μ„œμ— 전달 해도 상관없닀.

3.1 ActionProcessor Middleware

interface ActionProcessor<S: State> {
  fun run(action: Observable<Action>, store: Store<S>): Observable<out Action>
}

dispatch 된 Action 듀을 λ°›μ•„ λ‚΄λΆ€μ—μ„œ ν”„λ‘œμ„Έμ‹± ν•˜κ³  λ‚œ λ’€ λ‹€μŒ 미듀웨어에 전달 ν•œλ‹€. Action processor middleware λ‚΄ μ—λŠ” μ—¬λŸ¬κ°œμ˜ Action processor κ°€ 쑴재 ν•˜λ©° 이 듀을 μ΄ν„°λ ˆμ΄μ…”λ‹ ν•˜κ³  λ‚˜μ˜¨ Success ν˜Ήμ€ Failed Action 을 λ‹€μŒ 미듀웨어에 전달 ν•˜κ±°λ‚˜ λ‹€μ‹œ μƒˆλ‘œμš΄ Action 을 dispatch ν•˜κ²Œ ν•  μˆ˜λ„ μžˆλ‹€.

3.2 Logger Middlewar

class LoggerMiddleware<S : State> : Middleware<S> {
    override fun create(store: Store<S>, next: Dispatcher): Dispatcher {
        return { action: Action ->
            if (BuildConfig.DEBUG) {
                Log.d(LOG_TAG, "action dispatch : [${action.getSuperClassNames()}] $action")
            }
            val prevState = store.getCurrentState()
            next(action)

            if (BuildConfig.DEBUG) {
                val currentState = store.getCurrentState()
                if (prevState != currentState) {
                    (currentState as AppState).printStateLogs()
                }
            }
        }
    }
}

졜초 dispatch 된 Action κ³Ό Success/Failed Action λ“± Result Action κΉŒμ§€μ˜ λ³€ν™”λ₯Ό λ‘œκΉ… ν•΄ μ£ΌλŠ” 미듀웨어 이닀. 이 λ―Έλ“€μ›¨μ–΄λŠ” μ‹€μ œ Action 의 생성 에 κ΄€μ—¬ν•˜μ§€ μ•ŠμœΌλ©° λ‹¨μˆœνžˆ λ‚΄λΆ€μƒνƒœλ§Œ 읽고 λ””λ²„κΉ…μš© λ‘œκ·Έλ©”μ‹œμ§€λ‘œ 좜λ ₯ ν•˜λ„λ‘ ν•œλ‹€.

4. Reducer

interface Reducer<S: State> {
  fun reduce(oldState: S, resultAction: Action): S
}

Middleware λ₯Ό 톡해 전달받은 (Result) Action 을 핸듀링 ν•˜μ—¬ 단 ν•œκ°œμ˜ State λ₯Ό λ§Œλ“ λ‹€. State λŠ” 이전 State 일 μˆ˜λ„ μžˆλ‹€. State λŠ” 화면을 그리기 μœ„ν•œ 기반 데이터λ₯Ό 담은 Immutable data class 이닀.

4.1 AppReducer

class AppReducer: Reducer<AppState>, KoinComponent {
  override fun reduce(oldState: AppState, resultAction: Action): AppState {
    // ...
  }
}

AppState λ₯Ό μ œμ–΄ ν•˜κΈ° μœ„ν•œ Reducer 이닀.

4.2 Domain Reducer

각 Domain λ³„λ‘œ κ°–κ²Œλ˜λŠ” Reducer λ“€ 이닀. λ‚΄λΆ€μ—μ„œ Action 을 전달 λ°›μ•„ ν•„μš”ν•œ 경우 핸듀링 ν•˜κ³  μƒˆλ‘œμš΄ State λ₯Ό λ§Œλ“ λ‹€.

5. Store

interface Store<S : State> {
    fun dispatch(action: Action)
    fun getStateListener(): Observable<S>
    fun getCurrentState(): S
}

μ΅œμ‹  State κ°€ μ €μž₯된 Store 이닀.

5.1 AppStore

class AppStore(
    initializeAppState: AppState,
    reducer: Reducer<AppState>
) : Store<AppState>, KoinComponent {
    private val stateEmitter: BehaviorSubject<AppState> = BehaviorSubject.create()
    private val middleWares: Array<MiddleWare<AppState>> = getKoin().get()
    private var appState: AppState = initializeAppState
    private var dispatcher: Dispatcher

    init {
        dispatcher = middleWares.foldRight({ dispatchedAction ->
            appState = reducer.reduce(appState, dispatchedAction)
            stateEmitter.onNext(appState)
        }) { middleWare, next ->
            middleWare.create(this, next)
        }
    }

    override fun dispatch(action: Action) {
        dispatcher(action)
    }

    override fun subscribe(): Observable<AppState> =
        stateEmitter.hide().observeOn(AndroidSchedulers.mainThread())

    override fun getState(): AppState = appState
}

storeλŠ” 단 ν•œκ°œλ§Œ 쑴재 ν•˜λ©° stre μ—λŠ” 단 1개의 state 만 κ°€μ§ˆ 수 μžˆλ‹€. 그것이 AppState 이닀. ν•˜μ§€λ§Œ μ•ˆλ“œλ‘œμ΄λ“œ μ—μ„œλŠ” μ—¬λŸ¬κ°œμ˜ μƒνƒœκ°€ 쑴재 ν•  수 μžˆλ‹€. (예λ₯Ό λ“€μ–΄ ν˜„μž¬ 도메인 ν™”λ©΄ κ³Ό 화면에 μ—…λ°μ΄νŠΈ 될 ν† μŠ€νŠΈ, λ‹€μ΄μ–Όλ‘œκ·Έ 및 상단 λ°” λ“± ui λ³€ν™” 및 비동기 μž‘μ—… 으둜 μΈν•œ μ„Έμ…˜ λ“± λ³€ν™”) μ—¬λŸ¬κ°œμ˜ μƒνƒœλ₯Ό λ§Œλ“€λ˜ 이 λ₯Ό store μ—μ„œ κ°–κ²Œ ν•˜λ €λ©΄ μ—¬λŸ¬κ°œμ˜ AppState κ°€ μ•„λ‹Œ AppState 내뢀에 자료ꡬ쑰(list λ‚˜ map λ“±) 을 두어 각 도메인별 μœ λ‹ˆν¬ν•œ μƒνƒœλ₯Ό 관리 ν•˜κ²Œ ν•΄ μ€€λ‹€.

AppStore μ—μ„œλŠ” 이 AppState λ₯Ό κ°–κ³  있으며 이 state λ₯Ό ꡬ독 ν•  수 μžˆλŠ” listener 등을 제곡 ν•œλ‹€. 그리고 Action 을 dispatch ν•  μˆ˜λ„ μžˆλ‹€.

MVVM

μž‘μ„±μ€‘...

ViewModel

render()

abstract fun render(state: S): Boolean

Binding utils

LiveData

Koin

μž‘μ„±μ€‘...

vs Dagger

modules

Testing

μž‘μ„±μ€‘...

JunitTesting

mockito in kotlin

testing with Koin

About

Redux based Unidirectional data flow Android architecture. (+Kotlin, Rx2, MVVM, DI-Koin...)

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages