All Projects → sebaslogen → resaca

sebaslogen / resaca

Licence: MIT license
The right scope for View Models in Android Compose

Programming Languages

kotlin
9241 projects

Projects that are alternatives of or similar to resaca

arkitekt
Arkitekt is a set of architectural tools based on Android Architecture Components, which gives you a solid base to implement the concise, testable and solid application.
Stars: ✭ 114 (+8.57%)
Mutual labels:  compose, architecture-components, jetpack-compose
KTAndroidArchitecture
A Kotlin android architecture with Google Architecture Components
Stars: ✭ 33 (-68.57%)
Mutual labels:  android-architecture, viewmodel, architecture-components
News
A sample News 🗞 app built using Modern Android Development [Architecture Components, Coroutines, Retrofit, Room, Kotlin, Dagger]
Stars: ✭ 774 (+637.14%)
Mutual labels:  android-architecture, viewmodel, architecture-components
Delish
Delish, a Food Recipes App in Jetpack Compose and Hilt based on modern Android tech-stacks and MVI clean architecture.
Stars: ✭ 356 (+239.05%)
Mutual labels:  viewmodel, compose, jetpack-compose
Coolweather
Weather App that uses Android best practices. Android Jetpack, clean architecture. Written in Kotlin
Stars: ✭ 154 (+46.67%)
Mutual labels:  android-architecture, viewmodel, architecture-components
kmm-production-sample
This is an open-source, mobile, cross-platform application built with Kotlin Multiplatform Mobile. It's a simple RSS reader, and you can download it from the App Store and Google Play. It's been designed to demonstrate how KMM can be used in real production projects.
Stars: ✭ 1,476 (+1305.71%)
Mutual labels:  compose, jetpack-compose
TeamManagerApp
A sample app structure using the MVVM architecture LiveData, RxJava, ViewModel, Room and the Navigation Arch Components.
Stars: ✭ 36 (-65.71%)
Mutual labels:  viewmodel, architecture-components
android-compose-mvvm-foodies
Android sample app following best practices: Kotlin, Compose, Coroutines and Flow, Hilt, JetPack Navigation, ViewModel, MVVM, Retrofit, Coil
Stars: ✭ 374 (+256.19%)
Mutual labels:  compose, hilt-dependency-injection
android-kick-start-modular
Android Kick Start Project Template Framework FrogoBox || Admob, MVVM Archictecture || Clean Architecture Modularization
Stars: ✭ 16 (-84.76%)
Mutual labels:  android-architecture, architecture-components
ComposeBird
Flappy Bird game
Stars: ✭ 193 (+83.81%)
Mutual labels:  compose, jetpack-compose
Dagger-Hilt-MVVM
Sample app that demonstrates the usage of Dagger Hilt with Kotlin & MVVM
Stars: ✭ 62 (-40.95%)
Mutual labels:  viewmodel, hilt-dependency-injection
GitMessengerBot-Android
타입스크립트, V8 엔진의 자바스크립트, 파이썬 그리고 Git을 지원하는 최첨단 메신저 봇!
Stars: ✭ 51 (-51.43%)
Mutual labels:  architecture-components, jetpack-compose
Restaurants
Restaurants sample app built with the new architecture components (LiveData, Room, ViewModel) and Dagger 2
Stars: ✭ 47 (-55.24%)
Mutual labels:  viewmodel, architecture-components
Compose-Settings
Android #JetpackCompose Settings library
Stars: ✭ 188 (+79.05%)
Mutual labels:  compose, jetpack-compose
SuperheroLexicon
Simple superhero lexicon app to demonstrate Jetpack Compose.
Stars: ✭ 22 (-79.05%)
Mutual labels:  compose, jetpack-compose
compose-backstack
Simple composable for rendering transitions between backstacks.
Stars: ✭ 411 (+291.43%)
Mutual labels:  compose, jetpack-compose
Compose-ToDo
A fully functional Android TODO app built entirely with Kotlin and Jetpack Compose
Stars: ✭ 130 (+23.81%)
Mutual labels:  viewmodel, jetpack-compose
DaggerViewModel
An integration Module for injecting Google Architecture Components' ViewModel into Dagger2-injected Android components.
Stars: ✭ 67 (-36.19%)
Mutual labels:  viewmodel, architecture-components
Einsen
🎯 Einsen is a prioritization app that uses Eisenhower matrix technique as workflow to prioritize a list of tasks & built to Demonstrate use of Jetpack Compose with Modern Android Architecture Components & MVVM Architecture.
Stars: ✭ 821 (+681.9%)
Mutual labels:  android-architecture, jetpack-compose
Eiffel
Redux-inspired Android architecture library leveraging Architecture Components and Kotlin Coroutines
Stars: ✭ 203 (+93.33%)
Mutual labels:  android-architecture, viewmodel

Build Status Release API 21+ GitHub license

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

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

@Composable
fun DemoScopedObject() {
    val myRepository: MyRepository = rememberScoped { MyRepository() }
    DemoComposable(inputObject = myRepository)
}

@Composable
fun DemoScopedViewModel() {
    val myScopedVM: MyViewModel = viewModelScoped()
    DemoComposable(inputObject = myScopedVM)
}

@Composable
fun DemoScopedViewModelWithDependencies() {
    val myScopedVM: MyViewModelWithDependencies = viewModelScoped { MyViewModelWithDependencies(myDependency) }
    DemoComposable(inputObject = myScopedVM)
}

@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)
}

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 by this library.

💡 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 its clean-up method won't be called, that's the reason to use viewModelScoped instead.

Sample use cases

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

  • 📃📄 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.
  • ❤️ 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.

Demo app

Demo app documentation can be found here.

Resaca-demo

Before After backstack navigation & configuration change
Before After

Installation

Add the Jitpack repo and include the library (less than 5Kb):

   allprojects {
       repositories {
           [..]
           maven { url "https://jitpack.io" }
       }
   }
   dependencies {
       // The latest version of the lib is available in the badget at the top, replace X.X.X with that version
       implementation 'com.github.sebaslogen.resaca:resaca:X.X.X'
   }

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 (Dagger) support is available in a small extension of this library: resaca-hilt.

Documentation and installation instructions are available here.

Koin 🪙

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

Instead of using the getViewModel function from Koin, you have to use the standard way of getting a dependency from Koin.

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

General considerations for State Hoisting

Here are a few suggestions of how to provide objects in combination with this library in a Compose screen:

  • When using the Lazy* family of Composables it is recommended that you use rememberScoped/viewModelScoped outside the scope of Composables created by Lazy constructors (e.g. LazyColumn) because there is a chance that a lazy initialized Composable will be disposed of when it is not visible anymore (e.g. scrolled away) and that will also dispose of the rememberScoped/viewModelScoped object (after a few seconds), this might not be the intended behavior. For more info see Compose's 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 tree using Compose's State Hoisting.

Why not use 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

  • 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

  • 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

Lifecycle

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

  • 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

RememberScoped lifecycle internal implementation details

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

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 a small delay (currently 5 seconds). During the span of time of this delay, a few things can happen:

  • The Composable is not part of the composition anymore after the delay 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 delay finishes. 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 a small delay. 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 canceling any pending delayed disposal. 🎉
    • The Composable is not part of the composition anymore after the delay and the associated object is disposed of. 🚮

Notes:

  • 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 simply be achieved neither by using ViewModel provider functions nor Compose remember functions.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].