Skip to content

Instantly share code, notes, and snippets.

@MRezaNasirloo
Last active October 9, 2021 13:17
Show Gist options
  • Save MRezaNasirloo/883f3d5767891b39eb6a576e1b2f9441 to your computer and use it in GitHub Desktop.
Save MRezaNasirloo/883f3d5767891b39eb6a576e1b2f9441 to your computer and use it in GitHub Desktop.
Share ViewModels across LifecycleOwners
package com.mrezanasirloo.ganjeh
import android.util.SparseArray
import androidx.activity.ComponentActivity
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.fragment.app.createViewModelLazy
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelLazy
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
/**
* @author: [email protected]
*
* Ganjeh lets you share a [ViewModel] across multiple [LifecycleOwner]s and
* destroy the [ViewModel] when the last owner is destroyed
*/
object Ganjeh {
private val stores = SparseArray<GanjehViewModelStore>()
fun get(graphId: String, owner: LifecycleOwner): ViewModelStore {
val key = graphId.hashCode()
return (stores[key] ?: put(graphId, GanjehViewModelStore {
stores.delete(key)
})).apply {
add(owner, owner.javaClass.canonicalName.toString() + "_" + graphId)
}
}
private fun put(graphId: String, store: GanjehViewModelStore): GanjehViewModelStore {
return store.apply { stores.put(graphId.hashCode(), this) }
}
}
@MainThread
inline fun <reified VM : ViewModel> Fragment.ganjehViewModels(
graphId: String,
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
return createViewModelLazy(
VM::class,
{ Ganjeh.get(graphId, this) },
factoryProducer
)
}
@MainThread
inline fun <reified VM : ViewModel> Fragment.ganjehViewModels(
noinline graphIdProvider: () -> String,
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
return createViewModelLazy(
VM::class,
{ Ganjeh.get(graphIdProvider(), this) },
factoryProducer
)
}
@MainThread
inline fun <reified VM : ViewModel> Fragment.ganjehViewModels(
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
return createViewModelLazy(
VM::class,
{ Ganjeh.get(VM::class.java.canonicalName.toString(), this) },
factoryProducer
)
}
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.ganjehViewModels(
noinline graphIdProvider: () -> String = { VM::class.java.canonicalName.toString() },
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
val application = application ?: throw IllegalArgumentException(
"ViewModel can be accessed only when Activity is attached"
)
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
}
return ViewModelLazy(VM::class, { Ganjeh.get(graphIdProvider(), this) }, factoryPromise)
}
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.ganjehViewModels(
graphId: String = VM::class.java.canonicalName.toString(),
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
return ganjehViewModels({ graphId }, factoryProducer)
}
package com.mrezanasirloo.ganjeh
import android.app.Activity
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelStore
/**
* @author: [email protected]
*
* [GanjehViewModelStore] keeps a [ViewModel] until its last [LifecycleOwner] is destroyed
*/
internal class GanjehViewModelStore(
private val onCleared: () -> Unit
) : ViewModelStore() {
private val owners = HashSet<String>(2)
fun add(owner: LifecycleOwner, ownerId: String) {
owners.add(ownerId)
owner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY && !owner.isChangingConfigurations()) {
remove(ownerId)
}
})
}
private fun remove(ownerId: String) {
owners.remove(ownerId)
if (owners.isEmpty()) {
clear()
onCleared.invoke()
}
}
private fun LifecycleOwner.isChangingConfigurations(): Boolean {
return when (this) {
is Fragment -> activity?.run { isChangingConfigurations } ?: false
is Activity -> isChangingConfigurations
else -> throw IllegalStateException("Unknown lifecycle owner $this")
}
}
}
@MRezaNasirloo
Copy link
Author

Usage:

private val sharedViewModel: YourViewModel by ganjehViewModels("some_unique_key") { viewModelFactory }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment