μ΄ νλ‘μ νΈλ 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 μ λν μ€λͺ μ λ§ν¬λ₯Ό μ°Έκ³ νλ©΄ λλ€.
Android μμ redux λ₯Ό κΈ°λ°μΌλ‘ ν uni-directional data flow(UDA) λ₯Ό ꡬννκΈ° μν΄μλ reactive stream μ μ΄μ© νμ¬ λ¨λ°©ν₯ μ€νΈλ¦Όμ ν΅ν΄ μμ±λλ κ°μ²΄ data λ₯Ό ꡬλ νλ λ°©λ²μΌλ‘ ꡬν νλ€.
redux κΈ°λ° κ΅¬μ‘° μμλ μ₯, λ¨μ μ΄ μ‘΄μ¬ νλ€. κ°μ‘΄ java λ‘ κ΅¬νλμλ μ½λ μμλ λ°λ³΅λλ μ½λκ° λ무 λ§μ 골μΉκ° μν μ§λ§ κ·Έλλ§ kotlin μΌλ‘ μ μ© νλ©΄μ λ°λ³΅ μ½λλ₯Ό λ§μ΄ μ€μ΄κΈ°λ νλ€. νμ§λ§ κ·ΈλΌμλ λ¬Έμ λ μ¬μ ν μ‘΄μ¬ νλ€. μ 리λ μ₯-λ¨μ μ μλμ κ°λ€.
- μ₯μ
- use-caseλ‘ μ μλ λ°μ΄ν° νλ¦μ λΉλκΈ°, λκΈ°λ‘ κ΅¬ννμ¬ μ μ© ν μ μλ€.
- λΉμ¦λμ€ μ½λμ λ·° μ½λ μμ ν λΆλ¦¬ λκ³ dependency λ₯Ό μ 리 νμ¬ μ½λ κ°λ μ±μ λμ¬ μ μ§, 보μκ° μ¬μμ§λ€. (μ΄κ±΄ MVVM μ μ₯μ )
- λ¨μ
- Reduxꡬ쑰 λ₯Ό μμμΌ νκΈ° λλ¬Έμ λ¬λ 컀λΈκ° λλ€.
- ν μ€ν μμ€λ₯Ό μμ± νλλ° λ°λ³΅λλ μ½λκ° μ‘΄μ¬ νλ€. (μ΄ λΆλΆμ μ’ λ μ λ¦¬κ° νμν λ― νλ€)
- Store νκ°μ μ‘΄μ¬ νλ λ¨μΌ State ꡬ쑰μ μ¬λ¬κ°μ Domain μ κ°μ§ μ μλ μλλ‘μ΄λ μ± κ΅¬μ‘°μ μ€μ Redux μ½λμ λ¬λΌμ§ μ λ°μ μλ€.
κΈ°λ³Έμ μΈ κ΅¬μ‘°λ Redux λ₯Ό μ°Έκ³ νμμ§λ§ νλ©΄μμμ 보μ¬μ§λ μνλ₯Ό μ΅μν νμ¬λ νμ λ€μ΄μΌλ‘κ·Έ, ν μ€νΈ λ± domain μμ λ²μ΄λ κ³΅ν΅ νλ©΄ λ³κ²½ λ±μ΄ μμ΄ state, reducer λ€μ κ΅¬μ‘°κ° μ½κ° λ€λ₯΄λ€.
μμΈν νλ¦μ μλ μ΄λ―Έμ§μ μ€λͺ μ μ°Έκ³ νμ.
interface Actionμλλ‘μ΄λμμ μ μ λ Action μ κ°μ’ μ΄λ²€νΈ μ νΉμ Action μ νΈλ€λ§ νκ³ λ λ€ λ€μ νΈλ€λ§ νκΈ° μν΄μ Dispatch λ Success, Failed Action λ± μ΄ μ‘΄μ¬ νλ€.
Action μΈν°νμ΄μ€λ₯Ό ꡬνν Action μ Immutable Data class λ‘μ Action μ λ―Έλ€μ¨μ΄, 리λμ μμ νΈλ€λ§ νκΈ° μν λΆλ³ λ°μ΄ν°λ€μ λ΄μ μ μλ€. Action λ¨μν λ·°μ λν μ λ°μ΄νΈλ₯Ό μꡬ νλ νΈλ¦¬κ±° μ΄λ²€νΈκ° λ μλ μκ³ , λ°μ΄ν°λ₯Ό λ΄μ λ€νΈμν¬ API λ₯Ό μ΄μ©νκ±°λ Database μμ λ°μ΄ν°λ₯Ό κ°μ Έ μ€λ λ± λΉλκΈ° μμ μ μμ² ν μλ μλ€.
μλλ‘μ΄λμμ 곡ν΅μ μΌλ‘ μ¬μ© λλ λ·°λ€μ λν Action λ€ μ΄λ€.
- Toast
- Dialog
- Snackbar
- κ·Έ μΈ μ»€μ€ν λ©μμ§ λ·° λ±...
λλ©μΈμ νΉνλμ΄ κ° use-case λ₯Ό μ μν Action λ€ μ΄λ€.
interface StateStore μμλ λ¨ νκ°μ 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 λ₯Ό μ μ₯ νκ³ κ°±μ νκ² νλ€.
μλλ‘μ΄λ 곡ν΅μ μΌλ‘ μ¬μ© λλ λ·°μ λν μνλ€ μ΄λ€. μ Action μ ν΅ν΄μ 보μ¬μ€ λ©μμ§ λ° λ°μ΄ν°λ₯Ό λ°μ ViewModel λ± μμ νΈλ€λ§ ν μ μκ² νλ€.
λλ©μΈμ νΉνλμ΄ κ° use-case μ λμ λλ λ·° λ³νλ₯Ό μν λ°μ΄ν° λ‘ μ μ ν State μ΄λ€.
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 μ κ·Έλλ‘ λ¦¬λμμ μ λ¬ ν΄λ μκ΄μλ€.
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 νκ² ν μλ μλ€.
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 μ μμ± μ κ΄μ¬νμ§ μμΌλ©° λ¨μν λ΄λΆμνλ§ μ½κ³ λλ²κΉ μ© λ‘κ·Έλ©μμ§λ‘ μΆλ ₯ νλλ‘ νλ€.
interface Reducer<S: State> {
fun reduce(oldState: S, resultAction: Action): S
}Middleware λ₯Ό ν΅ν΄ μ λ¬λ°μ (Result) Action μ νΈλ€λ§ νμ¬ λ¨ νκ°μ State λ₯Ό λ§λ λ€. State λ μ΄μ State μΌ μλ μλ€. State λ νλ©΄μ 그리기 μν κΈ°λ° λ°μ΄ν°λ₯Ό λ΄μ Immutable data class μ΄λ€.
class AppReducer: Reducer<AppState>, KoinComponent {
override fun reduce(oldState: AppState, resultAction: Action): AppState {
// ...
}
}AppState λ₯Ό μ μ΄ νκΈ° μν Reducer μ΄λ€.
κ° Domain λ³λ‘ κ°κ²λλ Reducer λ€ μ΄λ€. λ΄λΆμμ Action μ μ λ¬ λ°μ νμν κ²½μ° νΈλ€λ§ νκ³ μλ‘μ΄ State λ₯Ό λ§λ λ€.
interface Store<S : State> {
fun dispatch(action: Action)
fun getStateListener(): Observable<S>
fun getCurrentState(): S
}μ΅μ State κ° μ μ₯λ Store μ΄λ€.
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 ν μλ μλ€.
μμ±μ€...
abstract fun render(state: S): Boolean
μμ±μ€...
μμ±μ€...

