Android + Redux = <3

¡Oye! Mi nombre es Vitaly Sulimov, soy desarrollador de Android en Wheely y hoy me gustaría hablarles sobre la arquitectura de las aplicaciones móviles. A saber, cómo en la empresa aplicamos la arquitectura Redux a nuestras dos aplicaciones y qué resultó de ella.





Descargo de responsabilidad n. ° 1





Android- 2016- , MVC, MVP, Moxy Arello Mobile, Clean Architecture Redux. — . , , . , , , . — Android-.





?

Redux, , :





1. View





View — , -, , , — (, , , , Pull To Refresh, ..)





2. , - /





, . 





— , runtime, , , .





3.





, , . , / ..





4.





, , , - . , , : , . 





Redux, !

Redux Android-, , , Android.





Redux . , Store, , , - , , - . , , Redux.





State





, Redux. () . 





Store





Store (State) , Middleware Reducer





API , (Action), .





Action





, , Store. ( ). .





Reducer





, (Action). , , () ( , , , ..).





, Reducer Copy-on-write , .





Middleware





Middleware , (Action) , , Reducer.





Middleware , - . 





Redux Android?

. Android (Action), , , Activity, View, BroadcastReceiver - Action , ( ).





Talk is cheap. Show me the code.





, , Redux , . Counter, , . . .





?





Android Studio Redux Kotlin.





GitLab.





Redux, Android. , , , !





#2





, Rx, Coroutines, - , , . KISS, , .









, Redux? , Copy-on-write. , Kotlin - data class. 





ApplicationState.kt





data class ApplicationState(
    val counter: Int = 0
)
      
      



, , .









Action Redux. Action . Middleware Reducer, sealed class’, , . .





CounterAction.kt





sealed class CounterAction : Action {
  
    object Increment : CounterAction()
    
    object Reset : CounterAction()
}
      
      



Reducer





, Reducer<S>, S - , .. ApplicationState. - - reduce. , .





CounterReducer.kt





object CounterReducer : Reducer<ApplicationState> {
  
    override fun reduce(action: Action, state: ApplicationState): ApplicationState =
        when (action) {
            is CounterAction.Increment ->
                state.copy(counter = state.counter.inc())
                
            is CounterAction.Reset ->
                state.copy(counter = 0)
                
            else ->
                state
        }
}
      
      



Store





, Store





Store . - AbstractStore<S> . Middleware Reducer.





ApplicationStore.kt





class ApplicationStore(
    initialState: ApplicationState,
    middlewares: List<Middleware<ApplicationState>>,
    reducers: List<Reducer<ApplicationState>>
) : AbstractStore<ApplicationState>(initialState, middlewares, reducers)
      
      



ApplicationStore, Middleware Reducer. Store, ApplicationState - AppComponent Store .





AppComponent.kt





object AppComponent {
    val store = ApplicationStore(
        initialState = ApplicationState(),
        middlewares = emptyList(),
        reducers = listOf(CounterReducer)
    )
}
      
      



, , .





ReduxFunctions.kt





fun dispatch(action: Action) =
    AppComponent.store.dispatch(action)
    
fun subscribe(subscription: Subscription<ApplicationState>) =
    AppComponent.store.subscribe(subscription)
    
fun unsubscribe(subscription: Subscription<ApplicationState>) =
    AppComponent.store.unsubscribe(subscription)
      
      







, , , , reducer, , . , . , reducer’a, , store . , UI!





Android-. (Single Activity / Multiple Activities / Fragments?), , - Activity View. Activity View, .





CounterView.kt





class CounterView(
    context: Context
) : FrameLayout(context) {
  
    private val counterSubscription = SubStateSubscription<ApplicationState, Int>(
        transform = { it.counter },
        onStateChange = { state: Int, _: Boolean -> handleCounterStateChange(state) }
    )
    
    private lateinit var counterTextView: TextView
    private lateinit var floatingActionButton: FloatingActionButton
  
    init {
        inflate(context, R.layout.view_counter, this)
        findViewsById()
        setOnClickListeners()
    }
    
    private fun findViewsById() {
        counterTextView = findViewById(R.id.counterTextView)
        floatingActionButton = findViewById(R.id.floatingActionButton)
    }
    
    private fun setOnClickListeners() {
        floatingActionButton.setOnClickListener { dispatch(CounterAction.Increment) }
    }
    
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        subscribeToStateChanges()
    }
    
    private fun subscribeToStateChanges() {
        subscribe(counterSubscription)
    }
    
    override fun onDetachedFromWindow() {
        unsubscribeFromStateChanges()
        super.onDetachedFromWindow()
    }
    
    private fun unsubscribeFromStateChanges() {
        unsubscribe(counterSubscription)
    }
    
    private fun handleCounterStateChange(state: Int) {
        counterTextView.text = state.toString()
    }
}
      
      



, . SubStateSubscription, , , - , , Rx, map(), - .





, lateinit var View. 





. XML-, Floating Action Button. dispatch CounterAction.Increment, . 





OnViewAttached / Detached from window.





, , . , TextView.





CounterView.kt





...
counterTextView.text = state.toString()
...
      
      



! .





, , View , counter ApplicationState, , by design, Application View , … , , , ( “”). ? .





, Redux Android

, Android Redux. . AppCompatActivity, AppCompatActivity : Activity ActivityLifecycleAction ( ) . - AppCompatActivity Store, . .





MainActivity.kt





class MainActivity : AppCompatActivity<ApplicationState>() {
  
    private lateinit var contentViewGroup: ViewGroup
  
    override fun getStore(): Store<ApplicationState> =
        AppComponent.store
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewsById()
        addCounterView()
    }
    
    private fun findViewsById() {
        contentViewGroup = findViewById(R.id.contentViewGroup)
    }
    
    private fun addCounterView() {
        contentViewGroup.addView(CounterView(context = this))
    }
}
      
      



Middleware





- MIddleware. , : Activity (onDestroy) isFInishing == true - . 





isFinishing , true, , , false - , . 





, , . , Middleware<S>, S - handleAction().





ActivityLifecycleMiddleware.kt





object ActivityLifecycleMiddleware : Middleware<ApplicationState> {
  
    override fun handleAction(
        action: Action,
        state: ApplicationState,
        next: Next<ApplicationState>
    ): Action {
        val newAction = when (action) {
            is ActivityLifecycleAction.OnDestroy ->
                handleActivityOnDestroy(action)
                
            else ->
                action
        }
        return next(newAction, state)
    }
    
    private fun handleActivityOnDestroy(action: ActivityLifecycleAction.OnDestroy): Action =
        if (action.isFinishing) CounterAction.Reset else action
}
      
      



, . ActivityLifecycleAction.OnDestroy Reducer, Middleware, , . , isFinishing == true, Reducer CounterAction.Reset, , false - , , , . middleware AppComponent-.





AppComponent.kt





store = ApplicationStore(
    initialState = ApplicationState(),
    middlewares = listOf(ActivityLifecycleMiddleware),
    reducers = listOf(CounterReducer)
)
      
      



!





Redux. , , — . , JavaScript. , , , . . - - Action. - Middleware, - Reducer. View , .









, , Counter, Middleware Reducer , . Redux- — “”, Android ( ), API OpenWeatherMap. .





https://gitlab.com/v.sulimov/android-redux-kotlin





https://gitlab.com/v.sulimov/android-redux-demo





https://gitlab.com/v.sulimov/android-openweather-kotlin









, Redux Android, . , , , , Redux , , . , - .



, , .





, . Wheely.








All Articles