{"id":13611472,"url":"https://github.com/sebaslogen/resaca","last_synced_at":"2026-04-02T13:16:03.774Z","repository":{"id":38071204,"uuid":"431489181","full_name":"sebaslogen/resaca","owner":"sebaslogen","description":"Compose Multiplatform library to scope ViewModels to a Composable, surviving configuration changes and navigation","archived":false,"fork":false,"pushed_at":"2025-04-01T07:08:34.000Z","size":1231,"stargazers_count":502,"open_issues_count":0,"forks_count":10,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-01T07:24:12.989Z","etag":null,"topics":["android","android-architecture","android-library","architecture-components","compose","compose-multiplatform","compose-multiplatform-library","hilt-dependency-injection","koin-dependency-injection","kotlin","viewmodel"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sebaslogen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-24T13:09:58.000Z","updated_at":"2025-04-01T07:08:33.000Z","dependencies_parsed_at":"2023-12-02T13:26:10.790Z","dependency_job_id":"ffe91ea5-3f47-4a95-b928-85a19a7221ed","html_url":"https://github.com/sebaslogen/resaca","commit_stats":{"total_commits":708,"total_committers":4,"mean_commits":177.0,"dds":"0.15536723163841804","last_synced_commit":"b4245fc01a11338086be914d00db781b09929185"},"previous_names":[],"tags_count":70,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebaslogen%2Fresaca","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebaslogen%2Fresaca/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebaslogen%2Fresaca/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebaslogen%2Fresaca/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sebaslogen","download_url":"https://codeload.github.com/sebaslogen/resaca/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247809963,"owners_count":20999816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","android-architecture","android-library","architecture-components","compose","compose-multiplatform","compose-multiplatform-library","hilt-dependency-injection","koin-dependency-injection","kotlin","viewmodel"],"created_at":"2024-08-01T19:01:55.746Z","updated_at":"2026-04-02T13:16:03.757Z","avatar_url":"https://github.com/sebaslogen.png","language":"Kotlin","readme":"[![Maven Central Release](https://img.shields.io/maven-central/v/io.github.sebaslogen/resaca)](https://central.sonatype.com/artifact/io.github.sebaslogen/resaca)\n[![Build Status](https://github.com/sebaslogen/resaca/actions/workflows/build.yml/badge.svg)](https://github.com/sebaslogen/resaca/actions/workflows/build.yml)\n[![codecov](https://codecov.io/github/sebaslogen/resaca/graph/badge.svg?token=GRPMOQ3COS)](https://codecov.io/github/sebaslogen/resaca)\n[![javadoc](https://javadoc.io/badge2/io.github.sebaslogen/resaca/javadoc.svg)](https://javadoc.io/doc/io.github.sebaslogen/resaca)\n[![API 21+](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)\n[![GitHub license](https://img.shields.io/github/license/sebaslogen/resaca)](https://github.com/sebaslogen/resaca/blob/main/LICENSE)\n[![Supported Compose Platforms](https://img.shields.io/badge/Platforms-Android_|_iOS_|_Desktop_JVM_|_Web-blue)](https://central.sonatype.com/namespace/io.github.sebaslogen)\n[![Kotlin Weekly](https://user-images.githubusercontent.com/1936647/277181222-6aba882e-eafe-4a38-b8ef-631bb66b442f.svg)](https://mailchi.mp/kotlinweekly/kotlin-weekly-285)\n[![Android Weekly](https://github-production-user-asset-6210df.s3.amazonaws.com/1936647/277184200-dbb226b8-9730-49b0-8b7e-23873debea1e.svg)](https://androidweekly.net/issues/issue-593)\n\nArticle about this library: [Every Composable deserves a ViewModel](https://engineering.q42.nl/compose/)\n\n# Resaca 🍹\n\nThe right scope for objects and View Models in Android [Compose](https://developer.android.com/jetpack/compose).\n\nResaca provides a simple way to **keep a Jetpack ViewModel** (or any other object) **in memory during the lifecycle of a** `@Composable` **function** and automatically\nclean it up when not needed anymore. This means, it retains your object or ViewModel across recompositions, during configuration changes, and also when the\ncontainer Fragment or Compose Navigation destination goes into the backstack.\n\nWith Resaca you can create fine grained ViewModels for fine grained Composables and finally have reusable components across screens.\n\n# Why\n\nCompose allows the creation of fine-grained UI components that can be easily reused like Lego blocks 🧱. Well architected Android apps isolate functionality in\nsmall business logic components (like use cases, interactors, repositories, etc.) that are also reusable like Lego blocks 🧱.\n\nScreens are built using Compose components together with business logic components, and the standard tool to connect these two types of components is\na [Jetpack ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel). Unfortunately, ViewModels can only be scoped to a whole screen (or\nlarger scope), but not to smaller Compose components on the screen.\n\nIn practice, this means that we are gluing UI Lego blocks with business logic Lego blocks using a big glue class for the whole screen, the ViewModel 🗜.\n\nUntil now...\n\n# Installation\n\nJust include the library (less than 5Kb):\n\n\n\u003cdetails open\u003e\n  \u003csummary\u003eKotlin (KTS)\u003c/summary\u003e\n  \n```kotlin\n// In module's build.gradle.kts\ndependencies {\n    // The latest version of the lib is available in the badget at the top from Maven Central, replace X.X.X with that version\n    implementation(\"io.github.sebaslogen:resaca:X.X.X\")\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eGroovy\u003c/summary\u003e\n  \n```gradle\ndependencies {\n    // The latest version of the lib is available in the badget at the top from Maven Central, replace X.X.X with that version\n    implementation 'io.github.sebaslogen:resaca:X.X.X'\n}\n```\n\u003c/details\u003e\n\n# Usage\n\nInside your `@Composable` function create and retrieve an object using `rememberScoped` to remember any type of object (except ViewModels). For ViewModels use\n`viewModelScoped`. That's all 🪄✨\n\nExamples:\n\n\u003cdetails open\u003e\n  \u003csummary\u003eScope an object to a Composable\u003c/summary\u003e\n  \n```kotlin\n@Composable\nfun DemoScopedObject() {\n    val myRepository: MyRepository = rememberScoped { MyRepository() }\n    DemoComposable(inputObject = myRepository)\n}\n```\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n  \u003csummary\u003eScope a ViewModel to a Composable\u003c/summary\u003e\n  \n```kotlin\n@Composable\nfun DemoScopedViewModel() {\n    val myScopedVM: MyViewModel = viewModelScoped()\n    DemoComposable(inputObject = myScopedVM)\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eScope a ViewModel with a dependency to a Composable\u003c/summary\u003e\n  \n```kotlin\n@Composable\nfun DemoScopedViewModelWithDependency() {\n    val myScopedVM: MyViewModelWithDependencies = viewModelScoped { MyViewModelWithDependencies(myDependency) }\n    DemoComposable(inputObject = myScopedVM)\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eScope a ViewModel with a key to a Composable\u003c/summary\u003e\n  \n```kotlin\n@Composable\nfun DemoViewModelWithKey() {\n    val scopedVMWithFirstKey: MyViewModel = viewModelScoped(\"myFirstKey\") { MyViewModel(\"myFirstKey\") }\n    val scopedVMWithSecondKey: MyViewModel = viewModelScoped(\"mySecondKey\") { MyViewModel(\"mySecondKey\") }\n    // We now have 2 ViewModels of the same type with different data inside the same Composable scope\n    DemoComposable(inputObject = scopedVMWithFirstKey)\n    DemoComposable(inputObject = scopedVMWithSecondKey)\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eScope a ViewModel with a dependency injected with Koin to a Composable\u003c/summary\u003e\n  \n```kotlin\n@Composable\nfun DemoKoinInjectedViewModelWithDependency() {\n    val myInjectedScopedVM: MyViewModelWithDependencies = viewModelScoped() { getKoin().get { parametersOf(myConstructorDependency) } }\n    DemoComposable(inputObject = myInjectedScopedVM)\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eScope a ViewModel with a dependency injected with Metro to a Composable\u003c/summary\u003e\n  \n```kotlin\n@Composable\nfun DemoMetroInjectedViewModelScoped() {\n    val myInjectedScopedVM: MyViewModel = metroViewModelScoped(factory = myMetroViewModelFactory)\n    DemoComposable(inputObject = myInjectedScopedVM)\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eScope a ViewModel with a clear delay to a Composable\u003c/summary\u003e\n  \n```kotlin\n@Composable\nfun DemoScopedViewModelWithClearDelay() {\n    // The ViewModel will be kept in memory for 5 seconds after the Composable is disposed,\n    // giving it a chance to be reused if the Composable returns to composition (e.g. quick navigation back and forth)\n    val myScopedVM: MyViewModel = viewModelScoped(clearDelay = 5.seconds)\n    DemoComposable(inputObject = myScopedVM)\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eScope an object with a clear delay to a Composable\u003c/summary\u003e\n  \n```kotlin\n@Composable\nfun DemoScopedObjectWithClearDelay() {\n    val myRepository: MyRepository = rememberScoped(clearDelay = 5.seconds) { MyRepository() }\n    DemoComposable(inputObject = myRepository)\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eUse a different ViewModel for each item in a LazyColumn and scope them to the Composable that contains the LazyColumn\u003c/summary\u003e\n  \n```kotlin\n@Composable\nfun DemoManyViewModelsScopedOutsideTheLazyColumn(listItems: List\u003cInt\u003e = (1..1000).toList()) {\n    val keys = rememberKeysInScope(inputListOfKeys = listItems)\n    LazyColumn() {\n        items(items = listItems, key = { it }) { item -\u003e\n            val myScopedVM: MyViewModel = viewModelScoped(key = item, keyInScopeResolver = keys)\n            DemoComposable(inputObject = myScopedVM)\n        }\n    }\n}\n```\n\u003c/details\u003e\n\nOnce you use the `rememberScoped` or `viewModelScoped` functions, the same object will be restored as long as the Composable is part of the composition, even if\nit _temporarily_ leaves composition on configuration change (e.g. screen rotation, change to dark mode, etc.) or while being in the backstack.\n\nFor ViewModels, in addition to being forgotten when they're really not needed anymore, their _coroutineScope_ will also be automatically canceled because\nViewModel's `onCleared` method will be automatically called.\n\n\u003e 💡 _Optional key_: a key can be provided to the call, `rememberScoped(key) { ... }` or `viewModelScoped(key) { ... }`. This makes possible to forget an old\nobject when there is new input data during a recomposition (e.g. a new input id for your ViewModel).\n\n\u003e 💡 _Optional clearDelay_: a `clearDelay` can be provided to delay the disposal of the scoped object after the Composable is removed from composition.\nThis is useful when a Composable might briefly leave composition and return (e.g. quick navigation back and forth), and you want to avoid recreating expensive objects.\nUsage: `rememberScoped(clearDelay = 5.seconds) { ... }` or `viewModelScoped(clearDelay = 5.seconds)`.\nIf the Composable returns to composition before the delay expires, the disposal is cancelled and the same object is reused.\n\n\u003e ⚠️ Note that ViewModels remembered with `viewModelScoped` **should not be created** using any of the Compose `viewModel()` or `ViewModelProviders` factories,\notherwise they will be retained in the scope of the screen regardless of `viewModelScoped`. Also, if a ViewModel is remembered with `rememberScoped`, instead of `viewModelScoped`, then its\nclean-up method won't be called, so it's always better to use `viewModelScoped` for ViewModels.\n\n# Sample use cases\n\nHere are some sample use cases reported by the users of this library:\n\n- ❤️ Isolated and stateful UI components like a **favorite button** that are widely used across the screens. This `FavoriteViewModel` can be very small, focused\n  and only require an id to work without affecting the rest of the screen's UI and state.\n- 🗪 **Dialog pop-ups** can have their own business-logic with state that is better to isolate in a separate ViewModel but the lifespan of these dialogs might be short, \nso it's important to clean-up the ViewModel associated to a Dialog after it has been closed.\n- 📃 A LazyColumn with a **ViewModel per list item**. Each item can have its own complex logic in an isolated ViewModel that will be lazily loaded when the item is\nvisible for the first time. The ViewModel will cleared and destroyed when the item is not part of the list in the source data or the whole LazyColumn is removed.\n- 📄📄 Multiple instances of the same type of ViewModel in a screen with a **view-pager**. This screen will have multiple sub-pages that use the same ViewModel\n  class with different ids. For example, a screen of holiday destinations with multiple pages and each page with its own `HolidayDestinationViewModel`.\n\n# Demo app\n\nDemo app [documentation can be found here](https://github.com/sebaslogen/resaca/blob/main/sample/README.md).\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1936647/144597718-db7e8901-a726-4871-abf8-7fc53333a90e.gif\" alt=\"Resaca-demo\" width=\"340\" height=\"802\" /\u003e\n\u003c/p\u003e\n\nBefore                     |  After backstack navigation \u0026 configuration change\n:-------------------------:|:-------------------------:\n\u003cimg width=\"429\" alt=\"Before\" src=\"https://user-images.githubusercontent.com/1936647/146558764-42333455-2dd8-43a9-932b-3249d42b7a7d.png\"\u003e  |  \u003cimg width=\"430\" alt=\"After\" src=\"https://user-images.githubusercontent.com/1936647/146558775-8c77231c-ed0f-4f52-b9b8-cdf9029e106c.png\"\u003e\n\n# Multiplatform support\n\u003cdetails\u003e\n  \u003csummary\u003eResaca works in Kotlin Multiplaform and also in Compose Multiplatform for Android, iOS, Desktop-JVM and Web targets:\u003c/summary\u003e\n\n## Compose Multiplatform\nSince version 4.0, Resaca supports Compose Multiplatform for Android, iOS, Desktop-JVM and Web targets.\nTo see an example of usage and configuration check the [Sample Compose Multiplatform project](https://github.com/sebaslogen/resaca/blob/main/samplecmp/sampleComposeApp/src/commonMain/kotlin/App.kt) in the `samplecmp` module.\n\n## Kotlin Multiplatform\nResaca is a Kotlin Multiplatform library and can be used in any Kotlin Multiplatform project that targets Android, iOS, Desktop-JVM and Web. Nevertheless, \nResaca does not make sense in a SwiftUI project because it's a Compose only library, instead, if you want to scope ViewModels to SwiftUI views, \nyou can look at the solutions provided in the first comments of this ticket https://github.com/sebaslogen/resaca/issues/91.\n\u003c/details\u003e\n\n# Dependency injection support\n\nThis library does not influence how your app provides or creates objects so it's dependency injection strategy and framework agnostic.\n\nNevertheless, this library supports three of the main **dependency injection frameworks**:\n\n## Hilt 🗡️\n\u003cdetails\u003e\n  \u003csummary\u003eHilt details\u003c/summary\u003e\n  \n[HILT](https://dagger.dev/hilt/quick-start) (Dagger) support is available in a small extension of this library: [**resaca-hilt**](https://github.com/sebaslogen/resaca/tree/main/resacahilt/).\n\n[Documentation and installation instructions are available here](https://github.com/sebaslogen/resaca/tree/main/resacahilt/README.md).\n\u003c/details\u003e\n  \n## Koin 🪙\n\u003cdetails\u003e\n  \u003csummary\u003eKoin details\u003c/summary\u003e\n  \n[Koin](https://insert-koin.io/) is out of the box supported by simply changing the way you request a dependency.\n\nInstead of using the `getViewModel` or `koinViewModel` functions from Koin, you have to use the standard way of getting a dependency from Koin `getKoin().get()`.\n\nUsage example: `val viewModel: MyViewModel = viewModelScoped(myId) { getKoin().get { parametersOf(myId) } }`\n\n\u003e **Note**: if you plan to use a ViewModel with a [SavedStateHandle](https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-savedstate), then you need to use the `koinViewModelScoped` function from the small extension library [**resaca-koin**](https://github.com/sebaslogen/resaca/blob/main/resacakoin/Readme.md).\n\u003c/details\u003e\n\n## Metro 🚇\n\u003cdetails\u003e\n  \u003csummary\u003eMetro details\u003c/summary\u003e\n\n[Metro](https://zacsweers.github.io/metro/) is a Kotlin-first dependency injection framework that uses a compiler plugin for compile-time dependency graph validation and code generation.\nMetro is supported through the small extension library [**resaca-metro**](https://github.com/sebaslogen/resaca/tree/main/resacametro/).\n\nUsage example:\n```kotlin\nval viewModel: MyViewModel = metroViewModelScoped(factory = myMetroViewModelFactory)\n```\n\nWhere `myMetroViewModelFactory` is a `ViewModelProvider.Factory` backed by your Metro `@DependencyGraph`.\n\nMetro's assisted injection is also supported:\n```kotlin\nval viewModel: MyViewModel = metroViewModelScoped(\n    key = myId,\n    factory = createAssistedFactory(metroGraph, myId)\n)\n```\n\n[Documentation and installation instructions are available here](https://github.com/sebaslogen/resaca/tree/main/resacametro/README.md).\n\u003c/details\u003e\n\n# Scoping in a LazyColumn, LazyRow, etc\nThis is handy for the typical case where you have a lazy list of items and you want to have a separate ViewModel for each item in the list, using the `viewModelScoped` function.\n\u003cdetails\u003e\n  \u003csummary\u003eHow to use `rememberKeysInScope` to control the lifecycle of a scoped object in a Lazy* list\u003c/summary\u003e\n  \nWhen using the Lazy* family of Composables it is recommended that -just above the call to the Lazy* Composable- you use `rememberKeysInScope` with a list of \nkeys corresponding to the items used in the Lazy* Composable to obtain a `KeyInScopeResolver` (it's already highly recommended in Compose that items in a Lazy* list have unique keys).\n\nThen, in the Lazy* Composable, once you are creating an item and you need an object or ViewModel for that item, \nall you have to do is include in the call to `rememberScoped`/`viewModelScoped` the key for the current item and the `KeyInScopeResolver` you previously got from `rememberKeysInScope`.\n\nWith this setup, when an item of the Lazy* list becomes visible for the first time, its associated `rememberScoped`/`viewModelScoped` object will be created and even if the item is scrolled away, the scoped object will still be alive. Only once the associated key is not present anymore in the list provided to `rememberKeysInScope` and the item is either not part of the Lazy* list anymore or scrolled away, then the associated object will be cleared and destroyed.\n\n🏷️ Example of a separate ViewModel for each item in a LazyColumn and scope them to the Composable that contains the LazyColumn\n  \n```kotlin\n@Composable\nfun DemoManyViewModelsScopedOutsideTheLazyColumn(listItems: List\u003cInt\u003e = (1..1000).toList()) {\n    val keys = rememberKeysInScope(inputListOfKeys = listItems)\n    LazyColumn() {\n        items(items = listItems, key = { it }) { item -\u003e\n            val myScopedVM: MyViewModel = viewModelScoped(key = item, keyInScopeResolver = keys)\n            DemoComposable(inputObject = myScopedVM)\n        }\n    }\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eCombine `clearDelay` with `keyInScopeResolver` in a Lazy* list\u003c/summary\u003e\n\nYou can use `clearDelay` alongside `keyInScopeResolver` in the same Lazy* list. This is useful when some items need a grace period before being cleared (e.g. the first item uses `clearDelay` for a smoother experience) while others should stay in memory as long as they are in the list (via `keyInScopeResolver`).\n\n\u003e 💡 Note: `clearDelay` and `keyInScopeResolver` are independent mechanisms. An item using `clearDelay` without `keyInScopeResolver` will be cleared after the delay, regardless of whether it is still in the list. An item using `keyInScopeResolver` without `clearDelay` will be kept alive as long as its key is present in the list.\n\n🏷️ Example of a LazyColumn where the first item uses `clearDelay` and the rest use `keyInScopeResolver`\n\n```kotlin\n@Composable\nfun DemoMixedScopingInLazyColumn(listItems: List\u003cInt\u003e = (1..1000).toList()) {\n    val keys = rememberKeysInScope(inputListOfKeys = listItems)\n    LazyColumn() {\n        items(items = listItems, key = { it }) { item -\u003e\n            val myScopedVM: MyViewModel = if (item == 1) {\n                // This item's ViewModel will be cleared 5 seconds after scrolling away\n                viewModelScoped(key = item, clearDelay = 5.seconds)\n            } else {\n                // These items' ViewModels stay alive as long as the item is in the list\n                viewModelScoped(key = item, keyInScopeResolver = keys)\n            }\n            DemoComposable(inputObject = myScopedVM)\n        }\n    }\n}\n```\n\u003c/details\u003e\n\n### General considerations for State Hoisting\nWhen a Composable is used more than once in the same screen with the same input, then the ViewModel (or business logic object) should be provided only once \nwith `viewModelScoped` at a higher level in the composition tree using Compose's [State Hoisting](https://developer.android.com/jetpack/compose/state#state-hoisting).\n\n# Why not use remember?\n\u003cdetails\u003e\n  \u003csummary\u003ePros, cons and alternatives to remember:\u003c/summary\u003e\n\n**[Remember](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#remember(kotlin.Function0))** will keep our object alive as\nlong as the Composable is not disposed of. Unfortunately, there are a few cases where our Composable will be disposed of and then added again, breaking the\nlifecycle parity with the remember function. 😢\n\u003cdetails\u003e\n  \u003csummary\u003ePros and Cons\u003c/summary\u003e\n  \n**_Pros_**\n\n- Simple API\n\n**_Cons_**\n\n- remember value will **NOT** survive a configuration change\n- remember value will **NOT** survive when going into the backstack\n- remember value will **NOT** survive a process death\n\u003c/details\u003e\n\n**[RememberSaveable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/saveable/package-summary#rememberSaveable(kotlin.Array,androidx.compose.runtime.saveable.Saver,kotlin.String,kotlin.Function0))**\nwill follow the lifecycle of the Composable, even in the few cases where the Composable is temporarily disposed of. But the object we want to remember needs to\nimplement Parcelable or the [Saver](https://developer.android.com/reference/kotlin/androidx/compose/runtime/saveable/Saver) interface in an additional class. 😢\nImplementing these interfaces might not trivial.\n\u003cdetails\u003e\n  \u003csummary\u003ePros and Cons\u003c/summary\u003e\n  \n**_Pros_**\n\n- rememberSaveable value will survive a configuration change\n- rememberSaveable value will survive when going into the backstack\n- rememberSaveable value will survive a process death\n\n**_Cons_**\n\n- **Complex** integration work is required to correctly implement Parcelable\n  or [Saver](https://developer.android.com/reference/kotlin/androidx/compose/runtime/saveable/Saver)\n\u003c/details\u003e\n  \n### Resaca's RememberScoped 🪄✨\n\n**[RememberScoped](https://github.com/sebaslogen/resaca/blob/main/resaca/src/main/java/com/sebaslogen/resaca/ScopedMemoizers.kt#L33)** function keeps\nobjects in memory during the lifecycle of the Composable, even in a few cases where the Composable is disposed of, and then added again. Therefore, it will\nretain objects longer than the `remember` function but shorter than `rememberSaveable` because there is no serialization involved.\n\u003cdetails\u003e\n  \u003csummary\u003ePros and Cons\u003c/summary\u003e\n  \n**_Pros_**\n\n- Simple API\n- rememberScoped/viewModelScoped value will survive a configuration change\n- rememberScoped/viewModelScoped value will survive when going into the backstack\n\n**_Cons_**\n\n- rememberScoped/viewModelScoped value will **NOT** survive a process death\n\u003c/details\u003e\n\u003c/details\u003e\n\n# Lifecycle explained\n\n\u003cdetails\u003e\n  \u003csummary\u003eHow does the lifecycle of the Resaca scoped objects work and some lifecycle illustrated examples:\u003c/summary\u003e\n\n**[RememberScoped](https://github.com/sebaslogen/resaca/blob/main/resaca/src/main/java/com/sebaslogen/resaca/ScopedMemoizers.kt#L33)** function keeps\nobjects in memory during the lifecycle of the Composable, even in a few cases where the Composable is disposed of, and then added again.\n\n## RememberScoped lifecycle internal implementation details\n\nThis project uses internally a ViewModel as a container to store all scoped ViewModels and scoped objects.\n\n\u003cdetails\u003e\n  \u003csummary\u003eWhat happens when a Composable is disposed?\u003c/summary\u003e\n  \nWhen a Composable is disposed of, we don't know for sure if it will return again later. So at the moment of disposal, we mark in our container the associated\nobject to be disposed of after the next frame when the Activity is resumed. During the span of time of this next frame, a few things can happen:\n\n- The Composable is not part of the composition anymore after the next frame and the associated object is disposed of. 🚮\n- The LifecycleOwner of the disposed Composable (i.e. the navigation destination where the Composable lived) is paused (e.g. screen went to background) before\n  the next frame happened. Then the disposal of the scoped object is canceled, but the object is still marked for disposal at a later stage.\n    - This can happen when the application goes through a configuration change and the container Activity is recreated.\n    - Also when the Composable is part of a Fragment that has been pushed to the backstack.\n    - And also when the Composable is part of a Compose Navigation destination that has been pushed to the backstack.\n- When the LifecycleOwner of the disposed Composable is resumed (e.g. Fragment comes back to foreground), then the disposal of the associated object is\n  scheduled again to happen after the next frame when the Activity is resumed. At this point two things can happen:\n    - The Composable becomes part of the composition again and the `rememberScoped`/`viewModelScoped` function restores the associated object while also\n      cancelling any pending disposal in the next frame when the Activity is resumed. 🎉\n    - The Composable is not part of the composition anymore after the next frame and then the associated object is disposed of. 🚮\n\n\u003e **Note**:\n\n- To know that the same Composable is being added to the composition again after being disposed of, we generate a random ID and store it with `rememberSaveable`\n  , which survives recomposition, recreation and even process death.\n- To detect when the requester Composable is not needed anymore (has left composition and the screen for good), the ScopedViewModelContainer also observes the\n  resume/pause Lifecycle events of the owner of this ScopedViewModelContainer (i.e. Activity, Fragment, or Compose Navigation destination)\n\u003c/details\u003e\n\n## Lifecycle example\n\n![Compose state scope](https://user-images.githubusercontent.com/1936647/144682707-dd06e2ee-5542-400b-9a8d-cb27fb7c28e8.png)\n\nThis diagram shows the lifecycle of three Composables (A, B, and C) with their respective objects scoped with the `rememberScoped` function. All these\nComposables are part of a Composable destination which is part of a Fragment which is part of an Activity which is part of the App. The horizontal arrows\nrepresent different lifecycle events, events like Composable being disposed of, Composable screen going into the backstack, Fragment going into the backstack\nand returning from backstack, or Activity recreated after a configuration change.\n\nThe existing alternatives to replicate the lifecycle of the objects in the diagram without using `rememberScoped` are:\n\n- Object A lifecycle could only be achieved using the Compose `viewModel()` or `ViewModelProviders` factories.\n- Object B lifecycle could only be achieved using the Compose `remember()` function.\n- Object C lifecycle could not be achieved neither by using ViewModel provider functions nor Compose `remember` functions.\n\u003c/details\u003e\n","funding_links":[],"categories":["Libraries","Kotlin"],"sub_categories":["🏗 Architecture"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsebaslogen%2Fresaca","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsebaslogen%2Fresaca","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsebaslogen%2Fresaca/lists"}