ksharma-xyz / Krail

Krail is a trip planning android app for public transport in Sydney, NSW, Australia
Apache License 2.0
1 stars 0 forks source link

Navigation is a pain with Navigation Compose API #149

Open ksharma-xyz opened 2 weeks ago

ksharma-xyz commented 2 weeks ago

I would really like to pass some data around different screens such as StopItem, but need to implement Parcelable for all data types used.

data class StopItem(
    val stopName: String,
    val transportModes: ImmutableSet<TransportModeType> = persistentSetOf(),
    val stopId: String,
): Serializable

I am unable to do this:

val fromStopItem = backStackEntry.savedStateHandle.get<StopItem>(SearchStopFieldType.FROM.key)
val toStopItem = backStackEntry.savedStateHandle.get<StopItem>(SearchStopFieldType.TO.key)
FATAL EXCEPTION: main (Ask Gemini)
                        Process: xyz.ksharma.krail.debug, PID: 11280
                        android.os.BadParcelableException: Parcelable encountered IOException writing serializable object (name = xyz.ksharma.krail.trip_planner.ui.state.searchstop.model.StopItem)
                            at android.os.Parcel.writeSerializable(Parcel.java:2907)
                            at android.os.Parcel.writeValue(Parcel.java:2673)
                            at android.os.Parcel.writeValue(Parcel.java:2472)
                            at android.os.Parcel.writeList(Parcel.java:1453)
                            at android.os.Parcel.writeValue(Parcel.java:2616)
                            at android.os.Parcel.writeValue(Parcel.java:2472)
                            at android.os.Parcel.writeArrayMapInternal(Parcel.java:1336)
                            at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1844)
                            at android.os.Bundle.writeToParcel(Bundle.java:1390)
                            at android.os.Parcel.writeBundle(Parcel.java:1405)
                            at android.os.Parcel.writeValue(Parcel.java:2589)
                            at android.os.Parcel.writeValue(Parcel.java:2479)
                            at android.os.Parcel.writeArrayMapInternal(Parcel.java:1336)
                            at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1844)
                            at android.os.Bundle.writeToParcel(Bundle.java:1390)
                            at android.os.Parcel.writeBundle(Parcel.java:1405)
                            at android.os.Parcel.writeValue(Parcel.java:2589)
                            at android.os.Parcel.writeValue(Parcel.java:2479)
                            at android.os.Parcel.writeArrayMapInternal(Parcel.java:1336)
                            at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1844)
                            at android.os.Bundle.writeToParcel(Bundle.java:1390)
                            at android.os.Parcel.writeBundle(Parcel.java:1405)
                            at androidx.navigation.NavBackStackEntryState.writeToParcel(NavBackStackEntryState.kt:73)
                            at android.os.Parcel.writeParcelable(Parcel.java:2694)
                            at android.os.Parcel.writeParcelableArray(Parcel.java:4507)
                            at android.os.Parcel.writeValue(Parcel.java:2637)
                            at android.os.Parcel.writeValue(Parcel.java:2472)
                            at android.os.Parcel.writeArrayMapInternal(Parcel.java:1336)
                            at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1844)
                            at android.os.Bundle.writeToParcel(Bundle.java:1390)
                            at android.os.Parcel.writeBundle(Parcel.java:1405)
                            at android.os.Parcel.writeValue(Parcel.java:2589)
                            at android.os.Parcel.writeValue(Parcel.java:2479)
                            at android.os.Parcel.writeList(Parcel.java:1453)
                            at android.os.Parcel.writeValue(Parcel.java:2616)
                            at android.os.Parcel.writeValue(Parcel.java:2472)
                            at android.os.Parcel.writeArrayMapInternal(Parcel.java:1336)
                            at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1844)
                            at android.os.Bundle.writeToParcel(Bundle.java:1390)
                            at android.os.Parcel.writeBundle(Parcel.java:1405)
                            at android.os.Parcel.writeValue(Parcel.java:2589)
                            at android.os.Parcel.writeValue(Parcel.java:2479)
                            at android.os.Parcel.writeArrayMapInternal(Parcel.java:1336)
                            at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1844)
                            at android.os.Bundle.writeToParcel(Bundle.java:1390)
                            at android.os.Parcel.writeBundle(Parcel.java:1405)
                            at android.os.Parcel.writeValue(Parcel.java:2589)
                            at android.os.Parcel.writeValue(Parcel.java:2479)
                            at android.os.BaseBundle.dumpStats(BaseBundle.java:1918)
                            at android.os.BaseBundle.dumpStats(BaseBundle.java:1955)
                            at android.app.servertransaction.PendingTransactionActions$StopInfo.collectBundleStates(PendingTransactionActions.java:123)
                            at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:139)
                            at android.os.Handler.handleCallback(Handler.java:959)
                            at android.os.Handler.dispatchMessage(Handler.java:100)
                            at android.os.Looper.loopOnce(Looper.java:232)
                            at android.os.Looper.loop(Looper.java:317)
                            at android.app.ActivityThread.main(ActivityThread.java:8592)
                            at java.lang.reflect.Method.invoke(Native Method)
                            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
                            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
                        Caused by: java.io.NotSerializableException: kotlinx.collections.immutable.implementations.persistentOrderedSet.PersistentOrderedSet
                            at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1240)
                            at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1620) (Ask Gemini)
                            at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1581)
                            at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1490)
                            at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1234)
                            at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:354)
                            at android.os.Parcel.writeSerializable(Parcel.java:2902)
ksharma-xyz commented 2 weeks ago

Probably using https://github.com/slackhq/circuit is a good idea, but that would mean using MVP? Need to explore

ksharma-xyz commented 2 weeks ago

As a temporary workaround will serialize the concrete data types into JSON format and then deserialize them back into their original concrete types.

val fromStopItem: StopItem? = backStackEntry.savedStateHandle.get<String>(SearchStopFieldType.FROM.key)
        ?.let { fromJsonString(it) }
val toStopItem: StopItem? = backStackEntry.savedStateHandle.get<String>(SearchStopFieldType.TO.key)
        ?.let { fromJsonString(it) }

This creates a core problem, that now values are nullable and it needs to be handled. We should be able to pass data around, and ensure it will be non null.

https://github.com/ksharma-xyz/Krail/pull/150

ksharma-xyz commented 2 weeks ago

Two major issues with using Navigation Compose API:

ksharma-xyz commented 1 week ago

Another problem:

I had to create two different variables for saving one thing. In this PR https://github.com/ksharma-xyz/Krail/pull/168 ,

// This holds Arguments value
val fromArg = backStackEntry.savedStateHandle.get<String>(SearchStopFieldType.FROM.key)?.let { fromJsonString(it) }
val toArg = backStackEntry.savedStateHandle.get<String>(SearchStopFieldType.TO.key)?.let { fromJsonString(it) }

// This holds state value for Screen
var fromStopItem: StopItem? by rememberSaveable { mutableStateOf(fromArg) }
var toStopItem: StopItem? by rememberSaveable { mutableStateOf(toArg) }

The first variable fromArg represents the argument, and the second variable fromStopItem represents the state.

When reverse button is clicked, then the data has to be changed twice because of two variables requirement.

Timber.d("onReverseButtonClick:")
val bufferStop = fromStopItem
backStackEntry.savedStateHandle[SearchStopFieldType.FROM.key] = toStopItem?.toJsonString()
backStackEntry.savedStateHandle[SearchStopFieldType.TO.key] = bufferStop?.toJsonString()

fromStopItem = toStopItem
toStopItem = bufferStop
ksharma-xyz commented 1 week ago

Exploring Circuit here - https://github.com/ksharma-xyz/Circuit-Demo