Git Product home page Git Product logo

sebaslogen / resaca Goto Github PK

View Code? Open in Web Editor NEW
389.0 4.0 9.0 1.06 MB

Compose Multiplatform library to scope ViewModels to a Composable, surviving configuration changes and navigation

License: MIT License

Kotlin 98.94% Swift 0.62% HTML 0.34% CSS 0.10%
kotlin android compose architecture-components android-architecture android-library hilt-dependency-injection viewmodel compose-multiplatform compose-multiplatform-library

resaca's Introduction

Maven Central Release Build Status codecov javadoc API 21+ GitHub license Supported Compose Platforms Kotlin Weekly Android Weekly

Article about this library: Every Composable deserves a ViewModel

Resaca ๐Ÿน

The right scope for objects and View Models in Android Compose.

Resaca provides a simple way to keep a Jetpack ViewModel (or any other object) in memory during the lifecycle of a @Composable function and automatically clean it up when not needed anymore. This means, it retains your object or ViewModel across recompositions, during configuration changes, and also when the container Fragment or Compose Navigation destination goes into the backstack.

With Resaca you can create fine grained ViewModels for fine grained Composables and finally have reusable components across screens.

Why

Compose allows the creation of fine-grained UI components that can be easily reused like Lego blocks ๐Ÿงฑ. Well architected Android apps isolate functionality in small business logic components (like use cases, interactors, repositories, etc.) that are also reusable like Lego blocks ๐Ÿงฑ.

Screens are built using Compose components together with business logic components, and the standard tool to connect these two types of components is a Jetpack ViewModel. Unfortunately, ViewModels can only be scoped to a whole screen (or larger scope), but not to smaller Compose components on the screen.

In 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 ๐Ÿ—œ.

Until now...

Installation

Just include the library (less than 5Kb):

Kotlin (KTS)
// In module's build.gradle.kts
dependencies {
    // The latest version of the lib is available in the badget at the top from Maven Central, replace X.X.X with that version
    implementation("io.github.sebaslogen:resaca:X.X.X")
}
Groovy
dependencies {
    // The latest version of the lib is available in the badget at the top from Maven Central, replace X.X.X with that version
    implementation 'io.github.sebaslogen:resaca:X.X.X'
}

Usage

Inside your @Composable function create and retrieve an object using rememberScoped to remember any type of object (except ViewModels). For ViewModels use viewModelScoped. That's all ๐Ÿช„โœจ

Examples:

Scope an object to a Composable
@Composable
fun DemoScopedObject() {
    val myRepository: MyRepository = rememberScoped { MyRepository() }
    DemoComposable(inputObject = myRepository)
}
Scope a ViewModel to a Composable
@Composable
fun DemoScopedViewModel() {
    val myScopedVM: MyViewModel = viewModelScoped()
    DemoComposable(inputObject = myScopedVM)
}
Scope a ViewModel with a dependency to a Composable
@Composable
fun DemoScopedViewModelWithDependency() {
    val myScopedVM: MyViewModelWithDependencies = viewModelScoped { MyViewModelWithDependencies(myDependency) }
    DemoComposable(inputObject = myScopedVM)
}
Scope a ViewModel with a key to a Composable
@Composable
fun DemoViewModelWithKey() {
    val scopedVMWithFirstKey: MyViewModel = viewModelScoped("myFirstKey") { MyViewModel("myFirstKey") }
    val scopedVMWithSecondKey: MyViewModel = viewModelScoped("mySecondKey") { MyViewModel("mySecondKey") }
    // We now have 2 ViewModels of the same type with different data inside the same Composable scope
    DemoComposable(inputObject = scopedVMWithFirstKey)
    DemoComposable(inputObject = scopedVMWithSecondKey)
}
Scope a ViewModel with a dependency injected with Koin to a Composable
@Composable
fun DemoKoinInjectedViewModelWithDependency() {
    val myInjectedScopedVM: MyViewModelWithDependencies = viewModelScoped() { getKoin().get { parametersOf(myConstructorDependency) } }
    DemoComposable(inputObject = myInjectedScopedVM)
}
Use a different ViewModel for each item in a LazyColumn and scope them to the Composable that contains the LazyColumn
@Composable
fun DemoManyViewModelsScopedOutsideTheLazyColumn(listItems: List<Int> = (1..1000).toList()) {
    val keys = rememberKeysInScope(inputListOfKeys = listItems)
    LazyColumn() {
        items(items = listItems, key = { it }) { item ->
            val myScopedVM: MyViewModel = viewModelScoped(key = item, keyInScopeResolver = keys)
            DemoComposable(inputObject = myScopedVM)
        }
    }
}

Once 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 it temporarily leaves composition on configuration change (e.g. screen rotation, change to dark mode, etc.) or while being in the backstack.

For ViewModels, in addition to being forgotten when they're really not needed anymore, their coroutineScope will also be automatically canceled because ViewModel's onCleared method will be automatically called.

๐Ÿ’ก Optional key: a key can be provided to the call, rememberScoped(key) { ... } or viewModelScoped(key) { ... }. This makes possible to forget an old object when there is new input data during a recomposition (e.g. a new input id for your ViewModel).

โš ๏ธ Note that ViewModels remembered with viewModelScoped should not be created using any of the Compose viewModel() or ViewModelProviders factories, otherwise 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 clean-up method won't be called, so it's always better to use viewModelScoped for ViewModels.

Sample use cases

Here are some sample use cases reported by the users of this library:

  • โค๏ธ Isolated and stateful UI components like a favorite button that are widely used across the screens. This FavoriteViewModel can be very small, focused and only require an id to work without affecting the rest of the screen's UI and state.
  • ๐Ÿ—ช 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, so it's important to clean-up the ViewModel associated to a Dialog after it has been closed.
  • ๐Ÿ“ƒ 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 visible 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.
  • ๐Ÿ“„๐Ÿ“„ 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 class with different ids. For example, a screen of holiday destinations with multiple pages and each page with its own HolidayDestinationViewModel.

Demo app

Demo app documentation can be found here.

Resaca-demo

Before After backstack navigation & configuration change
Before After

Multiplatform support

Resaca works in Kotlin Multiplaform and also in Compose Multiplatform for Android and iOS targets:

Compose Multiplatform

Since version 4.0, Resaca supports Compose Multiplatform for Android and iOS targets. To see an example of usage and configuration check the Sample Compose Multiplatform project in the samplecmp module.

Kotlin Multiplatform

Resaca is a Kotlin Multiplatform library and can be used in any Kotlin Multiplatform project that targets Android or iOS. Nevertheless, Resaca 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, you can look at the solutions provided in the first comments of this ticket #91.

Dependency injection support

This library does not influence how your app provides or creates objects so it's dependency injection strategy and framework agnostic.

Nevertheless, this library supports two of the main dependency injection frameworks:

Hilt ๐Ÿ—ก๏ธ

Hilt details

HILT (Dagger) support is available in a small extension of this library: resaca-hilt.

Documentation and installation instructions are available here.

Koin ๐Ÿช™

Koin details

Koin is out of the box supported by simply changing the way you request a dependency.

Instead of using the getViewModel or koinViewModel functions from Koin, you have to use the standard way of getting a dependency from Koin getKoin().get().

Usage example: val viewModel: MyViewModel = viewModelScoped(myId) { getKoin().get { parametersOf(myId) } }

Note: if you plan to use a ViewModel with a SavedStateHandle, then you need to use the koinViewModelScoped function from the small extension library resaca-koin.

Scoping in a LazyColumn, LazyRow, etc

This 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.

How to use `rememberKeysInScope` to control the lifecycle of a scoped object in a Lazy* list

When 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 keys 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).

Then, in the Lazy* Composable, once you are creating an item and you need an object or ViewModel for that item, all 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.

With 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.

๐Ÿท๏ธ Example of a separate ViewModel for each item in a LazyColumn and scope them to the Composable that contains the LazyColumn

@Composable
fun DemoManyViewModelsScopedOutsideTheLazyColumn(listItems: List<Int> = (1..1000).toList()) {
    val keys = rememberKeysInScope(inputListOfKeys = listItems)
    LazyColumn() {
        items(items = listItems, key = { it }) { item ->
            val myScopedVM: MyViewModel = viewModelScoped(key = item, keyInScopeResolver = keys)
            DemoComposable(inputObject = myScopedVM)
        }
    }
}

General considerations for State Hoisting

When 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 with viewModelScoped at a higher level in the composition tree using Compose's State Hoisting.

Why not use remember?

Pros, cons and alternatives to remember:

Remember will keep our object alive as long 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 lifecycle parity with the remember function. ๐Ÿ˜ข

Pros and Cons

Pros

  • Simple API

Cons

  • remember value will NOT survive a configuration change
  • remember value will NOT survive when going into the backstack
  • remember value will NOT survive a process death

RememberSaveable will 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 implement Parcelable or the Saver interface in an additional class. ๐Ÿ˜ข Implementing these interfaces might not trivial.

Pros and Cons

Pros

  • rememberSaveable value will survive a configuration change
  • rememberSaveable value will survive when going into the backstack
  • rememberSaveable value will survive a process death

Cons

  • Complex integration work is required to correctly implement Parcelable or Saver

Resaca's RememberScoped ๐Ÿช„โœจ

RememberScoped function keeps objects 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 retain objects longer than the remember function but shorter than rememberSaveable because there is no serialization involved.

Pros and Cons

Pros

  • Simple API
  • rememberScoped/viewModelScoped value will survive a configuration change
  • rememberScoped/viewModelScoped value will survive when going into the backstack

Cons

  • rememberScoped/viewModelScoped value will NOT survive a process death

Lifecycle explained

How does the lifecycle of the Resaca scoped objects work and some lifecycle illustrated examples:

RememberScoped function keeps objects in memory during the lifecycle of the Composable, even in a few cases where the Composable is disposed of, and then added again.

RememberScoped lifecycle internal implementation details

This project uses internally a ViewModel as a container to store all scoped ViewModels and scoped objects.

What happens when a Composable is disposed?

When 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 object 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:

  • The Composable is not part of the composition anymore after the next frame and the associated object is disposed of. ๐Ÿšฎ
  • The LifecycleOwner of the disposed Composable (i.e. the navigation destination where the Composable lived) is paused (e.g. screen went to background) before 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.
    • This can happen when the application goes through a configuration change and the container Activity is recreated.
    • Also when the Composable is part of a Fragment that has been pushed to the backstack.
    • And also when the Composable is part of a Compose Navigation destination that has been pushed to the backstack.
  • When the LifecycleOwner of the disposed Composable is resumed (e.g. Fragment comes back to foreground), then the disposal of the associated object is scheduled again to happen after the next frame when the Activity is resumed. At this point two things can happen:
    • The Composable becomes part of the composition again and the rememberScoped/viewModelScoped function restores the associated object while also cancelling any pending disposal in the next frame when the Activity is resumed. ๐ŸŽ‰
    • The Composable is not part of the composition anymore after the next frame and then the associated object is disposed of. ๐Ÿšฎ

Note:

  • 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 , which survives recomposition, recreation and even process death.
  • To detect when the requester Composable is not needed anymore (has left composition and the screen for good), the ScopedViewModelContainer also observes the resume/pause Lifecycle events of the owner of this ScopedViewModelContainer (i.e. Activity, Fragment, or Compose Navigation destination)

Lifecycle example

Compose state scope

This diagram shows the lifecycle of three Composables (A, B, and C) with their respective objects scoped with the rememberScoped function. All these Composables 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 represent different lifecycle events, events like Composable being disposed of, Composable screen going into the backstack, Fragment going into the backstack and returning from backstack, or Activity recreated after a configuration change.

The existing alternatives to replicate the lifecycle of the objects in the diagram without using rememberScoped are:

  • Object A lifecycle could only be achieved using the Compose viewModel() or ViewModelProviders factories.
  • Object B lifecycle could only be achieved using the Compose remember() function.
  • Object C lifecycle could not be achieved neither by using ViewModel provider functions nor Compose remember functions.

resaca's People

Contributors

dependabot[bot] avatar niyajali avatar sebaslogen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

resaca's Issues

ConcurrentModificationException v4.1.1

Sorry for submit an issue again instead of PR.

Well, forEach over a non-local MutableCollection probably not a good idea.

Stack trace

Caused by: java.util.ConcurrentModificationException
	at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:1061)
	at java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:1084)
	at com.sebaslogen.resaca.ScopedViewModelContainer.scheduleToDisposeAfterReturningFromBackground(ScopedViewModelContainer.kt:453)
	at com.sebaslogen.resaca.ScopedViewModelContainer.onStateChanged(ScopedViewModelContainer.kt:370)
	at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.jvm.kt:320)
        ......

Related to #129 (comment)

Crash in Preview

Hello, I am new to Android Dev and so I might do something wrong here :)
I thought this code should work in Previews but it will result in the error below.
I am using version 3.2.0

class IamScoped(var str: String = "OK") : ViewModel()

@Composable
fun ScopedVm() {
    val vm: IamScoped = viewModelScoped()
    Text(vm.str)
}
@Preview
@Composable
fun ScopedVmPreview() {
    ScopedVm()
}
_layoutlib_._internal_.kotlin.UninitializedPropertyAccessException: 
lateinit property viewModelProvider has not been initialized ย ย at com.sebaslogen.resaca.ScopedViewModelProvider.getViewModelProvider(ScopedViewModelProvider.kt:33)

Multiplatform support

Hi! Now that the Jetpack lifecycle and view model libraries have multiplatform support, I was wondering if this library could be made multiplatform compatible. In my case, I'm thinking of iOS, and being able to scope a ViewModel to a SwiftUI View.
I know of two solutions so far:

  1. KMP-ObservableViewModel solution where using the @StateViewModel annotation will take care of the lifecycle and ensure the view model is cleared when the View is destroyed

  2. This solution, that I'm currently using myself, and seems to work fine, though there's a bit of boilerplate involved (maybe because I don't know any better):

In the iOS app, create a ViewModelOwner class that implements the ViewModelOwner interface. This requires the shared module to export the Jetpack viewmodel library.

// ViewModelStoreOwner.swift

import shared 

class ViewModelStoreOwner: shared.ViewModelStoreOwner {
    internal let viewModelStore = ViewModelStore()
    
    fileprivate func put<VM: ViewModel>(_ vm: VM) {
        viewModelStore.put(key: VM.self.description(), viewModel: vm)
    }
    
    // This is called when the View containing this object is destroyed
    deinit {
        viewModelStore.clear()
    }
}

// A helper function to create a ViewModel with an owner
func viewModel<VM: ViewModel>(owner: ViewModelStoreOwner, _ factory: () -> VM) -> VM {
    let vm = factory()
    owner.put(vm)
    return vm
}

Then in a View implementation:

// SomeView.swift

import SwiftUI

struct SomeView: View {
    private let owner = ViewModelStoreOwner()
    private let vm: SomeViewModel

    init() {
        vm = viewModel(owner: owner) {
            SomeViewModel()
        }
    }

    var body: some View {
        // etc
    }
}

I'm using the second method because I don't need all the other stuff KMP-ObservableViewModel offers - I prefer to use SKIE to handle Flows.

But I started wondering if it wouldn't be possible to make viewModelScoped() work inside a SwiftUI View.

Best regards,
/Emil

ViewModelScope not separated

Hello!

I have two viewmodels of the same kind, both listening to inputs from a Firebase snapshotlistener (similar to a websocket).

The first viewmodel is running. I navigate to another screen with the same type of viewmodel running (so the first screen with the first viewmodel is not visible, it is in the backstack). I initialise the second viewmodel to listen to that websocket I was talking about, and it seems that I also receive data from the first viewmodel, updating the uistate in my second viewmodel.

Cannot inline bytecode built with JVM target 17

I use this library in my projects, especially when I use ViewModel in ModalBottomSheets and composables that don't have navigation. For one of my projects, I cannot use it because I get a javaTarget error. I have already set the javaTarget to 17, and I have tried invalidating caches and cleaning the project, but none of them worked. I am using Hilt version 2.51.1 and Resaca-Hilt version 4.1.3.

Java Target:
ss2

The error:
ss1

Koin is not supported

When trying to use the viewModelScoped with Koin's koinInject() the compilation throws the error: "Composable calls are not allowed inside the builder".
Please note that koinInject() is a @Composable function and it's usage is not allowed inside the non-composable noinline builder: @DisallowComposableCalls () -> T.

Example:

@Composable
@Preview
private fun DialogContent(
    modifier: Modifier = Modifier,
    viewModel: SubscriptionViewModel = viewModelScoped { koinInject() }, // Error
)

minSdkVersion 21

Hi,

Is there a specific reason this library targets a minimum of sdk 28? Our app doesn't compile because we're targeting minSdkVersion 21.

would it be safe to use
<uses-sdk tools:overrideLibrary="com.sebaslogen.resaca"/>
for this library?

Thanks!

ANR immediately after permission request since version 4.0.0 (Android)

Example code

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
package com.example.myapplication

import android.Manifest
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class MainActivity : ComponentActivity() {

    @com.google.accompanist.permissions.ExperimentalPermissionsApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(60.dp),
                verticalArrangement = Arrangement.spacedBy(20.dp)
            ) {
                val boolState = remember {
                    mutableStateOf(false)
                }

                AnimatedContent(
                    targetState = boolState.value,
                    label = "",
                    transitionSpec = {
                        fadeIn().togetherWith(fadeOut())
                    }
                ) {
                    if (it) {
                        val count = produceState(0) {
                            while (true) {
                                kotlinx.coroutines.delay(1000)
                                value++
                            }
                        }
                        BasicText(
                            text = "ANR??? ${count.value}",
                        )

                        val permissionState =
                            com.google.accompanist.permissions.rememberPermissionState(
                                permission = Manifest.permission.POST_NOTIFICATIONS
                            ) {
                                //noop
                            }
                        LaunchedEffect(Unit) {
                            withContext(Dispatchers.Main) {
                                permissionState.launchPermissionRequest()
                            }
                        }
                    } else {
                        val state = com.sebaslogen.resaca.rememberScoped {
                            mutableStateOf<String?>(null)
                        }
                        BasicText(
                            text = "rememberScoped in using",
                            modifier = Modifier.clickable { state.value = null }
                        )
                    }
                }

                BasicText(
                    text = "permission request dialog",
                    modifier = Modifier
                        .border(1.dp, Color.Black, RoundedCornerShape(50))
                        .clip(RoundedCornerShape(50))
                        .clickable { boolState.value = true }
                        .padding(20.dp)
                )
            }
        }
    }
}

Steps to reproduce

  1. Revoke runtime permission or clear App data first
  2. Click "permission request dialog"
  3. Grant or Deny

Possible fixes

Try not to use that rememberScoped.

Resaca pulls in unstable version of Compose runtime

Affected version: 4.1.3, probably also earlier ones

Steps to reproduce:

  1. Have project with stable version of Compose set up
  2. Add resaca
  3. Run ./gradlew :app:dependencies > out

In output you can see that resaca adds unstable version of Compose runtime:

|    +--- io.github.sebaslogen:resaca:4.1.3
|    |    \--- io.github.sebaslogen:resaca-android:4.1.3
|    |         \--- org.jetbrains.compose.runtime:runtime:1.7.0-alpha03
|    |              +--- androidx.compose.runtime:runtime:1.7.0-beta06 (*)

which replaces stable version added to the project, possibly resulting in some hard-to-track incompatibilities:

+--- project :some-module
|    +--- androidx.compose.ui:ui -> 1.6.8 (*)
|    +--- androidx.compose.runtime:runtime -> 1.7.0-beta06 (*)

ViewModel.onCleared() is not called

Thanks for this library, it might be very helpful. ๐Ÿ‘

Small gotcha: ScopedViewModelContainer.scheduleToDispose() calls viewModelScope.cancel(), but does not call clear() or onCleared() on scoped ViewModels. It think it should, if possible (these methods are not public, unfortunately).

ViewModel is disposed 5-10 secs after scoped composable dispose

ViewModel is disposed ~5-10 secs after scoped composable dispose. In this duration viewModel is alive and the flows inside it is triggered. Why viewmodels are disposed after a curtain amount of time? Shouldn't this be disposed properly with the scoped composable? Thanks in advance.

Ex: Dispose times from logcat
2023-03-20 00:32:28.561 Composable onDispose
2023-03-20 00:32:33.569 ViewModel onCleared

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.