Android Kaki

Build beautiful, usable products using required Components for Android.

Compose your individual cross-platform navigation with Decompose | by Isuru Rajapakse


The best way to survive configuration adjustments, survive course of loss of life, and scope ViewModels with out utilizing up Decompose stuff

Decompose model 1.0.0 latest 🙌. I feel it is a good alternative to share my particular use case with Decompose the place I exploit it to share navigation logic between Android and Desktop with out resorting to Decomposition substances pattern.

My app is focusing on Android, iOS, Desktop and Internet. Nonetheless, I simply wish to share navigation between my Android shopper and Desktop as they’re the one ones utilizing Compose Multiplatform. (on iOS, use SwiftUI and on the net, solely React with out enveloping response with simply plain previous vanilla typescript)

Listed here are some issues you’ll run into – when extracting navigate to shared code

  1. Basic navigation abstraction.
  2. Retain and localize your view fashions
  3. Restore view mannequin state after course of dies.

Would not Decompose clear up all these issues? Why not go all in?

That is actually only a interest. I would like make my very own The structure for my software and the inheritance-based part strategy usually are not my forte. Thankfully, Decompose lets you use elements of it and mix your navigation with enterprise logic (if you’ll). Decompose offers you in depth customization choices to tailor the library to your wants. That is the very last thing I did

That is what we can be working in direction of.

sealed class Display screen: Parcelable {
@Parcelize object Checklist : Display screen()
@Parcelize information class Particulars(val element: String) : Display screen()
}
@Composable
enjoyable ListDetailScreen() {
val router: Router = rememberRouter(listOf(Checklist))


RoutedContent(
router = router,
animation = stackAnimation(slide()),
) { display screen ->
when (display screen) {
Checklist -> ListScreen(onSelect { element -> router.push(element) } )
is Particulars -> DetailsScreen(display screen.element)
}
}
}


@Composable
enjoyable ListScreen(onSelect: (element: String) -> Unit) {
val viewModel: ListViewModel =
rememberViewModel { savedState -> ListViewModel(savedState) }


val state: ListState by viewModel.states.collectAsState()


LazyColumn {
objects(state.objects) { merchandise ->
TextButton(onClick = { onSelect(merchandise) } ) {
Textual content(textual content = merchandise)
}
}
}
}


@Composable
enjoyable DetailScreen(element: String) {
val viewModel: ListViewModel =
rememberViewModel(key = element) { DetailsViewModel(element) }


val state: DetailsState by viewModel.states.collectAsState()


Toolbar(title = element)
Textual content(state.descriptions)
}

There are 3 foremost elements to work with

  1. Router (instance: Router) preserve the display screen configuration (e.g.:Display screen) contained in the FILO . stack
  2. ONE @Composable for every configuration and prime stage @Composable to modify between these (ListScreen, DetailsScreen And ListDetailsScreencorresponding)
  3. One ViewModel instance (instance:ListViewModel, DetailsViewModel) persists after configuration adjustments, is in scope of the router (deleted when the person leaves the display screen), and may restore its state after the method dies

This drawback was solved for me with Decompose within the first place. Creator of Decompose, Arkadi Ivanov completely summarizes the whole lot you want, beneath 1̶0̶0̶ 30 strains of code. I extremely suggest studying this primary – earlier than persevering with to learn mine.

There are three essential elements that we use from Decompose to make issues extra lovely for ourselves, they’re

  1. StackNavigator; permit you push or pop completely different configuration
  2. ChildStack& rememberChildStack; the place all of your configurations are saved within the “FILO” stack
  3. @Composable Kids(stack: ChildStack, ..); One @Composableperform that lets you elevate completely different screens for every profile (+ some fancy animations for transitions between screens)

Router API

I prolonged Arkadii’s implementation, by wrapping each StackNavigatorAnd State>in a single Router. This Router The title is impressed by Conductor

import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.StackNavigation
class Router(
personal val navigator: StackNavigation,
val stack: State>,
) : StackNavigation by navigator

Be aware that the configuration (template kind C) have to be a Parcelable to be part of ChildStack. That is the phrase nature (by the identical creator)

I would like one other wrapper perform to supply router for youths by wrapping them @Composable Kids(stack: ChildStack, ..) with

import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Kids
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimation
@OptIn(ExperimentalDecomposeApi::class)
@Composable
enjoyable RoutedContent(
router: Router,
modifier: Modifier = Modifier,
animation: StackAnimation? = null,
content material: @Composable (C) -> Unit,
) {
Kids(
stack = router.stack.worth,
modifier = modifier,
animation = animation,
) { little one ->
CompositionLocalProvider(LocalComponentContext gives little one.occasion) {
content material(little one.configuration)
}
}
}

Now now we have a Router and a strategy to provision a router, then I would like a strategy to hook any new router Composables. that I’ve Not but one other wrapper wrap round Decompose’s rememberChildStack

@Composable
inline enjoyable rememberRouter(
stack: Checklist,
handleBackButton: Boolean = true
): Router {
val navigator: StackNavigation = keep in mind { StackNavigation() }
val childStackState: State> = rememberChildStack(
supply = navigator,
initialStack = { stack },
key = C::class.getFullName(),
handleBackButton = handleBackButton
)


return keep in mind { Router(navigator = navigator, stack = childStackState) }
}

That is lots of wrapping paper 🍬🍬🍬. You might ask, how do all these packages make the location less complicated to make use of?

Utilizing Router API

Let’s check out a easy itemizing particulars display screen. I’ll use the identical instance display screen configurations from the unique publish.

sealed class Display screen: Parcelable {
@Parcelize object Checklist : Display screen()
@Parcelize information class Particulars(val element: String) : Display screen()
}

A easy itemizing particulars display screen now appears like this

@Composable
enjoyable ListDetailScreen() {
val router: Router = rememberRouter(listOf(Checklist))
RoutedContent(
router = router,
animation = stackAnimation(slide()),
) { display screen ->
when (display screen) {
is Checklist -> ListScreen(onSelect { element -> router.push(element) } )
is Particulars -> DetailsScreen(display screen.element)
}
}
}

Neat stuff 👍 Transfer on to the following drawback

Your Android exercise goes by a life cycle (and if you happen to used fragments, much more life cycles!). Typically you wish to retain an occasion (e.g. a view kind) by a number of lifecycle levels with out dropping arduous to search out standing. If the instances themselves can’t match a Bundleor like a Parcelable – you possibly can’t get away with simply utilizing rememberSavableand that is normally the place you see most individuals utilizingandroidx.lifecycle.ViewModel and let both Exercise, Fragment or Utility maintain into these situations whereas the lifecycle does its job.

API ViewModels

Decomposition of use nature (from the identical creator) create a cross-platform abstraction to realize this and that is known as InstanceKeeper. To ship our variations, all we have to do is deploy InstanceKeeper.Occasion on the objects that you just wish to be saved.

import com.arkivanov.essenty.instancekeeper.InstanceKeeper
open class ViewModel() : InstanceKeeper.Occasion {
override enjoyable onDestroy() { // Clear up }
}

On condition that we’re not utilizing Decompose as supposed, we have to wire this up ourselves.

@Composable
inline enjoyable rememberViewModel(
key: Any = T::class,
crossinline block: @DisallowComposableCalls () -> T
): T {
val part: ComponentContext = LocalComponentContext.present
val packageName: String = T::class.getFullName()
val viewModelKey = "$packageName.view-model"
return keep in mind(key) {
part.instanceKeeper.getOrCreate(viewModelKey) { block() }
}
}

decompose ComponentContext do all of the heavy lifting for us right here. It manages these variations by defining their scope for every little one part and eradicating these variations when they’re not on the stack.

Utilizing the ViewModels API

Declare the ViewModel of the display screen by extending the bottom ViewModel

class ListViewModel() : ViewModel() 

Then use it with rememberViewModel

@Composable
enjoyable ListScreen() {
val viewModel: ListViewModel = rememberViewModel { ListViewModel() }
}

In the event you want reissued new case when the parameter adjustments you possibly can move in a key

@Composable
enjoyable DetailScreen(element: String) {
val viewModel = rememberViewModel(key = element) { ListViewModel(element) }
}

Fairly neat stuff 👍 Transfer on to the following drawback

You might die however that does not imply your opinion standing can be ☠️! Android can kill your course of if you happen to really feel prefer it – when nobody is watching. So you must cope with that every so often. That is normally the place you see most individuals utilizing androidx.lifecycle.SavedStateHandle to avoid wasting state earlier than loss of life and restore state after course of restart. We’d like an identical abstraction on the cross-platform aspect to make this occur.

Saved State API

To begin with, we have to create a wrapper to wrap our display screen state. i’ll name this SavedState. Be aware that we will retain the state throughout loss of life just one if your standing is Parcelable.

import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.essenty.parcelable.Parcelize
@Parcelize
information class SavedState(val worth: Parcelable): Parcelable

We additionally want a deal with to carry a single occasion of this wrapped state to persist upon configuration adjustments. I’ll name this deal with SavedStateHandle

@Parcelize
information class SavedState(val worth: Parcelable): Parcelable
class SavedStateHandle(default: SavedState?): InstanceKeeper.Occasion {
personal var savedState: SavedState? = default
val worth: Parcelable? get() = savedState
enjoyable get(): T? = savedState?.worth as? T?
enjoyable set(worth: Parcelable) { this.savedState = SavedState(worth) }
override enjoyable onDestroy() { savedState = null }
}

nature ofStateKeeper is the cross-platform abstraction of androidx.lifecycle.SavedStateHandle and that is accessible to us by ComponentContext. Incorporating that into our little API requires just some extra modifications. We have to modify the rememberViewModel hook which now we have executed earlier than to move on this SavedStateHandle

@Composable
inline enjoyable rememberViewModel(
key: Any = T::class,
crossinline block: @DisallowComposableCalls (savedState: SavedStateHandle) -> T
): T {
val part: ComponentContext = LocalComponentContext.present
val stateKeeper: StateKeeper = part.stateKeeper
val instanceKeeper: InstanceKeeper = part.instanceKeeper
val packageName: String = T::class.getFullName()
val viewModelKey = "$packageName.viewModel"
val stateKey = "$packageName.savedState"


val (viewModel, savedState) = keep in mind(key) {
val savedState: SavedStateHandle = instanceKeeper
.getOrCreate(stateKey) { SavedStateHandle(stateKeeper.devour(stateKey, SavedState::class)) }
val viewModel: T = instanceKeeper.getOrCreate(viewModelKey) { block(savedState) }
viewModel to savedState
}


LaunchedEffect(Unit) {
if (!stateKeeper.isRegistered(stateKey))
stateKeeper.register(stateKey) { savedState.worth }
}


return viewModel
}

decompose And nature StateKeeper do all of the heavy lifting for us right here. It calls the StateKeeper to get the state saved in SavedStateHandleand provides again to SavedStateHandle (we’ll get this from the state controller to revive the default state in ViewModel Subsequent).

Perfection! 👌

Utilizing the SavingState API

If you need your state to persist after course of loss of life ️ simply use StateKeeper (or depart it if you do not need that)

@Composable
enjoyable ListScreen() {
val viewModel: ListViewModel =
rememberViewModel { savedState -> ListViewModel(savedState) }
}

Then we have to use it after we instantiate our view mannequin, which we will do by passing it by the constructor as a parameter

class ListViewModel(savedState: SavedStateHandle) : ViewModel() {
personal val defaultState: ListState = savedState.get() ?: ListState()
val states: StateFlow by lazy {
moleculeFlow() { .. } // or nonetheless you wish to handle your state
.onEach { state -> savedState.set(state) }
.stateIn(this, Lazily, defaultState)
}
}

Fairly neat stuff 👍

That is how I exploit Decompose in my software 😎. I am positive there are issues that I have never considered that would break my plans — however this appears to work and solves all the standard issues you’d encounter when attempting to place logic in issues. oriented to the shared class in Compose Multiplatform functions.

Try pattern apps right here

Pattern android and desktop apps
John Wick: Chapter 4 (FREE) FULLMOVIE The Super Mario Bros Movie avatar 2 Where To Watch Creed 3 Free At Home Knock at the Cabin (2023) FullMovie Where To Watch Ant-Man 3 and the Wasp: Quantumania Cocaine Bear 2023 (FullMovie) Scream 6 Full Movie
Updated: April 21, 2023 — 10:16 pm

Leave a Reply

Your email address will not be published. Required fields are marked *

androidkaki.com © 2023 Android kaki