Skip to content

Commit 1080fc2

Browse files
committed
saving viewModel state, saving fragment arguments, completed readme
1 parent d979d27 commit 1080fc2

File tree

8 files changed

+126
-16
lines changed

8 files changed

+126
-16
lines changed

README.md

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,20 @@ As you can see, the codes for the Fragment and for the View are completely ident
114114

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

117-
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.
117+
Also, in the case of a View:
118+
- 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.
118119
```kotlin
119120
@Prop(twoWay = true)
120121
var twoWayText: String? = null //a two-way binding adapter will be generated
121122
```
122-
123-
And in the case of Fragments, you can pass callbacks to them:
123+
- You can bind xml attribute to your state property:
124124
```kotlin
125-
// ChildFragmentViewModel.kt
126-
@Prop
127-
var callback: (() -> Unit)? = null
128-
129-
// ChildFragment's parent Fragment
130-
showFragment(ChildFragmentComponent().apply { callback = this@ParentFragment.viewModel.childFragmentCallback })
125+
var picture by state<Drawable?>(null, attr = R.styleable.MyViewComponent_picture)
126+
```
127+
```xml
128+
<MyViewComponent
129+
app:picture="@drawable/myPicture"/>
131130
```
132-
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.
133131

134132
### 2. Observable state
135133

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

183181
### 5. Coroutine support
184182

183+
#### suspend funs
184+
185185
Suppose that before you display some data, you need to load it first. Here's how you do it:
186186
```kotlin
187187
var greeting: String? by state({
@@ -212,4 +212,20 @@ fun reloadGreeting() {
212212
}
213213
```
214214

215+
#### Flows
216+
217+
Suppose you need to subscribe to the Flow and display all its elements. Here's how you do it:
218+
```kotlin
219+
var countDown: Int? by state(flow {
220+
delay(1000)
221+
emit(3)
222+
delay(1000)
223+
emit(2)
224+
delay(1000)
225+
emit(1)
226+
delay(1000)
227+
emit(0)
228+
})
229+
```
230+
215231
***For detailed examples see module `app`.***

ui-generator-base/src/main/java/ru/impression/ui_generator_base/ComponentViewModel.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ru.impression.ui_generator_base
22

33
import android.os.Handler
44
import android.os.Looper
5+
import android.os.Parcelable
56
import androidx.annotation.CallSuper
67
import androidx.lifecycle.*
78
import kotlin.reflect.KMutableProperty0
@@ -107,6 +108,10 @@ abstract class ComponentViewModel(val attrs: IntArray? = null) : ViewModel(), St
107108

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

111+
open fun onSaveInstanceState(): Parcelable? = null
112+
113+
open fun onRestoreInstanceState(savedInstanceState: Parcelable?) = Unit
114+
110115
public override fun onCleared() = Unit
111116

112117
protected fun <T> KMutableProperty0<T>.set(value: T, renderImmediately: Boolean = false) {

ui-generator-base/src/main/java/ru/impression/ui_generator_base/Ext.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package ru.impression.ui_generator_base
33
import android.content.Context
44
import android.content.ContextWrapper
55
import android.graphics.drawable.Drawable
6+
import android.os.Bundle
67
import android.util.AttributeSet
78
import android.view.LayoutInflater
89
import android.view.View
910
import android.view.ViewGroup
1011
import androidx.appcompat.app.AppCompatActivity
12+
import androidx.core.os.bundleOf
1113
import androidx.databinding.ViewDataBinding
1214
import androidx.fragment.app.Fragment
1315
import androidx.lifecycle.Lifecycle
@@ -30,6 +32,11 @@ val View.activity: AppCompatActivity?
3032
return contextWrapper
3133
}
3234

35+
fun Fragment.putArgument(key: String, value: Any?) {
36+
val arguments = arguments ?: Bundle().also { arguments = it }
37+
arguments.putAll(bundleOf(key to value))
38+
}
39+
3340
fun <T, VM : ComponentViewModel> T.resolveAttrs(attrs: AttributeSet?) where T : Component<*, VM>, T : View {
3441
with(context.theme.obtainStyledAttributes(attrs, viewModel.attrs ?: return, 0, 0)) {
3542
try {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package ru.impression.ui_generator_base
2+
3+
import android.os.Parcelable
4+
import kotlinx.android.parcel.Parcelize
5+
6+
@Parcelize
7+
class SavedViewState(val superState: Parcelable?, val viewModelState: Parcelable?): Parcelable

ui-generator-base/src/main/java/ru/impression/ui_generator_base/StateDelegate.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ open class StateDelegate<R : StateOwner, T>(
6060
(parent as? ComponentViewModel)?.initSubscriptions(::collect) ?: collect()
6161
}
6262

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

6665
@Synchronized

ui-generator-processor/src/main/java/ru/impression/ui_generator_processor/ComponentClassBuilder.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,7 @@ abstract class ComponentClassBuilder(
9393
val type: TypeMirror,
9494
val twoWay: Boolean,
9595
val attrChangedPropertyName: String
96-
)
96+
) {
97+
val kotlinType = type.asTypeName().javaToKotlinType().copy(true)
98+
}
9799
}

ui-generator-processor/src/main/java/ru/impression/ui_generator_processor/FragmentComponentClassBuilder.kt

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class FragmentComponentClassBuilder(
3333
propProperties.forEach {
3434
add(
3535
"""
36+
val ${it.name} = ${it.name}
3637
if (${it.name} != null && ${it.name} !== viewModel.${it.name})
3738
viewModel::${it.name}.%M(${it.name})
3839
viewModel.onStateChanged(renderImmediately = true)
@@ -73,19 +74,51 @@ class FragmentComponentClassBuilder(
7374

7475
override fun TypeSpec.Builder.addRestMembers() {
7576
propProperties.forEach { addProperty(buildPropWrapperProperty(it)) }
77+
addFunction(buildOnCreateFunction())
7678
addFunction(buildOnCreateViewFunction())
7779
addFunction(buildOnActivityCreatedFunction())
80+
addFunction(buildOnSaveInstanceStateFunction())
7881
addFunction(buildOnDestroyViewFunction())
7982
}
8083

8184
private fun buildPropWrapperProperty(propProperty: PropProperty) = with(
8285
PropertySpec.builder(
8386
propProperty.name,
84-
propProperty.type.asTypeName().javaToKotlinType().copy(true)
87+
propProperty.kotlinType
8588
)
8689
) {
8790
mutable(true)
8891
initializer("null")
92+
getter(
93+
FunSpec.getterBuilder()
94+
.addCode(
95+
"""
96+
return field ?: arguments?.get("${propProperty.name}") as? ${propProperty.kotlinType}
97+
98+
""".trimIndent()
99+
)
100+
.build()
101+
)
102+
setter(
103+
FunSpec.setterBuilder().addParameter("value", propProperty.kotlinType).addCode(
104+
"""
105+
field = value
106+
%M("${propProperty.name}", value)
107+
""".trimIndent(),
108+
MemberName("ru.impression.ui_generator_base", "putArgument")
109+
).build()
110+
)
111+
build()
112+
}
113+
114+
private fun buildOnCreateFunction() = with(FunSpec.builder("onCreate")) {
115+
addModifiers(KModifier.OVERRIDE)
116+
addParameter("savedInstanceState", ClassName("android.os", "Bundle").copy(true))
117+
addCode(
118+
"""
119+
super.onCreate(savedInstanceState)
120+
viewModel.onRestoreInstanceState(savedInstanceState?.getParcelable("viewModelState"))""".trimIndent()
121+
)
89122
build()
90123
}
91124

@@ -110,10 +143,21 @@ class FragmentComponentClassBuilder(
110143
addCode(
111144
"""
112145
super.onActivityCreated(savedInstanceState)
113-
114-
""".trimIndent()
146+
viewModel.setComponent(this)
147+
""".trimIndent()
148+
)
149+
build()
150+
}
151+
152+
private fun buildOnSaveInstanceStateFunction() = with(FunSpec.builder("onSaveInstanceState")) {
153+
addModifiers(KModifier.OVERRIDE)
154+
addParameter("outState", ClassName("android.os", "Bundle"))
155+
addCode(
156+
"""
157+
super.onSaveInstanceState(outState)
158+
outState.putParcelable("viewModelState", viewModel.onSaveInstanceState())
159+
""".trimIndent()
115160
)
116-
addCode("viewModel.setComponent(this)")
117161
build()
118162
}
119163

ui-generator-processor/src/main/java/ru/impression/ui_generator_processor/ViewComponentClassBuilder.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class ViewComponentClassBuilder(
6262
addFunction(buildOnTwoWayPropChangedFunction())
6363
addFunction(buildOnAttachedToWindowFunction())
6464
addFunction(buildOnDetachedFromWindowFunction())
65+
addFunction(buildOnSaveInstanceStateFunction())
66+
addFunction(buildOnRestoreInstanceStateFunction())
6567
addType(buildCompanionObject())
6668
}
6769

@@ -191,6 +193,34 @@ class ViewComponentClassBuilder(
191193
build()
192194
}
193195

196+
private fun buildOnSaveInstanceStateFunction() = with(FunSpec.builder("onSaveInstanceState")) {
197+
addModifiers(KModifier.OVERRIDE)
198+
returns(ClassName("android.os", "Parcelable").copy(true))
199+
addCode(
200+
"""
201+
return %T(super.onSaveInstanceState(), viewModel.onSaveInstanceState())
202+
203+
""".trimIndent(),
204+
ClassName("ru.impression.ui_generator_base", "SavedViewState")
205+
)
206+
build()
207+
}
208+
209+
private fun buildOnRestoreInstanceStateFunction() =
210+
with(FunSpec.builder("onRestoreInstanceState")) {
211+
addModifiers(KModifier.OVERRIDE)
212+
addParameter("state", ClassName("android.os", "Parcelable").copy(true))
213+
addCode(
214+
"""
215+
super.onRestoreInstanceState((state as? %T)?.superState)
216+
viewModel.onRestoreInstanceState((state as? %T)?.viewModelState)
217+
""".trimIndent(),
218+
ClassName("ru.impression.ui_generator_base", "SavedViewState"),
219+
ClassName("ru.impression.ui_generator_base", "SavedViewState")
220+
)
221+
build()
222+
}
223+
194224
private fun buildCompanionObject(): TypeSpec = with(TypeSpec.companionObjectBuilder()) {
195225
propProperties.forEach {
196226
addFunction(buildPropSetter(it))

0 commit comments

Comments
 (0)