retained

Introduction: A lightweight alternative to Android's ViewModels. The easiest way to retain instances in Activities, Fragments or Composables.
More: Author   ReportBugs   
Tags:

Retained is a lightweight Kotlin Multiplatform library built on top of AndroidX ViewModel. It provides a unified API to retain object instances across Kotlin Multiplatform targets.

  • Remove the need for ViewModel inheritance.
  • Remove the need for ViewModelProvider.Factory.
  • Provide direct access to ViewModel properties: CoroutineScope (viewModelScope), SavedStateHandle, and parameters.
  • Automatic resource management via AutoCloseable.

Download

dependencies {
    // Core Kotlin Multiplatform support
    implementation 'dev.marcellogalhardo:retained-core:{Tag}'

    // `Activity` support
    implementation 'dev.marcellogalhardo:retained-activity:{Tag}'

    // `Fragment` support (includes `Activity` support)
    implementation 'dev.marcellogalhardo:retained-fragment:{Tag}'

    // Navigation support
    implementation 'dev.marcellogalhardo:retained-navigation:{Tag}'    

    // Navigation with Fragment support (includes `Navigation` support)
    implementation 'dev.marcellogalhardo:retained-navigation-fragment:{Tag}'
    
    // Compose support (Android, iOS, Desktop)
    implementation 'dev.marcellogalhardo:retained-compose:{Tag}'
    
    // View support (experimental)
    implementation 'dev.marcellogalhardo:retained-view:{Tag}'
    implementation 'dev.marcellogalhardo:retained-navigation-view:{Tag}'
}

(Replace {Tag} with the latest release version)

Usage

This section shows how to retain instances in activities and fragments. All examples use this class:

class Presenter(var counter: Int = 0)

Use Retained in Activities and Fragments

// Retain an instance in an Activity:
class CounterActivity : AppCompatActivity() {
    private val presenter: Presenter by retain { Presenter() }
}

// Retain an instance in a Fragment:
class CounterFragment : Fragment() {
    private val presenter: Presenter by retain { Presenter() }
}

// Share an instance between Fragments scoped to the Activity
class CounterFragment : Fragment() {
    private val sharedPresenter: Presenter by retainInActivity { Presenter() }
}

// Share an instance between Fragments scoped to the NavGraph
class CounterFragment : Fragment() {
    private val presenter: Presenter by retainInNavGraph(R.navigation.nav_graph) { Presenter() }
}

Use Retained in Compose

@Composable
fun CounterScreen() {
    // Scope to LocalViewModelStoreOwner (default)
    val presenter = retain { Presenter() }

    // Scope to ComponentActivity (Android)
    val activityPresenter = retainInActivity { Presenter() }

    // Scope to a specific ViewModelStoreOwner (e.g. NavBackStackEntry)
    val navBackStackEntry: NavBackStackEntry // Find NavBackStackEntry
    val scopedPresenter = retain(owner = navBackStackEntry) { Presenter() }
}

Advanced Usage

Custom Parameters from Jetpack ViewModel

When you retain an instance, RetainedEntry provides access to host parameters.

@Composable
fun CounterScreen() {
    val presenter = retain { entry: RetainedEntry ->
        Presenter()
    }
    // ...
}

RetainedEntry provides a SavedStateHandle to save and restore state.

class CounterFragment : Fragment() {
    private val presenter: Presenter by retain { entry -> 
        Presenter(counter = entry.savedStateHandle.get<Int>("count") ?: 0)
    }
    // ...
}

RetainedEntry provides a CoroutineScope that matches viewModelScope.

class Presenter(scope: CoroutineScope) { /* ... */ }

class SampleFragment : Fragment() {
    private val presenter: Presenter by retain { entry -> 
        Presenter(scope = entry.coroutineScope)
    }
    // ...
}

For more details, see RetainedEntry.

Automatic Resource Management (AutoCloseable)

If a retained instance implements AutoCloseable, retained automatically closes it when the host ViewModel is cleared (ViewModel.onCleared).

class ResourcePresenter : AutoCloseable {
    override fun close() {
        // Automatically called when the host ViewModel is cleared
    }
}

View Support (Experimental)

You can also retain instances in a View. Use these modules:

dependencies {
    implementation 'dev.marcellogalhardo:retained-view:{Tag}'
    implementation 'dev.marcellogalhardo:retained-navigation-view:{Tag}'
}

The retained-view module provides retain and retainInActivity to scope instances to an Activity or Fragment. The retained-navigation-view module provides retainInNavGraph to scope instances to a NavGraph.

License

Copyright 2019 Marcello Galhardo

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools
AI Daily Digest