Status Update
Comments
jb...@google.com <jb...@google.com>
il...@google.com <il...@google.com> #2
reemission of the same liveData is racy
ed...@gmail.com <ed...@gmail.com> #3
il...@google.com <il...@google.com> #4
cl...@google.com <cl...@google.com>
ap...@google.com <ap...@google.com> #5
@Test
fun raceTest() {
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData)
emitSource(subLiveData) //crashes
}
subject.addObserver().apply {
testScope.advanceUntilIdle()
}
}
cl...@google.com <cl...@google.com> #6
ed...@gmail.com <ed...@gmail.com> #7
I actually have a WIP fix for it:
if your case is the one i found (emitting same LiveData multiple times, as shown in #5) you can work around it by adding a dummy transformation.
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData.map {it })
emitSource(subLiveData.map {it} )
}
pr...@google.com <pr...@google.com> #8
Branch: androidx-master-dev
commit af12e75e6b4110f48e44ca121466943909de8f06
Author: Yigit Boyar <yboyar@google.com>
Date: Tue Sep 03 12:58:11 2019
Fix coroutine livedata race condition
This CL fixes a bug in liveData builder where emitting same
LiveData source twice would make it crash because the second
emission registry could possibly happen before first one is
removed as source.
We fix it by using a suspending dispose function. It does feel
a bit hacky but we cannot make DisposableHandle.dispose async
and we do not want to block there. This does not mean that there
is a problem if developer disposes it manually since our emit
functions take care of making sure it disposes (and there is
no other way to add source to the underlying MediatorLiveData)
Bug: 140249349
Test: BuildLiveDataTest#raceTest_*
Change-Id: I0b464c242a583da4669af195cf2504e2adc4de40
M lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha05.txt
M lifecycle/lifecycle-livedata-ktx/api/current.txt
M lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_2.2.0-alpha05.txt
M lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_current.txt
M lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha05.txt
M lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
M lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
M lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/BuildLiveDataTest.kt
je...@gmail.com <je...@gmail.com> #9
This seems to be still be busted (same error) if you change isNullableAllowed = true (I don't have any errors when changing the same code to isNullableAllowed = false AND <IndividualId>)
Example:
Custom Class
@JvmInline
@Serializable
value class IndividualId(val value: String)
NavType for nullable:
val IndividualIdNullableNavType = object : NavType<IndividualId?>(
isNullableAllowed = true
) {
override fun get(bundle: Bundle, key: String): IndividualId? = bundle.getString(key)?.let { IndividualId(it) }
override fun put(bundle: Bundle, key: String, value: IndividualId?) {
value?.let { bundle.putString(key, it.value) }
}
override fun parseValue(value: String): IndividualId? {
if (value == "null") return null
return Json.decodeFromString<IndividualId>(value)
}
override fun serializeAsValue(value: IndividualId?): String = value?.let { Json.encodeToString(IndividualId.serializer(), it) } ?: ""
}
NavHost code:
composable<IndividualEditRoute>(mapOf(
typeOf<IndividualId?>() to IndividualIdNullableNavType,
)) { backStackEntry ->
Log.w("TEST", "Before") // prints
val individualEditRoute = backStackEntry.toRoute<IndividualEditRoute>()
Log.w("TEST", "After: individualEditRoute == $individualEditRoute") // never gets here because: ClassCastException: IndividualId cannot be cast to java.lang.String
IndividualEditScreen(navController)
}
The following works (no error):
onClick = {
navController.navigate(IndividualEditRoute())
},
The following will cause a "ClassCastException: IndividualId cannot be cast to java.lang.String"
onClick = {
navController.navigate(IndividualEditRoute(IndividualId("123")))
},
cl...@google.com <cl...@google.com> #11
Re
ga...@gmail.com <ga...@gmail.com> #12
I'm also having this issue. I have created a repo for showcasing this:
In there you will find another case with a Parcelable object. In my case is quite strange since the logcat shows this error:
Process: com.bobbyesp.navigationbugreport, PID: 23169
kotlinx.serialization.SerializationException: Serializer for class 'Companion' is not found.
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
at kotlinx.serialization.internal.Platform_commonKt.serializerNotRegistered(Platform.common.kt:91)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:278)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source:1)
at androidx.navigation.NavGraph.setStartDestination(NavGraph.kt:404)
cl...@google.com <cl...@google.com> #13
Re
But based on your source code, it is your startDestination in song
arg for your SongInformationPage
.
With safe args, you can conveniently pass in starting arguments by passing in a class instance. The arguments will automatically be available within the starting composable.
This means your nested graph should be
navigation<UtilitiesNavigator>(
startDestination = SongInformationPage(song = ...),
)
If your startDestination does not contain arguments, then you can pass in either:
@Serializable
object MyDestination
startDestination = MyDestination
--- OR ---
@Serializable
class MyDestination
startDestination = MyDestination::class
If you have further questions, please file a separate bug so it is easier for other users with the same question to locate the answer.
ga...@gmail.com <ga...@gmail.com> #14
du...@matechmobile.com <du...@matechmobile.com> #15
here is my code for you guys to look around:
fun setNavController(mNavController: NavController) {
this.navController = mNavController
navController.graph = navController.createGraph(startDestination = nav_routes.manual_add_otp_data) {
fragment<AddOtpManualFragment, AddOtpData>(
typeMap = mapOf(typeOf<AddOtpData>() to getCustomParametersType<AddOtpData>())
) {
label = nav_routes.manual_add_otp_data
}
}
}
--------
private inline fun <reified T : Parcelable> getCustomParametersType(nullable: Boolean = false) =
object : NavType<T>(
isNullableAllowed = nullable
) {
override fun put(bundle: Bundle, key: String, value: T) {
// Handle null case if the type is nullable
bundle.putParcelable(key, value)
}
override fun get(bundle: Bundle, key: String): T? {
// Return null if the value is nullable and not present
return if (isNullableAllowed) {
bundle.getParcelable(key)
} else {
bundle.getParcelable(key)
?: throw IllegalArgumentException("No value present for non-nullable type")
}
}
override fun parseValue(value: String): T {
// Decode and deserialize the string into the object
return Json.decodeFromString(value)
}
override fun serializeAsValue(value: T): String {
// Encode the object into a JSON string and Uri encode it
return Uri.encode(Json.encodeToString(value))
}
}
---------
@Serializable
@Parcelize
data class AddOtpData(val otpAuthDB: OtpAuthDB) : Parcelable
that all, thanks
Description
Version used: 2.8.0-alpha08
Devices/Android versions reproduced on: Pixel 6a (Android 14)
Getting the custom arg in a ViewModel when using just serializables instead of parcelables with this NavType:
val bookType = object : NavType<Book>(isNullableAllowed = false) {
override fun get(bundle: Bundle, key: String): Book? {
return bundle.getString(key)?.let { Json.decodeFromString<Book>(it) }
}
override fun parseValue(value: String) = Json.decodeFromString<Book>(value)
override fun serializeAsValue(value: Book): String = Json.encodeToString(value)
override fun put(bundle: Bundle, key: String, value: Book) {
bundle.putString(key, Json.encodeToString(value))
}
}
It works well if I get the value from the NavBackStackEntry but if I do this in the ViewModel:
val bookDetail = savedStateHandle.toRoute<BookDetail>()
I get this error when navigating to the detail:
java.lang.ClassCastException: java.lang.String cannot be cast to com.example.composenavigation.typesafety.Book
You can see an example in:
The navigation is in the file: TypeSafetyNavigation.kt
Thanks in advance!