Skip to content

Commit f228bce

Browse files
committed
#12 provide option for runtime to listen for lifecycle changes and respond
1 parent e689edd commit f228bce

File tree

11 files changed

+52
-25
lines changed

11 files changed

+52
-25
lines changed

elmdroid/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ dependencies {
2525
implementation deps.kotlin.stdlib
2626

2727
// arch
28+
implementation deps.lifecycle.runtime
2829
implementation deps.lifecycle.viewmodel
2930
implementation deps.lifecycle.livedata
31+
implementation deps.lifecycle.java8
3032

3133
// rx
3234
implementation deps.rx_java2

elmdroid/src/main/java/cz/inventi/elmdroid/ElmArchitecture.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
package cz.inventi.elmdroid
22

3+
import android.arch.lifecycle.DefaultLifecycleObserver
4+
import android.arch.lifecycle.LifecycleOwner
35
import android.arch.lifecycle.LiveData
46
import io.reactivex.Observable
57
import io.reactivex.Single
68

79
/** UI 'loop' implementation that 'runs" given [Component]. */
8-
interface ComponentRuntime<STATE : State, in MSG : Msg> {
10+
interface ComponentRuntime<STATE : State, in MSG : Msg> : DefaultLifecycleObserver {
911
/** Provides always up to date [State]. */
1012
fun state(): LiveData<STATE>
1113
/** Dispatch [Msg] to handle. */
1214
fun dispatch(msg: MSG)
1315
/** Stops the inner loop, no more state updates. */
1416
fun clear()
17+
/** Runtime will be able to call [clear] by itself based on the [lifecycleOwner] */
18+
fun bindTo(lifecycleOwner: LifecycleOwner) {
19+
lifecycleOwner.lifecycle.addObserver(this)
20+
}
21+
override fun onDestroy(owner: LifecycleOwner) {
22+
clear()
23+
}
1524
}
1625

1726
/** Key class specifying all mayor pars of your elm architecture. */

elmdroid/src/main/java/cz/inventi/elmdroid/ElmViewModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import android.arch.lifecycle.ViewModel
66

77
open class ElmViewModel<STATE : State, in MSG : Msg, CMD : Cmd> (component: Component<STATE, MSG, CMD>, logLevel: LogLevel = LogLevel.NONE) :
88
ViewModel(),
9-
ComponentRuntime<STATE, MSG> by RxRuntime<STATE, MSG, CMD>(component, logLevel) {
9+
ComponentRuntime<STATE, MSG> by RuntimeFactory.create(component, logLevel = logLevel) {
1010

1111
override fun onCleared() {
1212
super.onCleared()
@@ -18,7 +18,7 @@ abstract class ElmBaseViewModel<STATE : State, MSG : Msg, CMD : Cmd>(logLevel: L
1818
Component<STATE, MSG, CMD>,
1919
ComponentRuntime<STATE, MSG> {
2020

21-
private val runtime = RxRuntime(this, logLevel)
21+
private val runtime = RuntimeFactory.create(this, logLevel = logLevel)
2222

2323
override fun state(): LiveData<STATE> = runtime.state()
2424

@@ -31,7 +31,7 @@ abstract class ElmSimpleBaseViewModel<STATE : State, MSG : Msg>(logLevel: LogLev
3131
SimpleComponent<STATE, MSG>,
3232
ComponentRuntime<STATE, MSG> {
3333

34-
private val runtime = RxRuntime(this, logLevel)
34+
private val runtime = RuntimeFactory.create(this, logLevel = logLevel)
3535

3636
override fun state(): LiveData<STATE> = runtime.state()
3737

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package cz.inventi.elmdroid
22

3+
import android.arch.lifecycle.LifecycleOwner
4+
35

46
object RuntimeFactory {
57
var defaultLogLevel = LogLevel.NONE
6-
fun <STATE : State, MSG : Msg, CMD : Cmd> create(component: Component<STATE, MSG, CMD>, logLevel: LogLevel = defaultLogLevel): ComponentRuntime<STATE, MSG> {
7-
return RxRuntime(component, logLevel)
8+
fun <STATE : State, MSG : Msg, CMD : Cmd> create(component: Component<STATE, MSG, CMD>, lifecycleOwner: LifecycleOwner? = null, logLevel: LogLevel = defaultLogLevel): ComponentRuntime<STATE, MSG> {
9+
val rxRuntime = RxRuntime(component, logLevel)
10+
if (lifecycleOwner != null) {
11+
rxRuntime.bindTo(lifecycleOwner)
12+
}
13+
return rxRuntime
814
}
915
}

elmdroid/src/test/java/cz/inventi/elmdroid/ComponentRuntimeTest.kt

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package cz.inventi.elmdroid
22

33
import android.arch.core.executor.testing.InstantTaskExecutorRule
4+
import android.arch.lifecycle.Lifecycle
5+
import android.arch.lifecycle.LifecycleOwner
6+
import android.arch.lifecycle.LifecycleRegistry
47
import android.arch.lifecycle.Observer
58
import cz.inventi.elmdroid.utils.RxImmediateSchedulerRule
69
import org.junit.Before
@@ -11,6 +14,7 @@ import org.mockito.Mock
1114
import org.mockito.Mockito.`when`
1215
import org.mockito.Mockito.verify
1316
import org.mockito.MockitoAnnotations
17+
import org.mockito.Spy
1418

1519

1620
class ComponentRuntimeTest {
@@ -20,11 +24,11 @@ class ComponentRuntimeTest {
2024
@get:Rule
2125
val instantExecutorRule = InstantTaskExecutorRule()
2226

23-
private lateinit var runtime: ComponentRuntime<TestState, TestMsg>
27+
@Spy lateinit var runtime: ComponentRuntime<TestState, TestMsg>
2428
@Mock lateinit var observer: Observer<TestState>
2529
@Mock lateinit var component: Component<TestState, TestMsg, TestCmd>
2630

27-
31+
private lateinit var lifecycleOwner: TestLifecycleOwner
2832

2933
companion object {
3034
@JvmField
@@ -35,17 +39,23 @@ class ComponentRuntimeTest {
3539
@Before
3640
fun setUp() {
3741
MockitoAnnotations.initMocks(this)
38-
42+
`when`(component.initState()).thenReturn(TestState(3, "init"))
43+
lifecycleOwner = TestLifecycleOwner()
44+
runtime = RuntimeFactory.create(component)
3945
}
4046

4147
@Test
4248
fun initState() {
43-
`when`(component.initState()).thenReturn(TestState(3, "init"))
44-
runtime = RuntimeFactory.create(component)
4549
runtime.state().observeForever(observer)
4650
verify(observer).onChanged(TestState(3, "init"))
4751
}
4852

53+
@Test
54+
fun autoClear() {
55+
lifecycleOwner.markState(Lifecycle.State.CREATED)
56+
lifecycleOwner.markState(Lifecycle.State.DESTROYED)
57+
verify(runtime).clear()
58+
}
4959
}
5060

5161
data class TestState(val number: Int, val text: String): State
@@ -57,3 +67,11 @@ data class NumberMsg(val number: Int) : TestMsg()
5767
sealed class TestCmd: Cmd
5868
data class NumberCmd(val num: Int): TestCmd()
5969

70+
class TestLifecycleOwner : LifecycleOwner {
71+
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
72+
override fun getLifecycle(): Lifecycle = lifecycleRegistry
73+
fun markState(state: Lifecycle.State) {
74+
lifecycleRegistry.markState(state)
75+
}
76+
}
77+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mock-maker-inline

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

sample/src/main/java/com/example/elmdroid/counter/CounterActivity.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CounterActivity : AppCompatActivity() {
1717
setContentView(R.layout.activity_counter)
1818
supportActionBar?.title = getString(R.string.basic_counter)
1919

20-
runtime = RuntimeFactory.create(CounterComponent())
20+
runtime = RuntimeFactory.create(CounterComponent(), this)
2121

2222
runtime.state().observe(this, Observer {
2323
it?.let { counter.text = "${it.counter}" }
@@ -26,9 +26,4 @@ class CounterActivity : AppCompatActivity() {
2626
increment.setOnClickListener { runtime.dispatch(Increment) }
2727
decrement.setOnClickListener { runtime.dispatch(Decrement) }
2828
}
29-
30-
override fun onDestroy() {
31-
super.onDestroy()
32-
runtime.clear()
33-
}
3429
}

sample/src/main/java/com/example/elmdroid/login/presentation/LoginActivity.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class LoginActivity : AppCompatActivity(), LoginView {
1919

2020
supportActionBar?.title = getString(R.string.complex_login)
2121

22-
runtime = RuntimeFactory.create(LoginComponent())
22+
runtime = RuntimeFactory.create(LoginComponent(), this)
2323

2424
// observe state
2525
runtime.state().observe(this, LoginRenderer(this))
@@ -30,11 +30,6 @@ class LoginActivity : AppCompatActivity(), LoginView {
3030
loginButton().setOnClickListener { runtime.dispatch(LoginClicked) }
3131
}
3232

33-
override fun onDestroy() {
34-
super.onDestroy()
35-
runtime.clear()
36-
}
37-
3833
override fun email(): EditText = email
3934
override fun password(): EditText = password
4035
override fun loginButton(): Button = loginButton
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mock-maker-inline

0 commit comments

Comments
 (0)