Skip to content

Commit

Permalink
Merge pull request #1 from wada811/feature/support-enum-and-custom-de…
Browse files Browse the repository at this point in the history
…legate

Support enum and custom delegate
  • Loading branch information
wada811 authored Oct 12, 2019
2 parents 140b9c2 + cb28e1c commit 8fd07f0
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 162 deletions.
3 changes: 2 additions & 1 deletion ViewModel-SavedState-ktx/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05'
implementation 'androidx.lifecycle:lifecycle-livedata:2.2.0-beta01'
api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-beta01'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wada811.viewmodelsavedstate

import android.os.Bundle

@Suppress("unused")
fun <T : Enum<T>> Bundle.putEnum(key: String, enum: T) = this.putInt(key, enum.toInt())
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wada811.viewmodelsavedstate

internal fun <T> Int?.toEnum(enumClass: Class<T>): T? = this?.let { enumClass.enumConstants!![this] }
internal fun <T> T?.toInt(): Int? = (this as? Enum<*>)?.ordinal
internal fun <T : Enum<T>> T.toInt(): Int = this.ordinal
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wada811.viewmodelsavedstate

import android.content.Intent

@Suppress("unused")
fun <T : Enum<T>> Intent.putEnumExtra(key: String, enum: T) = this.putExtra(key, enum.toInt())
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wada811.viewmodelsavedstate

import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
Expand All @@ -9,38 +10,118 @@ import kotlin.reflect.KProperty

@Suppress("unused")
abstract class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
protected fun <T> savedStateProperty(key: String? = null): ReadWriteProperty<SavedStateViewModel, T> {
return SavedStateProperty(savedStateHandle, key)
@Suppress("DEPRECATION")
protected inline fun <reified T> savedStateProperty(key: String? = null): ReadWriteProperty<SavedStateViewModel, T> {
return if (T::class.java.isEnum) _savedStateEnumProperty(T::class.java, key) else _savedStateBundleProperty(key)
}

protected fun <T> savedStateLiveData(key: String? = null): ReadOnlyProperty<SavedStateViewModel, MutableLiveData<T>> {
@Suppress("FunctionName")
@Deprecated("Use savedStateProperty<T>(key)", ReplaceWith("savedStateProperty(key)"), DeprecationLevel.WARNING)
protected fun <T> _savedStateBundleProperty(key: String? = null): ReadWriteProperty<SavedStateViewModel, T> {
return savedStateProperty<T, T>({ it }, { it }, key)
}

@Suppress("FunctionName", "UNCHECKED_CAST")
@Deprecated("Use savedStateProperty<T>(key)", ReplaceWith("savedStateProperty(key)"), DeprecationLevel.WARNING)
protected fun <T> _savedStateEnumProperty(enumClass: Class<T>, key: String?): ReadWriteProperty<SavedStateViewModel, T> {
return savedStateProperty<Int?, T>({ it.toEnum(enumClass) as T }, { it.toInt() }, key)
}

@Suppress("MemberVisibilityCanBePrivate")
protected fun <T, R> savedStateProperty(deserialize: (T) -> R, serialize: (R) -> T, key: String? = null): ReadWriteProperty<SavedStateViewModel, R> {
return SavedStateProperty(savedStateHandle, key, deserialize, serialize)
}

@Suppress("DEPRECATION")
protected inline fun <reified T> savedStateLiveData(key: String? = null): ReadOnlyProperty<SavedStateViewModel, MutableLiveData<T>> {
return if (T::class.java.isEnum) _savedStateEnumLiveData(T::class.java, key) else _savedStateBundleLiveData(key)
}

@Suppress("FunctionName")
@Deprecated("Use savedStateLiveData<T>(key)", ReplaceWith("savedStateLiveData(key)"), DeprecationLevel.WARNING)
protected fun <T> _savedStateBundleLiveData(key: String? = null): ReadOnlyProperty<SavedStateViewModel, MutableLiveData<T>> {
return SavedStateLiveData(savedStateHandle, key)
}

internal class SavedStateProperty<T>(
@Suppress("FunctionName", "UNCHECKED_CAST")
@Deprecated("Use savedStateLiveData<T>(key)", ReplaceWith("savedStateLiveData(key)"), DeprecationLevel.WARNING)
protected fun <T> _savedStateEnumLiveData(enumClass: Class<T>, key: String?): ReadOnlyProperty<SavedStateViewModel, MutableLiveData<T>> {
return savedStateLiveData<Int?, T>({ it.toEnum(enumClass) as T }, { it.toInt() }, key)
}

@Suppress("MemberVisibilityCanBePrivate")
protected fun <T, R> savedStateLiveData(deserialize: (T) -> R, serialize: (R) -> T, key: String? = null): ReadOnlyProperty<SavedStateViewModel, MutableLiveData<R>> {
return SavedStateDelegatedLiveData(savedStateHandle, key, deserialize, serialize)
}

private class SavedStateProperty<T, R>(
private val savedStateHandle: SavedStateHandle,
private val key: String? = null
) : ReadWriteProperty<SavedStateViewModel, T> {
private val key: String?,
private val deserialize: (T) -> R,
private val serialize: (R) -> T
) : ReadWriteProperty<SavedStateViewModel, R> {

@Suppress("RemoveExplicitTypeArguments", "UNCHECKED_CAST")
override fun getValue(thisRef: SavedStateViewModel, property: KProperty<*>): T {
return savedStateHandle.get<T>(key ?: property.name) as T
override fun getValue(thisRef: SavedStateViewModel, property: KProperty<*>): R {
return deserialize(savedStateHandle.get<T>(key ?: property.name) as T)
}

override fun setValue(thisRef: SavedStateViewModel, property: KProperty<*>, value: T) {
savedStateHandle.set(key ?: property.name, value)
override fun setValue(thisRef: SavedStateViewModel, property: KProperty<*>, value: R) {
savedStateHandle.set(key ?: property.name, serialize(value))
}
}

internal class SavedStateLiveData<T>(
private class SavedStateLiveData<T>(
private val savedStateHandle: SavedStateHandle,
private val key: String? = null
private val key: String?
) : ReadOnlyProperty<SavedStateViewModel, MutableLiveData<T>> {
override fun getValue(thisRef: SavedStateViewModel, property: KProperty<*>): MutableLiveData<T> {
return savedStateHandle.getLiveData(key ?: property.name)
}
}

companion object {
private const val KEY_TEXT = "text"
private class SavedStateDelegatedLiveData<T, R>(
private val savedStateHandle: SavedStateHandle,
private val key: String?,
private val deserialize: (T) -> R,
private val serialize: (R) -> T
) : ReadOnlyProperty<SavedStateViewModel, MutableLiveData<R>> {
private var savedStateLiveData: MutableLiveData<R>? = null

override fun getValue(thisRef: SavedStateViewModel, property: KProperty<*>): MutableLiveData<R> {
if (savedStateLiveData == null) {
savedStateLiveData = SavedStateLiveData(savedStateHandle, key ?: property.name, deserialize, serialize)
}
return savedStateLiveData!!
}

@Suppress("UNCHECKED_CAST")
private class SavedStateLiveData<T, R>(
savedStateHandle: SavedStateHandle,
key: String,
private val deserialize: (T) -> R,
private val serialize: (R) -> T
) : MediatorLiveData<R>() {
private val liveData: MutableLiveData<T> = savedStateHandle.getLiveData<T>(key)

init {
value = deserialize(liveData.value as T)
addSource(liveData) {
value = deserialize(it as T)
}
}

override fun getValue(): R {
return deserialize(liveData.value as T)
}

override fun setValue(value: R) {
super.setValue(value)
val serializedValue = serialize(value)
if (liveData.value != serializedValue) {
liveData.value = serializedValue
}
}
}
}
}
8 changes: 4 additions & 4 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.activity:activity:1.1.0-alpha03'
implementation 'androidx.activity:activity-ktx:1.1.0-alpha03'
implementation 'androidx.fragment:fragment:1.2.0-alpha04'
implementation 'androidx.fragment:fragment-ktx:1.2.0-alpha04'
implementation 'androidx.activity:activity:1.1.0-beta01'
implementation 'androidx.activity:activity-ktx:1.1.0-beta01'
implementation 'androidx.fragment:fragment:1.2.0-beta01'
implementation 'androidx.fragment:fragment-ktx:1.2.0-beta01'
implementation project(':ViewModel-SavedState-ktx')
implementation 'com.github.wada811:DataBinding-ktx:2.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.wada811.databinding.dataBinding
import com.wada811.viewmodelsavedstate.sample.SampleViewModel.CountUpValue
import com.wada811.viewmodelsavedstate.sample.databinding.SampleActivityBinding

class SampleActivity : AppCompatActivity() {
Expand All @@ -21,9 +22,22 @@ class SampleActivity : AppCompatActivity() {
binding.viewModel = viewModel
binding.activityCountText.text = "$count"
binding.countUpButton.setOnClickListener {
binding.activityCountText.text = "${++count}"
count += viewModel.countUpValueEnumLiveData.value?.count ?: 0
binding.activityCountText.text = "$count"
viewModel.countUp()
}
binding.plusOneButton.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
binding.plusTenButton.isChecked = false
}
viewModel.countUpValueIntLiveData.value = if (isChecked) CountUpValue.ONE.ordinal else null
}
binding.plusTenButton.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
binding.plusOneButton.isChecked = false
}
viewModel.countUpValueEnumLiveData.value = if (isChecked) CountUpValue.TEN else null
}
binding.rotateButton.setOnClickListener {
viewModel.appendLog("Activity::rotate")
requestedOrientation = when (requestedOrientation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,35 @@ import androidx.lifecycle.SavedStateHandle
import com.wada811.viewmodelsavedstate.SavedStateViewModel

class SampleViewModel(savedStateHandle: SavedStateHandle) : SavedStateViewModel(savedStateHandle) {
enum class CountUpValue(val count: Int) {
ONE(1),
TEN(10)
}

private var viewModelCount = MutableLiveData(0)
var viewModelCountText: LiveData<String> = MediatorLiveData<String>().also { liveData ->
liveData.addSource(viewModelCount) { count ->
liveData.value = "$count"
}
}

val savedStateCount: MutableLiveData<Int> by savedStateLiveData(SampleViewModel::savedStateCount.name)
val countUpValueIntLiveData: MutableLiveData<Int?> by savedStateLiveData(this::countUpValueEnumLiveData.name)
val countUpValueEnumLiveData: MutableLiveData<CountUpValue?> by savedStateLiveData()
val savedStateCount: MutableLiveData<Int> by savedStateLiveData()
var savedStateCountText: LiveData<String> = MediatorLiveData<String>().also { liveData ->
liveData.addSource(savedStateCount) { count ->
liveData.value = "$count"
}
}

val log: MutableLiveData<String> by savedStateLiveData(SampleViewModel::log.name)
val log: MutableLiveData<String> by savedStateLiveData()

init {
appendLog("ViewModel::init")
}

fun countUp() {
viewModelCount.value = viewModelCount.value?.plus(1)
savedStateCount.value = savedStateCount.value?.plus(1)
viewModelCount.value = viewModelCount.value?.plus(CountUpValue.values().firstOrNull { it.ordinal == countUpValueIntLiveData.value }?.count ?: 0)
savedStateCount.value = savedStateCount.value?.plus(countUpValueEnumLiveData.value?.count ?: 0)
}

fun appendLog(text: String) {
Expand Down

This file was deleted.

Loading

0 comments on commit 8fd07f0

Please sign in to comment.