Skip to content

Instantly share code, notes, and snippets.

@osipxd
Last active May 13, 2020 11:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save osipxd/bb5aee5fb2aec77f4a0e9af33fa089fa to your computer and use it in GitHub Desktop.
Save osipxd/bb5aee5fb2aec77f4a0e9af33fa089fa to your computer and use it in GitHub Desktop.
Partial view state rendering with LiveData transformations
class DetailsFragment : BaseFragment(R.layout.fragment_details) {
lateinit val viewModel: DetailsViewModel
// ...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onVewCreated(view, savedInstanceState)
// Assumed that viewModel initialized here
observe(viewModel.title, ::renderTitle)
observe(viewModel.description, ::renderDescription)
}
private fun renderTitle(title: String) {
setTitleText(title)
}
private fun renderDescription(description: String) {
setDescriptionText(state.description)
}
// ...
}
class DetailsViewModel : ViewModel() {
// "Source of truth" LiveData
private val liveState = MutableLiveData<DetailsViewState>(createInitialState())
// State representations
val title: LiveData<String> = liveState.mapDistinct { "${it.title} ${it.number}" }
val description: LiveData<String> = liveState.mapDistinct { it.description }
// Delegate for easier work with state inside LiveData
private var state: DetailsViewState by liveState.delegate()
private fun createInitialState(): DetailsViewState {
return DetailsViewState(title = "None", description = "None")
}
fun onUpdateRequested() {
state = state.copy(title = "New Title")
}
// ...
}
data class DetailsViewState(
val title: String,
val description: String,
val number: Int
)
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
/**
* Sequential call of [map] and [distinctUntilChanged] in one function.
* Requires `androidx.lifecycle:lifecycle-livedata-ktx` dependency
*/
inline fun <X, Y> LiveData<X>.mapDistinct(crossinline transform: (X) -> Y): LiveData<Y> {
return map(transform).distinctUntilChanged()
}
/**
* Delegate for easier work with [MutableLiveData] content.
*
* It is shortest version of:
* ```
* val liveState = MutableLiveData<IntroViewState>(initialState)
* var state: IntroViewState
* get() = liveState.requireValue()
* set(value) = liveState.onNext(value)
* ```
* With delegate you can replace it with next code:
* ```
* val liveState = MutableLiveData<IntroViewState>(initialState)
* var state: IntroViewState by liveState.delegate()
* ```
*/
fun <T : Any> MutableLiveData<T>.delegate(): ReadWriteProperty<Any, T> {
return object : ReadWriteProperty<Any, T> {
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) = onNext(value)
override fun getValue(thisRef: Any, property: KProperty<*>): T = requireValue()
}
}
fun <T> MutableLiveData<T>.onNext(next: T) {
this.value = next
}
fun <T : Any> LiveData<T>.requireValue(): T = checkNotNull(value)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment