Skip to content

Instantly share code, notes, and snippets.

@bolot
Created August 15, 2017 03:15
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save bolot/7a6db460abf701d2526be287ea0eabaf to your computer and use it in GitHub Desktop.
Save bolot/7a6db460abf701d2526be287ea0eabaf to your computer and use it in GitHub Desktop.
Lifecycle Aware Lazy and Find View
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.bignerdranch.android.lazyforlifecycle.MainActivity">
<TextView
android:id="@+id/questionTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="Civil Rights Leader"/>
<TextView
android:id="@+id/nameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="Martin Luther King Jr."/>
<TextView
android:id="@+id/cityTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="Atlanta"/>
<TextView
android:id="@+id/stateTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="Georgia"/>
<Button
android:id="@+id/showAnswerButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show answer"/>
</LinearLayout>
package com.bignerdranch.android.kotleta.ext
import android.app.Activity
import android.arch.lifecycle.GenericLifecycleObserver
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleOwner
import android.support.v4.app.Fragment
import android.view.View
import kotlin.reflect.KProperty
/**
* Represents a value with lifecycleAwareLazy initialization.
*
* To create an instance of [LifecycleAwareLazy] use the [lifecycleAwareLazy] function.
*/
public interface LifecycleAwareLazy<out T> {
/**
* Gets the lazily initialized value of the current LifecycleAwareLazy instance.
* Once the value was initialized it must not change during the rest of lifetime of this LifecycleAwareLazy instance.
*/
public val value: T
/**
* Returns `true` if a value for this LifecycleAwareLazy instance has been already initialized, and `false` otherwise.
* Once this function has returned `true` it stays `true` for the rest of lifetime of this LifecycleAwareLazy instance.
*/
public fun isInitialized(): Boolean
}
/**
* Lifecycle aware
*/
public fun <T> lifecycleAwareLazy(lifecycle: Lifecycle, initializer: () -> T): LifecycleAwareLazy<T>
= LifecycleAwareLazyImpl(lifecycle, initializer)
public fun <T, U> findViewLazy(fragment: U, viewId: Int): LifecycleAwareLazy<T>
where T: View, U: Fragment, U: LifecycleOwner {
return lifecycleAwareLazy(fragment.lifecycle) {
fragment.view!!.findViewById<T>(viewId)
}
}
public fun <T, U> findViewLazy(activity: U, viewId: Int): LifecycleAwareLazy<T>
where T: View, U: Activity, U: LifecycleOwner {
return lifecycleAwareLazy(activity.lifecycle) {
activity.findViewById<T>(viewId)
}
}
/**
* An extension to delegate a read-only property of type [T] to an instance of [LifecycleAwareLazy].
*
* This extension allows to use instances of LifecycleAwareLazy for property delegation:
* `val property: TextView by lifecycleAwareLazy(lifecycle) { initializer }`
*/
public inline operator fun <T> LifecycleAwareLazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
private object UNINITIALIZED_VALUE
private class LifecycleAwareLazyImpl<out T>(lifecycle: Lifecycle, initializer: () -> T)
: LifecycleAwareLazy<T>, GenericLifecycleObserver {
override fun getReceiver() = this
override fun onStateChanged(source: LifecycleOwner?, event: Lifecycle.Event?) {
when(event) {
Lifecycle.Event.ON_STOP -> _value = UNINITIALIZED_VALUE
// Lifecycle.Event.ON_DESTROY -> initializer = null
else -> return
}
}
init {
lifecycle.addObserver(this)
}
private var initializer: (() -> T)? = initializer
private var _value: Any? = UNINITIALIZED_VALUE
override val value: T
get() {
if (_value === UNINITIALIZED_VALUE) {
_value = initializer!!()
}
@Suppress("UNCHECKED_CAST")
return _value as T
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "LifecycleAwareLazy value not initialized yet."
}
public interface LifecycleAwareFindView<out T: View> {
/**
* Gets the lazily initialized value of the current LifecycleAwareLazy instance.
* Once the value was initialized it must not change during the rest of lifetime of this LifecycleAwareLazy instance.
*/
public val value: T
/**
* Returns `true` if a value for this LifecycleAwareLazy instance has been already initialized, and `false` otherwise.
* Once this function has returned `true` it stays `true` for the rest of lifetime of this LifecycleAwareLazy instance.
*/
public fun isInitialized(): Boolean
}
public fun <T, U> findView(fragment: U, viewId: Int): LifecycleAwareFindView<T>
where T: View, U: Fragment, U: LifecycleOwner = FindFragmentViewImpl(fragment, viewId)
private class FindFragmentViewImpl<out T, out U>(fragment: U, val viewId: Int)
: LifecycleAwareFindView<T>, GenericLifecycleObserver where T: View, U: Fragment, U: LifecycleOwner {
override fun getReceiver() = this
override fun onStateChanged(source: LifecycleOwner?, event: Lifecycle.Event?) {
when(event) {
Lifecycle.Event.ON_STOP -> _value = UNINITIALIZED_VALUE
Lifecycle.Event.ON_DESTROY -> if (fragment?.retainInstance == false) fragment = null
else -> return
}
}
init {
fragment.lifecycle.addObserver(this)
}
private var fragment: U? = fragment
private var _value: Any? = UNINITIALIZED_VALUE
override val value: T
get() {
if (_value === UNINITIALIZED_VALUE) {
_value = fragment!!.view!!.findViewById(viewId)
}
@Suppress("UNCHECKED_CAST")
return _value as T
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "LifecycleAwareLazy value not initialized yet."
}
/**
* An extension to delegate a read-only property of type [T] to an instance of [LifecycleAwareLazy].
*
* This extension allows to use instances of LifecycleAwareLazy for property delegation:
* `val property: TextView by lifecycleAwareLazy(lifecycle) { initializer }`
*/
public inline operator fun <T: View> LifecycleAwareFindView<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
package com.bignerdranch.android.lazyforlifecycle
import android.arch.lifecycle.LifecycleRegistry
import android.arch.lifecycle.LifecycleRegistryOwner
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import com.bignerdranch.android.kotleta.ext.findView
import com.bignerdranch.android.kotleta.ext.getValue
import com.bignerdranch.android.kotleta.ext.lifecycleAwareLazy
class MainFragment: Fragment(), LifecycleRegistryOwner {
val registry = LifecycleRegistry(this)
override fun getLifecycle() = registry
var showAnswerButton: Button? = null
lateinit var questionTextView: TextView
val nameTextView by lazy { view!!.findViewById<TextView>(R.id.nameTextView) }
val cityTextView by lifecycleAwareLazy(lifecycle) { view!!.findViewById<TextView>(R.id.cityTextView) }
val stateTextView: TextView by findView(this, R.id.stateTextView)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = layoutInflater.inflate(R.layout.fragment_main, container, false)
showAnswerButton = view.findViewById(R.id.showAnswerButton)
questionTextView = view.findViewById(R.id.questionTextView)
questionTextView.text = getString(R.string.question1)
showAnswerButton?.setOnClickListener {
nameTextView.setText(R.string.answer1_name)
stateTextView.setText(R.string.answer1_state)
cityTextView.setText(R.string.answer1_city)
}
return view
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment