Skip to content

Commit

Permalink
coroutines.Flow support, restoring subscriptions after component reat…
Browse files Browse the repository at this point in the history
…tach
  • Loading branch information
UtkonosCorp committed Feb 11, 2021
1 parent 8b36933 commit d979d27
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 64 deletions.
16 changes: 15 additions & 1 deletion app/src/main/java/ru/impression/ui_generator_example/Ext.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
package ru.impression.ui_generator_example

import android.view.View
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.databinding.BindingAdapter

object DataBindingExt {

@JvmStatic
@BindingAdapter("isInvisible")
fun setIsVisible(view: View, value: Boolean) {
fun setIsInvisible(view: View, value: Boolean) {
view.isInvisible = value
}

@JvmStatic
@BindingAdapter("isVisible")
fun setIsVisible(view: View, value: Boolean) {
view.isVisible = value
}

@JvmStatic
@BindingAdapter("isGone")
fun setIsGone(view: View, value: Boolean) {
view.isGone = value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ru.impression.ui_generator_example

import androidx.fragment.app.Fragment
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import ru.impression.ui_generator_annotations.MakeComponent
import ru.impression.ui_generator_annotations.Prop
Expand All @@ -17,6 +18,20 @@ class MainFragment :

class MainFragmentViewModel : CoroutineViewModel() {

var countDown by state(flow {
delay(1000)
emit(3)
delay(1000)
emit(2)
delay(1000)
emit(1)
delay(1000)
emit(0)
})

val countDownIsLoading get() = ::countDown.isLoading


@Prop
var welcomeText by state<String?>(null)

Expand All @@ -34,9 +49,6 @@ class MainFragmentViewModel : CoroutineViewModel() {
val currentTimeIsLoading get() = ::currentTime.isLoading

fun reloadCurrentTime() {
launch {
::currentTime.reload().join()
toastMessage = "Current time reloaded"
}
::currentTime.reload()
}
}
127 changes: 75 additions & 52 deletions app/src/main/res/layout/main_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,85 +10,108 @@

</data>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
android:layout_height="match_parent">

<ru.impression.ui_generator_example.ToastShowerComponent
message="@={viewModel.toastMessage}"
android:layout_width="0dp"
android:layout_height="0dp" />

<androidx.appcompat.widget.AppCompatTextView
<ProgressBar
isVisible="@{viewModel.countDownIsLoading}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.welcomeText}"
android:textSize="24sp"
android:textStyle="bold" />
android:layout_gravity="center" />

<LinearLayout
<TextView
isVisible="@{!viewModel.countDownIsLoading &amp;&amp; viewModel.countDown > 0}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:orientation="horizontal">
android:layout_gravity="center"
android:text="@{String.valueOf(viewModel.countDown)}"
android:textSize="24sp"
android:textStyle="bold"
tools:text="3..2..1" />

<ru.impression.ui_generator_example.TitledPictureComponent
<LinearLayout
isVisible="@{!viewModel.countDownIsLoading &amp;&amp; viewModel.countDown == 0}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">

<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:picture="@drawable/sample_image_1"
app:title="Apples" />
android:text="@{viewModel.welcomeText}"
android:textSize="24sp"
android:textStyle="bold" />

<ru.impression.ui_generator_example.TitledPictureComponent
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:picture="@drawable/sample_image_2"
app:title="Flower" />

</LinearLayout>

<ru.impression.ui_generator_example.CounterComponent
count="@={viewModel.count}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Current time is:"
android:textStyle="bold" />

<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
android:layout_marginTop="32dp"
android:orientation="horizontal">

<ru.impression.ui_generator_example.TitledPictureComponent
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:picture="@drawable/sample_image_1"
app:title="Apples" />

<ru.impression.ui_generator_example.TitledPictureComponent
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:picture="@drawable/sample_image_2"
app:title="Flower" />

</LinearLayout>

<ru.impression.ui_generator_example.CounterComponent
count="@={viewModel.count}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@{viewModel.currentTime}"
tools:text="12345" />
android:layout_marginTop="32dp"
android:text="Current time is:"
android:textStyle="bold" />

<ProgressBar
isInvisible="@{!viewModel.currentTimeIsLoading}"
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
android:layout_marginTop="8dp">

</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@{viewModel.currentTime}"
tools:text="12345" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:onClick="@{() -> viewModel.reloadCurrentTime()}"
android:text="reload" />
<ProgressBar
isInvisible="@{!viewModel.currentTimeIsLoading}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />

</FrameLayout>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:onClick="@{() -> viewModel.reloadCurrentTime()}"
android:text="reload" />

</LinearLayout>

</LinearLayout>
</FrameLayout>

</layout>
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ abstract class ComponentViewModel(val attrs: IntArray? = null) : ViewModel(), St

private val stateObserversNotifier = Runnable { notifyStateObservers(true) }

private val subscriptionsInitializers = ArrayList<(() -> Unit)>()

protected fun <T> state(initialValue: T, attr: Int? = null, onChanged: ((T) -> Unit)? = null) =
StateDelegate(this, initialValue, null, onChanged)
StateDelegate(this, initialValue, onChanged)
.also { delegate -> attr?.let { delegateToAttrs[delegate] = it } }

@CallSuper
Expand All @@ -44,6 +46,15 @@ abstract class ComponentViewModel(val attrs: IntArray? = null) : ViewModel(), St
component = null
}

fun initSubscriptions(block: () -> Unit) {
block()
subscriptionsInitializers.add(block)
}

fun restoreSubscriptions() {
subscriptionsInitializers.forEach { it() }
}

fun addStateObserver(lifecycleOwner: LifecycleOwner, observer: () -> Unit) {
fun addActual() {
val set = stateObservers[lifecycleOwner]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package ru.impression.ui_generator_base

import androidx.annotation.CallSuper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow

abstract class CoroutineViewModel(attrs: IntArray? = null) : ComponentViewModel(attrs),
ClearableCoroutineScope by ClearableCoroutineScopeImpl(Dispatchers.IO) {

protected fun <T> state(getInitialValue: suspend () -> T, onChanged: ((T?) -> Unit)? = null) =
StateDelegate(this, null, getInitialValue, onChanged)
protected fun <T> state(loadValue: suspend () -> T, onChanged: ((T?) -> Unit)? = null) =
StateDelegate(this, null, onChanged, loadValue = loadValue)

protected fun <T> state(valueFlow: Flow<T>, onChanged: ((T?) -> Unit)? = null) =
StateDelegate(this, null, onChanged, valueFlow = valueFlow)

@CallSuper
override fun onCleared() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package ru.impression.ui_generator_base

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import ru.impression.ui_generator_annotations.Prop
import kotlin.properties.ReadWriteProperty
Expand All @@ -11,8 +13,9 @@ import kotlin.reflect.full.findAnnotation
open class StateDelegate<R : StateOwner, T>(
val parent: R,
initialValue: T,
val getInitialValue: (suspend () -> T)?,
val onChanged: ((T) -> Unit)?
val onChanged: ((T) -> Unit)?,
val loadValue: (suspend () -> T)? = null,
val valueFlow: Flow<T>? = null
) : ReadWriteProperty<R, T> {

@Volatile
Expand All @@ -27,7 +30,10 @@ open class StateDelegate<R : StateOwner, T>(
private var loadJob: Job? = null

init {
if (getInitialValue != null) load(false)
when {
loadValue != null -> load(false)
valueFlow != null -> collectValueFlow()
}
}

@Synchronized
Expand All @@ -36,13 +42,24 @@ open class StateDelegate<R : StateOwner, T>(
isLoading = true
if (notifyStateChangedBeforeLoading) parent.onStateChanged()
return (parent as CoroutineScope).launch {
val result = getInitialValue!!.invoke()
val result = loadValue!!.invoke()
isLoading = false
setValueToProperty(result)
loadJob = null
}.also { loadJob = it }
}

private fun collectValueFlow() {
isLoading = true
fun collect() = (parent as CoroutineScope).launch {
valueFlow!!.collect {
isLoading = false
setValueToProperty(it)
}
}
(parent as? ComponentViewModel)?.initSubscriptions(::collect) ?: collect()
}

@Synchronized
override fun getValue(thisRef: R, property: KProperty<*>) = value

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ class ViewComponentClassBuilder(
if (isDetachedFromWindow) {
isDetachedFromWindow = false
viewModel.setComponent(this)
viewModel.restoreSubscriptions()
}
lifecycle.handleLifecycleEvent(%T.Event.ON_CREATE)
""".trimIndent(),
Expand Down

0 comments on commit d979d27

Please sign in to comment.