Skip to content

Commit

Permalink
saving viewModel state, saving fragment arguments, completed readme
Browse files Browse the repository at this point in the history
  • Loading branch information
UtkonosCorp committed Feb 19, 2021
1 parent d979d27 commit 1080fc2
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 16 deletions.
36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,22 +114,20 @@ As you can see, the codes for the Fragment and for the View are completely ident

**Note:** you may need to build the project twice so that the binding adapters and component classes are generated correctly.

Also, in the case of a View, you can set `Prop.twoWay = true`, and then a two-way binding adapter will be generated for the View. It will send the value back when the annotated property changes.
Also, in the case of a View:
- You can set `Prop.twoWay = true`, and then a two-way binding adapter will be generated for the View. It will send the value back when the annotated property changes.
```kotlin
@Prop(twoWay = true)
var twoWayText: String? = null //a two-way binding adapter will be generated
```

And in the case of Fragments, you can pass callbacks to them:
- You can bind xml attribute to your state property:
```kotlin
// ChildFragmentViewModel.kt
@Prop
var callback: (() -> Unit)? = null

// ChildFragment's parent Fragment
showFragment(ChildFragmentComponent().apply { callback = this@ParentFragment.viewModel.childFragmentCallback })
var picture by state<Drawable?>(null, attr = R.styleable.MyViewComponent_picture)
```
```xml
<MyViewComponent
app:picture="@drawable/myPicture"/>
```
But keep in mind that the source value of the callback must be in the ViewModel so that the callback does not refer to a Fragment or View.

### 2. Observable state

Expand Down Expand Up @@ -182,6 +180,8 @@ class MyTextView : ComponentScheme<TextView, MyTextViewModel>({

### 5. Coroutine support

#### suspend funs

Suppose that before you display some data, you need to load it first. Here's how you do it:
```kotlin
var greeting: String? by state({
Expand Down Expand Up @@ -212,4 +212,20 @@ fun reloadGreeting() {
}
```

#### Flows

Suppose you need to subscribe to the Flow and display all its elements. Here's how you do it:
```kotlin
var countDown: Int? by state(flow {
delay(1000)
emit(3)
delay(1000)
emit(2)
delay(1000)
emit(1)
delay(1000)
emit(0)
})
```

***For detailed examples see module `app`.***
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ru.impression.ui_generator_base

import android.os.Handler
import android.os.Looper
import android.os.Parcelable
import androidx.annotation.CallSuper
import androidx.lifecycle.*
import kotlin.reflect.KMutableProperty0
Expand Down Expand Up @@ -107,6 +108,10 @@ abstract class ComponentViewModel(val attrs: IntArray? = null) : ViewModel(), St

protected open fun onLifecycleEvent(event: Lifecycle.Event) = Unit

open fun onSaveInstanceState(): Parcelable? = null

open fun onRestoreInstanceState(savedInstanceState: Parcelable?) = Unit

public override fun onCleared() = Unit

protected fun <T> KMutableProperty0<T>.set(value: T, renderImmediately: Boolean = false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package ru.impression.ui_generator_base
import android.content.Context
import android.content.ContextWrapper
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
Expand All @@ -30,6 +32,11 @@ val View.activity: AppCompatActivity?
return contextWrapper
}

fun Fragment.putArgument(key: String, value: Any?) {
val arguments = arguments ?: Bundle().also { arguments = it }
arguments.putAll(bundleOf(key to value))
}

fun <T, VM : ComponentViewModel> T.resolveAttrs(attrs: AttributeSet?) where T : Component<*, VM>, T : View {
with(context.theme.obtainStyledAttributes(attrs, viewModel.attrs ?: return, 0, 0)) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.impression.ui_generator_base

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
class SavedViewState(val superState: Parcelable?, val viewModelState: Parcelable?): Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ open class StateDelegate<R : StateOwner, T>(
(parent as? ComponentViewModel)?.initSubscriptions(::collect) ?: collect()
}

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

@Synchronized
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,7 @@ abstract class ComponentClassBuilder(
val type: TypeMirror,
val twoWay: Boolean,
val attrChangedPropertyName: String
)
) {
val kotlinType = type.asTypeName().javaToKotlinType().copy(true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class FragmentComponentClassBuilder(
propProperties.forEach {
add(
"""
val ${it.name} = ${it.name}
if (${it.name} != null && ${it.name} !== viewModel.${it.name})
viewModel::${it.name}.%M(${it.name})
viewModel.onStateChanged(renderImmediately = true)
Expand Down Expand Up @@ -73,19 +74,51 @@ class FragmentComponentClassBuilder(

override fun TypeSpec.Builder.addRestMembers() {
propProperties.forEach { addProperty(buildPropWrapperProperty(it)) }
addFunction(buildOnCreateFunction())
addFunction(buildOnCreateViewFunction())
addFunction(buildOnActivityCreatedFunction())
addFunction(buildOnSaveInstanceStateFunction())
addFunction(buildOnDestroyViewFunction())
}

private fun buildPropWrapperProperty(propProperty: PropProperty) = with(
PropertySpec.builder(
propProperty.name,
propProperty.type.asTypeName().javaToKotlinType().copy(true)
propProperty.kotlinType
)
) {
mutable(true)
initializer("null")
getter(
FunSpec.getterBuilder()
.addCode(
"""
return field ?: arguments?.get("${propProperty.name}") as? ${propProperty.kotlinType}
""".trimIndent()
)
.build()
)
setter(
FunSpec.setterBuilder().addParameter("value", propProperty.kotlinType).addCode(
"""
field = value
%M("${propProperty.name}", value)
""".trimIndent(),
MemberName("ru.impression.ui_generator_base", "putArgument")
).build()
)
build()
}

private fun buildOnCreateFunction() = with(FunSpec.builder("onCreate")) {
addModifiers(KModifier.OVERRIDE)
addParameter("savedInstanceState", ClassName("android.os", "Bundle").copy(true))
addCode(
"""
super.onCreate(savedInstanceState)
viewModel.onRestoreInstanceState(savedInstanceState?.getParcelable("viewModelState"))""".trimIndent()
)
build()
}

Expand All @@ -110,10 +143,21 @@ class FragmentComponentClassBuilder(
addCode(
"""
super.onActivityCreated(savedInstanceState)
""".trimIndent()
viewModel.setComponent(this)
""".trimIndent()
)
build()
}

private fun buildOnSaveInstanceStateFunction() = with(FunSpec.builder("onSaveInstanceState")) {
addModifiers(KModifier.OVERRIDE)
addParameter("outState", ClassName("android.os", "Bundle"))
addCode(
"""
super.onSaveInstanceState(outState)
outState.putParcelable("viewModelState", viewModel.onSaveInstanceState())
""".trimIndent()
)
addCode("viewModel.setComponent(this)")
build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class ViewComponentClassBuilder(
addFunction(buildOnTwoWayPropChangedFunction())
addFunction(buildOnAttachedToWindowFunction())
addFunction(buildOnDetachedFromWindowFunction())
addFunction(buildOnSaveInstanceStateFunction())
addFunction(buildOnRestoreInstanceStateFunction())
addType(buildCompanionObject())
}

Expand Down Expand Up @@ -191,6 +193,34 @@ class ViewComponentClassBuilder(
build()
}

private fun buildOnSaveInstanceStateFunction() = with(FunSpec.builder("onSaveInstanceState")) {
addModifiers(KModifier.OVERRIDE)
returns(ClassName("android.os", "Parcelable").copy(true))
addCode(
"""
return %T(super.onSaveInstanceState(), viewModel.onSaveInstanceState())
""".trimIndent(),
ClassName("ru.impression.ui_generator_base", "SavedViewState")
)
build()
}

private fun buildOnRestoreInstanceStateFunction() =
with(FunSpec.builder("onRestoreInstanceState")) {
addModifiers(KModifier.OVERRIDE)
addParameter("state", ClassName("android.os", "Parcelable").copy(true))
addCode(
"""
super.onRestoreInstanceState((state as? %T)?.superState)
viewModel.onRestoreInstanceState((state as? %T)?.viewModelState)
""".trimIndent(),
ClassName("ru.impression.ui_generator_base", "SavedViewState"),
ClassName("ru.impression.ui_generator_base", "SavedViewState")
)
build()
}

private fun buildCompanionObject(): TypeSpec = with(TypeSpec.companionObjectBuilder()) {
propProperties.forEach {
addFunction(buildPropSetter(it))
Expand Down

0 comments on commit 1080fc2

Please sign in to comment.