diff --git a/activity/build.gradle b/activity/build.gradle index d4a7bcd..7c79d2c 100644 --- a/activity/build.gradle +++ b/activity/build.gradle @@ -8,7 +8,7 @@ ext { apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.2" defaultConfig { diff --git a/core/build.gradle b/core/build.gradle index 7dbcfcb..ae02487 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -8,7 +8,7 @@ ext { apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.2" defaultConfig { diff --git a/fragment/build.gradle b/fragment/build.gradle index 00880de..280b459 100644 --- a/fragment/build.gradle +++ b/fragment/build.gradle @@ -9,7 +9,7 @@ ext { apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.2" defaultConfig { diff --git a/fragment/src/main/java/dev/marcellogalhardo/retained/fragment/FragmentRetainedObject.kt b/fragment/src/main/java/dev/marcellogalhardo/retained/fragment/FragmentRetainedObject.kt index 016bb88..8f3091f 100644 --- a/fragment/src/main/java/dev/marcellogalhardo/retained/fragment/FragmentRetainedObject.kt +++ b/fragment/src/main/java/dev/marcellogalhardo/retained/fragment/FragmentRetainedObject.kt @@ -13,7 +13,7 @@ import dev.marcellogalhardo.retained.core.retain * * ``` * class MyFragment : Fragment() { - * val vm by retainInFragment(getFragment = { this }) { ViewModel() } + * val vm by findFragment(getFragment = { this }) { ViewModel() } * } * class ViewModel(val name: String = "") * ``` @@ -27,8 +27,8 @@ import dev.marcellogalhardo.retained.core.retain public inline fun retainInFragment( noinline findFragment: () -> Fragment, key: String = T::class.java.name, - noinline initializer: (RetainedEntry) -> T, -): Retained = retain(key, findFragment, { owner -> owner.arguments }, initializer) + noinline instantiate: (RetainedEntry) -> T, +): Retained = retain(key, findFragment, { owner -> owner.arguments }, instantiate) /** * Returns a [Lazy] delegate to access a retained object by **default** scoped to this [Fragment]: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ef2bd6b..1095b1f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,16 @@ [versions] androidx-compose = '1.0.0' -androidx-fragment = "1.3.5" -androidx-lifecycle = "2.3.1" +androidx-fragment = "1.3.6" +androidx-lifecycle = "2.4.0" androidx-test = "1.3.0" kotlin = "1.5.20" navigation = "2.3.5" [libraries] -androidx-activity-core = { group = "androidx.activity", name = "activity-ktx", version = "1.2.3" } +androidx-activity-core = { group = "androidx.activity", name = "activity-ktx", version = "1.4.0" } androidx-appcompat-core = { group = "androidx.appcompat", name = "appcompat", version = "1.3.1" } -androidx-constraintlayout-core = { group = "androidx.constraintlayout", name = "constraintlayout", version = "2.0.4" } -androidx-core = { group = "androidx.core", name = "core", version = "1.3.2" } +androidx-constraintlayout-core = { group = "androidx.constraintlayout", name = "constraintlayout", version = "2.1.1" } +androidx-core = { group = "androidx.core", name = "core", version = "1.7.0" } androidx-fragment-core = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "androidx-fragment" } androidx-fragment-testing = { group = "androidx.fragment", name = "fragment-testing", version.ref = "androidx-fragment" } androidx-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" } diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle index 8834962..061f698 100644 --- a/integration-tests/build.gradle +++ b/integration-tests/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.2" defaultConfig { diff --git a/integration-tests/src/main/AndroidManifest.xml b/integration-tests/src/main/AndroidManifest.xml index 88ea67c..5b2baee 100644 --- a/integration-tests/src/main/AndroidManifest.xml +++ b/integration-tests/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ package="dev.marcellogalhardo.retained.integration.tests"> - + diff --git a/navigation-fragment/build.gradle b/navigation-fragment/build.gradle index fdf4c01..5eb7368 100644 --- a/navigation-fragment/build.gradle +++ b/navigation-fragment/build.gradle @@ -8,7 +8,7 @@ ext { apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.2" defaultConfig { diff --git a/navigation-view/build.gradle b/navigation-view/build.gradle index ec0fa08..5365701 100644 --- a/navigation-view/build.gradle +++ b/navigation-view/build.gradle @@ -8,7 +8,7 @@ ext { apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.2" defaultConfig { diff --git a/navigation/build.gradle b/navigation/build.gradle index e2444f0..3795308 100644 --- a/navigation/build.gradle +++ b/navigation/build.gradle @@ -8,7 +8,7 @@ ext { apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.2" defaultConfig { diff --git a/sample/build.gradle b/sample/build.gradle index cb2688b..f5b5318 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.2" defaultConfig { diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 0b14220..2e98e2e 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -3,19 +3,24 @@ package="dev.marcellogalhardo.retained.sample"> - + + + - + + - \ No newline at end of file + diff --git a/view/build.gradle b/view/build.gradle index 30fb650..f048921 100644 --- a/view/build.gradle +++ b/view/build.gradle @@ -8,7 +8,7 @@ ext { apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.2" defaultConfig { diff --git a/view/src/main/java/dev/marcellogalhardo/retained/view/ViewRetained.kt b/view/src/main/java/dev/marcellogalhardo/retained/view/ViewRetained.kt index 573be06..14259b9 100644 --- a/view/src/main/java/dev/marcellogalhardo/retained/view/ViewRetained.kt +++ b/view/src/main/java/dev/marcellogalhardo/retained/view/ViewRetained.kt @@ -7,19 +7,47 @@ import android.view.View import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelStoreOwner -import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.navigation.NavBackStackEntry import androidx.savedstate.SavedStateRegistryOwner -import androidx.savedstate.findViewTreeSavedStateRegistryOwner import dev.marcellogalhardo.retained.core.ExperimentalRetainedApi import dev.marcellogalhardo.retained.core.InternalRetainedApi import dev.marcellogalhardo.retained.core.Retained import dev.marcellogalhardo.retained.core.RetainedEntry import dev.marcellogalhardo.retained.core.retain +/** + * Returns a [Lazy] delegate to access a retained object by **default** scoped to this [View]: + * + * ``` + * class MyView(context: Context) : View(context) { + * val vm by retainInView(findInView = { this }) { ViewModel() } + * } + * class ViewModel(val name: String = "") + * ``` + * + * This property can be accessed only after this [View] is attached i.e., after + * [View.isAttachedToWindow], and access prior to that will result in [IllegalStateException]. + * + * @see retain + */ +@OptIn(InternalRetainedApi::class) +public inline fun retainInView( + noinline findView: () -> View, + key: String = "${findView().id}:${T::class.java.name}", + noinline instantiate: (RetainedEntry) -> T, +): Retained { + val owner = lazy { checkNotNull(findView().findViewTreeViewModelStoreOwner()) } + return retain( + key = key, + findViewModelStoreOwner = { owner.value }, + findSavedStateRegistryOwner = { owner.value as SavedStateRegistryOwner }, + findDefaultArgs = { owner.value.defaultArgs }, + instantiate = instantiate, + ) +} + /** * Returns a [Lazy] delegate to access a retained object by **default** scoped to this * [View] (e.g., [NavBackStackEntry], [Fragment] or [Activity]). @@ -39,11 +67,15 @@ import dev.marcellogalhardo.retained.core.retain @OptIn(InternalRetainedApi::class) @ExperimentalRetainedApi public inline fun View.retain( - key: String = id.toString(), - noinline getViewModelStoreOwner: () -> ViewModelStoreOwner = { findViewModelStoreOwnerOrThrow() }, - noinline getSavedStateRegistryOwner: () -> SavedStateRegistryOwner = { findViewTreeSavedStateRegistryOwner()!! }, + key: String = T::class.java.name, noinline instantiate: (RetainedEntry) -> T -): Retained = retain(key, getViewModelStoreOwner, getSavedStateRegistryOwner, { findViewTreeLifecycleOwner()!!.defaultArgs }, instantiate) +): Retained { + return retainInView( + findView = { this }, + key = key, + instantiate = instantiate, + ) +} /** * Returns a [Lazy] delegate to access a retained object by **default** scoped to the @@ -63,14 +95,24 @@ public inline fun View.retain( @OptIn(InternalRetainedApi::class) @ExperimentalRetainedApi public inline fun View.retainInActivity( - key: String = id.toString(), - activity: FragmentActivity = findActivity(), + key: String = T::class.java.name, noinline instantiate: (RetainedEntry) -> T -): Retained = retain(key, { activity }, { activity }, { activity.intent?.extras }, instantiate) +): Retained { + val activity = findActivity() + return retain( + key = key, + findViewModelStoreOwner = { activity }, + findSavedStateRegistryOwner = { activity }, + findDefaultArgs = { activity.intent?.extras }, + instantiate = instantiate, + ) +} @PublishedApi -internal fun View.findViewModelStoreOwnerOrThrow(): ViewModelStoreOwner = findViewTreeViewModelStoreOwner() - ?: throw IllegalStateException("Your view is not yet attached, and thus its ViewModelStoreOwner is null.") +internal fun View.findViewModelStoreOwnerOrThrow(): ViewModelStoreOwner { + return findViewTreeViewModelStoreOwner() + ?: error("Your view is not yet attached, and thus its ViewModelStoreOwner is null.") +} @PublishedApi internal fun View.findActivity(): FragmentActivity { @@ -79,11 +121,11 @@ internal fun View.findActivity(): FragmentActivity { if (currentContext is FragmentActivity) return currentContext currentContext = (context as ContextWrapper).baseContext } - throw IllegalStateException("Your view is not attached to an activity.") + error("Your view is not attached to an activity.") } @PublishedApi -internal val LifecycleOwner.defaultArgs: Bundle +internal val ViewModelStoreOwner.defaultArgs: Bundle get() = when (this) { is Activity -> intent?.extras is Fragment -> arguments