Code Monkey home page Code Monkey logo

essenty's Introduction

Maven Central License Twitter URL

Essenty

The most essential libraries for Kotlin Multiplatform development.

Supported targets:

  • android
  • jvm
  • js
  • wasmJs
  • ios
  • watchos
  • tvos
  • macos
  • linuxX64

Lifecycle

When writing Kotlin Multiplatform (common) code we often need to handle lifecycle events of a screen. For example, to stop background operations when the screen is destroyed, or to reload some data when the screen is activated. Essenty provides the Lifecycle API to help with lifecycle handling in the common code. It is very similar to Android Activity lifecycle.

Setup

Groovy:

// Add the dependency, typically under the commonMain source set
implementation "com.arkivanov.essenty:lifecycle:<essenty_version>"

Kotlin:

// Add the dependency, typically under the commonMain source set
implementation("com.arkivanov.essenty:lifecycle:<essenty_version>")

Lifecycle state transitions

Content

The main Lifecycle interface provides ability to observe the lifecycle state changes. There are also handy extension functions for convenience.

The LifecycleRegistry interface extends both the Lifecycle and the Lifecycle.Callbacks at the same time. It can be used to manually control the lifecycle, for example in tests. You can also find some useful extension functions.

The LifecycleOwner just holds the Lifecyle. It may be implemented by an arbitrary class, to provide convenient API.

Android extensions

From Android, the Lifecycle can be obtained by using special functions, can be found here.

iOS and tvOS extensions

There is ApplicationLifecycle awailable for ios and tvos targets. It follows the UIApplication lifecycle notifications.

⚠️ Since this implementation subscribes to UIApplication global lifecycle events, the instance and all its registered callbacks (and whatever they capture) will stay in memory until the application is destroyed or until ApplicationLifecycle#destroy method is called. It's ok to use it in a global scope like UIApplicationDelegate, but it may cause memory leaks when used in a narrower scope like UIViewController if it gets destroyed earlier. Use the destroy method to destroy the lifecycle manually and prevent memory leaks.

Reaktive extensions

There are some useful Lifecycle extensions for Reaktive.

  • Automatic management of Disposable and DisposableScope by Lifecycle, can be found here.

Coroutines extensions

There are some useful Lifecycle extensions for Coroutines.

  • Automatic management of CoroutineScope by Lifecycle, can be found here
  • Flow.withLifecycle(Lifecycle): Flow - can be found here.
  • Lifecycle.repeatOnLifecycle(block) - can be found here.

Usage example

Observing the Lifecyle

The lifecycle can be observed using its subscribe/unsubscribe methods:

import com.arkivanov.essenty.lifecycle.Lifecycle

class SomeLogic(lifecycle: Lifecycle) {
    init {
        lifecycle.subscribe(
            object : Lifecycle.Callbacks {
                override fun onCreate() {
                    // Handle lifecycle created
                }

                // onStart, onResume, onPause, onStop are also available

                override fun onDestroy() {
                    // Handle lifecycle destroyed
                }
            }
        )
    }
}

Or using the extension functions:

import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.doOnCreate
import com.arkivanov.essenty.lifecycle.doOnDestroy
import com.arkivanov.essenty.lifecycle.subscribe

class SomeLogic(lifecycle: Lifecycle) {
    init {
        lifecycle.subscribe(
            onCreate = { /* Handle lifecycle created */ },
            // onStart, onResume, onPause, onStop are also available
            onDestroy = { /* Handle lifecycle destroyed */ }
        )

        lifecycle.doOnCreate {
            // Handle lifecycle created
        }

        // doOnStart, doOnResume, doOnPause, doOnStop are also available

        lifecycle.doOnDestroy {
            // Handle lifecycle destroyed
        }
    }
}

Using the LifecycleRegistry manually

A default implementation of the LifecycleRegisty interface can be instantiated using the corresponding builder function:

import com.arkivanov.essenty.lifecycle.LifecycleRegistry
import com.arkivanov.essenty.lifecycle.resume
import com.arkivanov.essenty.lifecycle.destroy

val lifecycleRegistry = LifecycleRegistry()
val someLogic = SomeLogic(lifecycleRegistry)

lifecycleRegistry.resume()

// At some point later
lifecycleRegistry.destroy()

StateKeeper

When writing common code targeting Android, it might be required to preserve some data over Android configuration changes or process death. For this purpose, Essenty provides the StateKeeper API, which is inspired by the AndroidX SavedStateHandle.

Setup

Groovy:

// Add the dependency, typically under the commonMain source set
implementation "com.arkivanov.essenty:state-keeper:<essenty_version>"

Kotlin:

// Add the dependency, typically under the commonMain source set
implementation("com.arkivanov.essenty:state-keeper:<essenty_version>")

Content

The main StateKeeper interface provides ability to register/unregister state suppliers, and also to consume any previously saved state. You can also find some handy extension functions.

The StateKeeperDispatcher interface extends StateKeeper and allows state saving, by calling all registered state providers.

The StateKeeperOwner interface is just a holder of StateKeeper. It may be implemented by an arbitrary class, to provide convenient API.

Android extensions

From Android side, StateKeeper can be obtained by using special functions, can be found here.

There are also some handy extension functions for serializing/deserializing KSerializable objects to/from Bundle:

  • fun <T : Any> Bundle.putSerializable(key: String?, value: T?, strategy: SerializationStrategy<T>)
  • fun <T : Any> Bundle.getSerializable(key: String?, strategy: DeserializationStrategy<T>): T?
  • fun Bundle.putSerializableContainer(key: String?, value: SerializableContainer?)
  • fun Bundle.getSerializableContainer(key: String?): SerializableContainer?

Usage example

Using StateKeeper

⚠️ Make sure you setup kotlinx-serialization properly.

import com.arkivanov.essenty.parcelable.Parcelable
import kotlinx.serialization.Serializable

class SomeLogic(stateKeeper: StateKeeper) {
    // Use the saved State if any, otherwise create a new State
    private var state: State = stateKeeper.consume(key = "SAVED_STATE", strategy = State.serializer()) ?: State()

    init {
        // Register the State supplier
        stateKeeper.register(key = "SAVED_STATE", strategy = State.serializer()) { state }
    }

    @Serializable
    private class State(
        val someValue: Int = 0
    ) : Parcelable
}
Polymorphic serialization (experimental)

Sometimes it might be necessary to serialize an interface or an abstract class that you don't own but have implemented. For this purpose Essenty provides polymorphicSerializer function that can be used to create custom polymorphic serializers for unowned base types.

For example a third-party library may have the following interface.

interface Filter {
    // Omitted code
}

Then we can have multiple implementations of Filter.

@Serializable
class TextFilter(val text: String) : Filter { /* Omitted code */ }

@Serializable
class RatingFilter(val stars: Int) : Filter { /* Omitted code */ }

Now we can create a polymorphic serializer for Filter as follows. It can be used to save and restore Filter directly via StateKeeper, or to have Filter as part of another Serializable class.

object FilterSerializer : KSerializer<Filter> by polymorphicSerializer(
    SerializersModule {
        polymorphic(Filter::class) {
            subclass(TextFilter::class, TextFilter.serializer())
            subclass(RatingFilter::class, RatingFilter.serializer())
        }
    }
)

Using the StateKeeperDispatcher manually

On Android, the StateKeeper obtained via one of the extensions described above automatically saves and restores the state. On other platforms (if needed) the state can be saved and restored manually. A default implementation of StateKeeperDisptacher interface can be instantiated using the corresponding builder function. The state can be encoded as a JSON string and saved using the corresponding platform-specific API.

import com.arkivanov.essenty.statekeeper.SerializableContainer
import com.arkivanov.essenty.statekeeper.StateKeeper
import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher

val stateKeeperDispatcher = StateKeeperDispatcher(/*Previously saved state, or null*/)
val someLogic = SomeLogic(stateKeeperDispatcher)

// At some point later when it's time to save the state
val savedState: SerializableContainer = stateKeeperDispatcher.save()

// The returned SerializableContainer can now be saved using the corresponding platform-specific API

InstanceKeeper

When writing common code targetting Android, it might be required to retain objects over Android configuration changes. This use case is covered by the InstanceKeeper API, which is similar to the AndroidX ViewModel.

Setup

Groovy:

// Add the dependency, typically under the commonMain source set
implementation "com.arkivanov.essenty:instance-keeper:<essenty_version>"

Kotlin:

// Add the dependency, typically under the commonMain source set
implementation("com.arkivanov.essenty:instance-keeper:<essenty_version>")

Content

The main InstanceKeeper interface is responsible for storing object instances, represented by the [InstanceKeeper.Instance] interface. Instances of the InstanceKeeper.Instance interface survive Android Configuration changes, the InstanceKeeper.Instance.onDestroy() method is called when InstanceKeeper goes out of scope (e.g. the screen is finished). You can also find some handy extension functions.

The InstanceKeeperDispatcher interface extends InstanceKeeper and adds ability to destroy all registered instances.

The InstanceKeeperOwner interface is just a holder of InstanceKeeper. It may be implemented by an arbitrary class, to provide convenient API.

Android extensions

From Android side, InstanceKeeper can be obtained by using special functions, can be found here.

Usage example

Using the InstanceKeeper

import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.getOrCreate

class SomeLogic(instanceKeeper: InstanceKeeper) {
    // Get the existing instance or create a new one
    private val thing: RetainedThing = instanceKeeper.getOrCreate { RetainedThing() }
}

/*
 * Survives Android configuration changes.
 * ⚠️ Pay attention to not leak any dependencies.
 */
class RetainedThing : InstanceKeeper.Instance {
    override fun onDestroy() {
        // Called when the screen is finished
    }
}

Using the InstanceKeeperDispatcher manually

A default implementation of the InstanceKeeperDispatcher interface can be instantiated using the corresponding builder function:

import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher

// Create a new instance of InstanceKeeperDispatcher, or reuse an existing one
val instanceKeeperDispatcher = InstanceKeeperDispatcher()
val someLogic = SomeLogic(instanceKeeperDispatcher)

// At some point later
instanceKeeperDispatcher.destroy()

BackHandler

The BackHandler API provides ability to handle back button clicks (e.g. the Android device's back button), in common code. This API is similar to AndroidX OnBackPressedDispatcher.

Setup

Groovy:

// Add the dependency, typically under the commonMain source set
implementation "com.arkivanov.essenty:back-handler:<essenty_version>"

Kotlin:

// Add the dependency, typically under the commonMain source set
implementation("com.arkivanov.essenty:back-handler:<essenty_version>")

Content

The BackHandler interface provides ability to register and unregister back button callbacks. When the device's back button is pressed, all registered callbacks are called in reverse order, the first enabled callback is called and the iteration finishes.

Starting from v1.2.x, when the device's back button is pressed, all registered callbacks are sorted in ascending order first by priority and then by index, the last enabled callback is called.

BackCallback allows handling back events, including predictive back gestures.

The BackDispatcher interface extends BackHandler and is responsible for triggering the registered callbacks. The BackDispatcher.back() method triggers all registered callbacks in reverse order, and returns true if an enabled callback was called, and false if no enabled callback was found.

Android extensions

From Android side, BackHandler can be obtained by using special functions, can be found here.

Predictive Back Gesture

Both BackHandler and BackDispatcher bring the new Android Predictive Back Gesture to Kotlin Multiplatform.

Predictive Back Gesture on Android

On Android, the predictive back gesture only works starting with Android T. On Android T, it works only between Activities, if enabled in the system settings. Starting with Android U, the predictive back gesture also works between application's screens inside an Activity. In the latter case, back gesture events can be handled using BackCallback.

Predictive Back Gesture on other platforms

On all other platforms, predictive back gestures can be dispatched manually via BackDispatcher. This can be done e.g. by adding an overlay on top of the UI and handling touch events manually.

Usage example

Using the BackHandler

import com.arkivanov.essenty.backhandler.BackHandler

class SomeLogic(backHandler: BackHandler) {
    private val callback = BackCallback {
        // Called when the back button is pressed
    }

    init {
        backHandler.register(callback)

        // Disable the callback when needed
        callback.isEnabled = false
    }
}

Using the BackDispatcher manually

A default implementation of the BackDispatcher interface can be instantiated using the corresponding builder function:

import com.arkivanov.essenty.backhandler.BackDispatcher

val backDispatcher = BackDispatcher()
val someLogic = SomeLogic(backDispatcher)

if (!backDispatcher.back()) {
    // The back pressed event was not handled
}

Author

Twitter: @arkann1985

If you like this project you can always Buy Me A Coffee ;-)

essenty's People

Contributors

arkivanov avatar sebaslogen avatar vladislavsumin avatar xxfast 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  avatar  avatar  avatar

essenty's Issues

Utility function for StateKeeper

Good day!

Extension function InstanceKeeper.getOrCreate(...) provides convenient way to get value from InstanceKeeper or create it otherwise.

It would be very useful to add similar function for StateKeeper to allow receive saved value from StateKeeper (if any) and at the same time register supplier that will provide value to save.

It will have the following benefits:

  • key will be specified once only, less copy-paste typos
  • it will ensure that supplier will provide value of the same type that is consumed from StateKeeper for the same key
  • it will ensure that supplier is provided, so it will be harder to forget to register it

The function may be implemented as the following (name could be better):

inline fun <reified T : Parcelable> StateKeeper.registerAndConsume(key: String, noinline supplier: () -> T): T? {
    register(key, supplier)
    return consume(key)
}

And usage looks like:

class SomeValue : Parcelable

val someValue: SomeValue? = stateKeeper.registerAndConsume("some_key") { SomeValue() }

I can create PR, if needed.

[StateKeeper] Add another ```register``` method with onError parameter

The current statekeeper implementation is pretty nice, espacially when using Decompose, however I think this feature should be supported.

Currently the StateKeeper has this method only

fun <T : Parcelable> register(key: String, supplier: () -> T?)

An additional (optional) onError method would be handy

fun <T : Parcelable> register(key: String, supplier: () -> T?, onError: (T?, Throwable) -> Unit)

To handle the exception and saving yourself.

E.g. some (Android) devices could throw a TooLargeException, so we could save it in a file for example as fallback

K2 compiler support

With K2 compiler coming it would be good to make this library work with it too. Right now I'm getting this error:

Class SomeParcelableClass is not abstract and does not implement abstract member describeContents

K2 compiler enabled via kotlin.experimental.tryK2=true in gradle.properties

Add Parceler and WriteWith

This should be placed in the parcelable module.

See the discussion #47.

Here is a tentative implementation from the discussion:

// Parceler.kt in commonMain

expect interface Parceler<T>
// WriteWith.kt in commonMain

@OptIn(ExperimentalMultiplatform::class)
@OptionalExpectation
@Target(AnnotationTarget.TYPE)
expect annotation class WriteWith<P : Parceler<*>>()
// Parceler.kt in androidMain

actual typealias Parceler<T> = kotlinx.parcelize.Parceler<T>
// WriteWith.kt in androidMain

actual typealias WriteWith<P> = kotlinx.parcelize.WriteWith<P>
// Parceler.kt in nonAndroidMain

actual interface Parceler<T>

Unable to get LifeCycle event for the KMP

In Common code of Compose Multiplatform,

I have the below code.

 LaunchedEffect("LifeCycle"){
        val callbacks: Lifecycle.Callbacks =
            object : Lifecycle.Callbacks {
                override fun onCreate() {

                }

                override fun onStart() {

                }

                override fun onResume() {

                }

                override fun onPause() {
                    Logger.i (messageString = "Home Screen LaunchedEffect onPause", tag = "Home")
                }

                override fun onStop() {
                    Logger.i (messageString = "Home Screen LaunchedEffect onStop", tag = "Home")
                }

                override fun onDestroy() {

                }
            }

        viewModel.lifeCycleCallBack = callbacks
        mAppLifeCycleOwner.lifecycle.subscribe(callbacks)
        Logger.i (messageString = "Home Screen LaunchedEffect lifeCycleCallBack register", tag = "Home")
    } 

I does not trigger, I am testing it with Android for now, May be I am missing something...

I am also try below as well

private var mlifecycle :LifecycleRegistry = LifecycleRegistry(initialState = Lifecycle.State.INITIALIZED)

In my Android Native activity.

 override fun onStop() {
         super.onStop()
         mlifecycle.onStop()
         Logger.i (messageString = "onStop", tag = "MainActivity")
     }

StateKeeper may crash on Android with ZipException: Entry is not named

Android 6 (23)

Caused by java.util.zip.ZipException: Entry is not named
       at java.util.zip.ZipInputStream.getNextEntry([ZipInputStream.java:268](https://zipinputstream.java:268/))
       at com.arkivanov.essenty.statekeeper.Utils_javaKt.deserialize(Utils.java.kt:1)
       at com.arkivanov.essenty.statekeeper.AndroidExtKt.StateKeeper(AndroidExt.kt:1)
       at com.arkivanov.essenty.statekeeper.AndroidExtKt.stateKeeper(AndroidExt.kt:1)
       at com.arkivanov.decompose.DefaultComponentContextBuilderKt.defaultComponentContext(DefaultComponentContextBuilder.kt:1)
       at com.arkivanov.decompose.DefaultComponentContextBuilderKt.defaultComponentContext(DefaultComponentContextBuilder.kt:1)
       at com.arkivanov.decompose.DefaultComponentContextBuilderKt.defaultComponentContext$default(DefaultComponentContextBuilder.kt:1)

Upgrade to 0.4.0 removes support for js target

After upgrading to 0.4.0 from 0.3.1 compilation fails for target js with these kind of errors:

      > No matching variant of com.arkivanov.essenty:lifecycle:0.4.0 was found. The consumer was configured to find a usage of 'kotlin-runtime' of a library, preferably optimized for non-jvm, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js', attribute 'org.jetbrains.kotlin.js.compiler' with value 'legacy' but:
          - Variant 'commonMainMetadataElements' capability com.arkivanov.essenty:lifecycle:0.4.0 declares a library:
              - Incompatible because this component declares a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'common' and the consumer needed a usage of 'kotlin-runtime' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js'
              - Other compatible attributes:
                  - Doesn't say anything about its target Java environment (preferred optimized for non-jvm)
                  - Doesn't say anything about org.jetbrains.kotlin.js.compiler (required 'legacy')
          - Variant 'debugApiElements-published' capability com.arkivanov.essenty:lifecycle:0.4.0 declares a library:
              - Incompatible because this component declares an API of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a usage of 'kotlin-runtime' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js'
              - Other compatible attributes:
                  - Doesn't say anything about its target Java environment (preferred optimized for non-jvm)
                  - Doesn't say anything about org.jetbrains.kotlin.js.compiler (required 'legacy')
          - Variant 'debugRuntimeElements-published' capability com.arkivanov.essenty:lifecycle:0.4.0 declares a runtime of a library:
              - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js'
              - Other compatible attributes:
                  - Doesn't say anything about its target Java environment (preferred optimized for non-jvm)
                  - Doesn't say anything about org.jetbrains.kotlin.js.compiler (required 'legacy')
          - Variant 'iosArm64ApiElements-published' capability com.arkivanov.essenty:lifecycle:0.4.0 declares a library:
              - Incompatible because this component declares a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' and the consumer needed a usage of 'kotlin-runtime' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js'
              - Other compatible attributes:
                  - Doesn't say anything about its target Java environment (preferred optimized for non-jvm)
                  - Doesn't say anything about org.jetbrains.kotlin.js.compiler (required 'legacy')
          - Variant 'iosArm64MetadataElements-published' capability com.arkivanov.essenty:lifecycle:0.4.0 declares a library:
              - Incompatible because this component declares a usage of 'kotlin-metadata' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' and the consumer needed a usage of 'kotlin-runtime' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js'
              - Other compatible attributes:
                  - Doesn't say anything about its target Java environment (preferred optimized for non-jvm)
                  - Doesn't say anything about org.jetbrains.kotlin.js.compiler (required 'legacy')
          - Variant 'iosSimulatorArm64ApiElements-published' capability com.arkivanov.essenty:lifecycle:0.4.0 declares a library:
and so on

When I remove js from my app (leaving jvm, Android and iOS) it compiles without issues.

Support optional values in StateKeeper's supplier

StateKeeper makes a bit redundantly strong contract for supplier imo, requiring non-optional Parcelable from supplier (here).

Simply put, we have

interface StateKeeper {
    ...
    fun <T : Parcelable> register(key: String, supplier: () -> T)
    ...
}

And I want

interface StateKeeper {
    ...
    fun <T : Parcelable?> register(key: String, supplier: () -> T)
    ...
}

Note the T : Parcelable? part.

Maybe I'm not seeing the reason behind the non-nullable contract, please correct me if I'm wrong.

Issue with Parcelable

file:///Users/shubham.garg/Downloads/Android-SDK-Sample-App-main/payu-sdk/src/commonMain/kotlin/com/payu/india/Model/Bnpl.kt:9:6 Class 'Bnpl' is not abstract and does not implement abstract member public abstract fun describeContents(): Int defined in android.os.Parcelable

Custom Parcelers

I have an abstract class that I want to write a custom Parceler for per https://developer.android.com/kotlin/parcelize

e.g.

@Parcelize
data class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
    private companion object : Parceler<User> {
        override fun User.write(parcel: Parcel, flags: Int) {
            // Custom write implementation
        }

        override fun create(parcel: Parcel): User {
            // Custom read implementation
        }
    }
}

Cheers.

Consider moving `decompose.extensions.compose.jetbrains.lifecycle.LifecycleController` to Essenty

For desktop apps that don't need to use Decompose, but want to use Essenty for lifecycle handling, having LifecycleController in the Decompose introduces unnecessary overhead of depending on the whole library just for the lifecycle functionality. Consider moving functionality that allows to provide Essenty lifecycle for desktop, compose and other platforms to a separate module in Essenty instead of Decompose.

Unresolved reference: Parcelize

The latest version (0.7.0) shows the Parcelize annotation red with this error message: "Unresolved reference: Parcelize" in IntelliJ Ultimate 2022.3.1

This is NOT urgent as the project builds fine, just a little annoying.

Version 0.6.0 doesn't have this problem.

Comply with the new predictive back gesture on Android 13

The current API of BackPressedHandler and BackPressedDispatcher is not compatible with the new predictive back gesture on Android 13. When this mode is enabled and there is a callback registered in BackPressedHandler, the predictive back gesture just doesn't work and the back gesture works in the old-fashioned way.

The point of this task is to update the existing API or introduce a new one compatible with the new behaviour.

Question: Is there a way to Parcelize generic type parameters in Sealed Classes?

Hi, I have a generic parameter in a child class of a Sealed Class in Common Module.
Generally if I had to parcelize this parameter in Android Module then I would have the @RawValue annotation coming from kotlinx.parcelize.RawValue . Is there any annotation provided by this library which can help me do the same?

My Sealed Class

import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.essenty.parcelable.Parcelize

/**
 * Generic class which can be used to describe the state of a screen
 */

@Parcelize
sealed class UIState<out T> : Parcelable {

    object Empty : UIState<Nothing>()

    object Loading : UIState<Nothing>()

    @Parcelize
    class Success<T>(val data: T) : UIState<T>()

    class Error(val exception: Exception) : UIState<Nothing>()
}

Getting this error for the data variable of Success class which is of type T:

Type is not directly supported by 'Parcelize'. Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()'

Basic use of lifecycle functions

This is a very promising library. So far, I haven't been able to figure out how to use it for my Kotlin Multiplatform project.
My only real requirement is to run a OnResume function to update my main activity, when it is brought back into focus from background.

I am not clear where to instantiate the functions:

val lifecycleRegistry = LifecycleRegistry()
val someLogic = SomeLogic(lifecycleRegistry)

Do I need to call lifecycleRegistry.subscribe() from the Composable of the activity in question?

What I'm currently doing in my main module is define the SomeLogic class:

class SomeLogic(lifecycle: Lifecycle,t:MutableState<String>) {
    init {
        lifecycle.subscribe(
            onResume = {t.value="Res1"}
        )
        lifecycle.doOnResume {
             t.value = "Res";
        }
    }
}

then instantiate the members in the Composable of the main activity:
The OnResume function is however never called.

val lifecycleRegistry = LifecycleRegistry()
val someLogic = SomeLogic(lifecycleRegistry,t3)
lifecycleRegistry.subscribe()
lifecycleRegistry.doOnCreate { t3.value="yo" }

The OnResume function is however never called.
Any advice or reference to sample projects would be highly appreciated.

Exception on building project Could not find "Essenty:lifecycle", kotlin\daemon, js (wasm) target

Exception on building project, js target:
./gradlew :composeApp:jsBrowserDevelopmentRun

e: Could not find "Essenty:lifecycle" in [C:\Users\User\AppData\Local\kotlin\daemon] 
e: java.lang.IllegalStateException: FATAL ERROR: Could not find "Essenty:lifecycle" in [C:\Users\User\AppData\Local\kotlin\daemon]

versions
kotlin = "1.8.20"
compose = "1.4.0"
kotlinx-coroutines = "1.6.4"
decompose = "1.0.0"
mvikotlin = "3.0.2"
essenty = "1.1.0"
kotlinx-serialization = "1.5.1"
"com.arkivanov.decompose:extensions-compose-jetbrains:1.0.0-compose-experimental"
"com.arkivanov.decompose:extensions-compose-jetbrains:1.0.0-beta-04-native-compose"

kotlin version: 1.8.20
error message: java.lang.IllegalStateException: FATAL ERROR: Could not find "Essenty:lifecycle" in [C:\Users\User\AppData\Local\kotlin\daemon]
	at org.jetbrains.kotlin.ir.backend.js.KlibKt$resolverLogger$1.fatal(klib.kt:111)
	at org.jetbrains.kotlin.library.KotlinLibrarySearchPathResolver.resolve(SearchPathResolver.kt:171)
	at org.jetbrains.kotlin.library.SearchPathResolver.resolve$default(SearchPathResolver.kt:20)
	at org.jetbrains.kotlin.library.SearchPathResolverKt.resolve(SearchPathResolver.kt:29)
	at org.jetbrains.kotlin.library.metadata.resolver.impl.KotlinLibraryResolverImpl$resolveDependencies$2$2.invoke(KotlinLibraryResolverImpl.kt:122)
	at org.jetbrains.kotlin.library.metadata.resolver.impl.KotlinLibraryResolverImpl$resolveDependencies$2$2.invoke(KotlinLibraryResolverImpl.kt:122)
	at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:210)
	at kotlin.sequences.FilteringSequence$iterator$1.calcNext(Sequences.kt:170)
	at kotlin.sequences.FilteringSequence$iterator$1.hasNext(Sequences.kt:194)
	at kotlin.sequences.TransformingSequence$iterator$1.hasNext(Sequences.kt:214)
	at kotlin.sequences.FilteringSequence$iterator$1.calcNext(Sequences.kt:169)
	at kotlin.sequences.FilteringSequence$iterator$1.hasNext(Sequences.kt:194)
	at kotlin.sequences.SequencesKt___SequencesKt.toCollection(_Sequences.kt:787)
	at kotlin.sequences.SequencesKt___SequencesKt.toMutableList(_Sequences.kt:817)
	at kotlin.sequences.SequencesKt___SequencesKt.toList(_Sequences.kt:808)
	at org.jetbrains.kotlin.library.metadata.resolver.impl.KotlinLibraryResolverImpl.resolveDependencies(KotlinLibraryResolverImpl.kt:135)
	at org.jetbrains.kotlin.library.metadata.resolver.impl.KotlinLibraryResolverImpl.resolveWithDependencies(KotlinLibraryResolverImpl.kt:44)
	at org.jetbrains.kotlin.ir.backend.js.JsLibraryResolverKt.jsResolveLibraries(JsLibraryResolver.kt:51)
	at org.jetbrains.kotlin.ir.backend.js.ModulesStructure.<init>(klib.kt:476)
	at org.jetbrains.kotlin.ir.backend.js.PrepareAnalyzedSourceModuleKt.prepareAnalyzedSourceModule(prepareAnalyzedSourceModule.kt:26)
	at org.jetbrains.kotlin.ir.backend.js.PrepareAnalyzedSourceModuleKt.prepareAnalyzedSourceModule$default(prepareAnalyzedSourceModule.kt:16)
	at org.jetbrains.kotlin.cli.js.K2JsIrCompiler.processSourceModule(K2JsIrCompiler.kt:435)
	at org.jetbrains.kotlin.cli.js.K2JsIrCompiler.doExecute(K2JsIrCompiler.kt:295)
	at org.jetbrains.kotlin.cli.js.K2JSCompiler.doExecute(K2JSCompiler.java:180)
	at org.jetbrains.kotlin.cli.js.K2JSCompiler.doExecute(K2JSCompiler.java:72)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:100)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:46)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
	at org.jetbrains.kotlin.incremental.IncrementalJsCompilerRunner.runCompiler(IncrementalJsCompilerRunner.kt:213)
	at org.jetbrains.kotlin.incremental.IncrementalJsCompilerRunner.runCompiler(IncrementalJsCompilerRunner.kt:84)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:486)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:409)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:290)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:112)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile$default(IncrementalCompilerRunner.kt:94)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execJsIncrementalCompiler(CompileServiceImpl.kt:567)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execJsIncrementalCompiler(CompileServiceImpl.kt:101)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1678)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

Project with reproduction:
https://github.com/t1r/ComposeThemeBuilder/tree/gradle_rework
branch - gradle_rework

TypeParceler is missing in `Parcelable` artifact

Hey,

I'm using Essenty in one of my KMM projects and I noticed that TypeParceler and IgnoredOnParcel are not available in published artifact.

Can be replicated running version 0.6.0, with new KMM project, Kotlin 1.7.10.

Make `Dispatchers.immediateOrFallback` public

This function is needed to develop custom integrations with Essenty for other libraries that want to provide the same default values for certain APIs as decompose does. For example, creating a function analogous to

fun LifecycleOwner.coroutineScope(
    context: CoroutineContext = Dispatchers.Main.immediateOrFallback,
): CoroutineScope =
    CoroutineScope(context = context).withLifecycle(lifecycle)

Is impossible right now because the default value cannot be retrieved.

Our use-case is such that we want to provide a retained coroutine scope that uses immediate dispatcher by default and we cannot do that unless we copy and paste the code from Essenty.

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.