Sanjeeviraj M
03/13/2023, 3:10 PMclass Example {
val loggedInUser: ResettableLazy<User> = resettableLazy {
getUserFromDB()
}
fun logout() {
loggedInUser.reset()
}
fun showUserInfo() {
userNameText.text = loggedInUser.value.name
}
}
object UninitializedValue
fun <T> resettableLazy(initializer: () -> T): ResettableLazy<T> = ResettableLazyImpl(initializer)
fun <T> resettableLazy(lock: Any? = null, initializer: () -> T): ResettableLazy<T> = ResettableLazyImpl(initializer)
fun <T> resettableLazyUnSynchronized(initializer: () -> T): ResettableLazy<T> = ResettableLazyUnSynchronizedImpl(initializer)
interface ResettableLazy<T> {
val value: T
fun isInitialized(): Boolean
fun reset()
}
private class ResettableLazyImpl<T>(private val initializer: () -> T, lock: Any? = null) :
ResettableLazy<T> {
/**
* This is an extended version of Kotlin Lazy property [kotlin.SynchronizedLazyImpl]
* calling reset() will set UninitializedValue
* if the values are used after reset() call, the value will be initialised again
*/
@Volatile private var _value: Any? = UninitializedValue
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
var tempValue = _value
if (tempValue !== UninitializedValue) {
@Suppress("UNCHECKED_CAST")
return tempValue as T
}
return synchronized(lock) {
tempValue = _value
if (tempValue !== UninitializedValue) {
@Suppress("UNCHECKED_CAST") (tempValue as T)
} else {
val typedValue = initializer!!()
_value = typedValue
typedValue
}
}
}
override fun reset() {
synchronized(lock) {
_value = UninitializedValue
}
}
override fun isInitialized(): Boolean = _value !== UninitializedValue
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
}
private class ResettableLazyUnSynchronizedImpl<T>(private val initializer: () -> T) :
ResettableLazy<T> {
/**
* This is a downgraded version of Kotlin Lazy property [ResettableLazyImpl], use it if everything happens in main thread
*/
private var _value: Any? = UninitializedValue
override val value: T
get() {
if (_value === UninitializedValue) {
_value = initializer()
}
@Suppress("UNCHECKED_CAST")
return _value as T
}
override fun reset() {
_value = UninitializedValue
}
override fun isInitialized(): Boolean = _value !== UninitializedValue
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
}
Andreas Scheja
03/13/2023, 7:40 PMExample
class? From where does getUserFromDB
know the user to load from db?
From the looks of it right now it would have to be created with some outer context that would supply this function, but this would also mean that it is inherently bound to exactly one user - so why keep the object around when the user is logged out?
One would think that if the user logged out the session will be invalid, but calling showUserInfo
would re-initialize the ResettableLazy
and as such "log-in" your user again?Sanjeeviraj M
03/13/2023, 10:11 PMclass Example {
private var loggedInUserNullable: User? = null
val loggedInUser: User
get() {
if (loggedInUserNullable != null) {
return loggedInUserNullable!!
}
synchronized(this) {
if (loggedInUserNullable == null) {
loggedInUserNullable = getUserFromDB()
}
return loggedInUserNullable!!
}
}
fun logout() {
loggedInUserNullable = null
}
fun showUserInfo() {
userNameText.text = loggedInUser.value.name
}
}
// retrofitClient will be used in lots of places
// the client should be reinstantiated when the user logs out
// it should be synchronized
object ApiClient {
private val retrofitClient: ResettableLazy<Retrofit> = resettableLazy {
Retrofit.Builder()
.baseUrl(IAMUtil.getBaseUrl())
.client(getOkHttpClient())
.addConverterFactory(
MoshiConverterFactory.create(
Moshi.Builder().add(JSONObjectAdapter).build()
)
)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
fun getClient(): Retrofit {
return retrofitClient.value
}
fun refresh() {
retrofitClient.reset()
}
}
object Logger {
private val logFile: ResettableLazy<File> = resettableLazy {
getOrCreateFile("APILogs.txt")
}
fun logV(content: String) {
// append is an extension function of File
logFile.value.append("V: $content")
}
fun logD(content: String) {
logFile.value.append("D: $content")
}
fun refresh() {
// This method will be called when logging out.
// Before calling this function, the parent directory would have been deleted
// resetting the logFile value will create new file on next logFile access
apiLogFile.reset()
}
}
// Need to observer push count and change UI
// observer need to be removed on lifecyle end
// here the view can be attached and detached multiple times
// removing old scope, cancelling it and creating new scope everytime when needed
// everything happens in main thread. so using UnSynchronized ResettableLazy version
class MenuView : FrameLayout {
private val coroutineScope: ResettableLazy<CoroutineScope> = resettableLazyUnSynchronized {
CoroutineScope(Dispatchers.Main)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
// launchAndCollect is an extension function
Util.pushCountFlow<Int>.launchAndCollect(
scope = coroutineScope.value,
context = Dispatchers.Main
) { count ->
pushCountView.text = count.toString()
}
Util.searchPermissionFlow<Boolean>.launchAndCollect(
scope = coroutineScope.value,
context = Dispatchers.Main
) { isSearchEnabled ->
searchIconView.isVisible = isSearchEnabled
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
coroutineScope.value.cancel()
coroutineScope.reset()
}
}
object RemoteConfig {
val isFullScreenFeatureEnabled: ResettableLazy<Boolean> = resettableLazyUnSynchronized {
// reads value from remote config preferences file
// return respective value
return@resettableLazyUnSynchronized isUnSupported
}
// will be called whenever remote config preferences is updated
fun refreshCondition() {
isFullScreenFeatureEnabled.reset()
}
}
Andreas Scheja
03/15/2023, 7:43 PMSanjeeviraj M
03/22/2023, 10:12 PMfindViewTreeLifecycleOwner().lifecycleScope
It either returns the Activity scope or Fragment root view scope which won't be suitable for some cases.
In the above MenuView example, using
findViewTreeLifecycleOwner().lifecycleScope will leak memory. The scope of menu view is smaller than Activity. Even after MenuView is removed from the view hierarchy, the MenuView won't be Garbage collected because of parent lifecycle scopeJoffrey
03/29/2023, 11:49 AM