Fixed
Status Update
Comments
il...@google.com <il...@google.com>
mi...@gmail.com <mi...@gmail.com> #2
Yigit, do you have time to fix it?
reemission of the same liveData is racy
reemission of the same liveData is racy
au...@gmail.com <au...@gmail.com> #3
yea i'll take it.
be...@gmail.com <be...@gmail.com> #4
Thanks for the detailed analysis. This may not be an issue anymore since we've started using Main.immediate there but I' not sure; I'll try to create a test case.
md...@gmail.com <md...@gmail.com> #5
just emitting same live data reproduces the issue.
@Test
fun raceTest() {
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData)
emitSource(subLiveData) //crashes
}
subject.addObserver().apply {
testScope.advanceUntilIdle()
}
}
@Test
fun raceTest() {
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData)
emitSource(subLiveData) //crashes
}
subject.addObserver().apply {
testScope.advanceUntilIdle()
}
}
il...@google.com <il...@google.com>
ap...@google.com <ap...@google.com> #6
With 2.2.0-alpha04 (that use Main.immediate), the issue seems to be still there (I tested it by calling emitSource() twice, like your test case)
ap...@google.com <ap...@google.com> #7
yea sorry immediate does not fix it.
I actually have a WIP fix for it:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1112186
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} )
}
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} )
}
il...@google.com <il...@google.com> #8
Project: platform/frameworks/support
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
https://android-review.googlesource.com/1112186
https://goto.google.com/android-sha1/af12e75e6b4110f48e44ca121466943909de8f06
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
an...@gmail.com <an...@gmail.com> #9
Sounds good. I understand how I would pass the argument values during normal navigation, but how would I pass the argument to the starting fragment (set in `app:startDestination`)?
il...@google.com <il...@google.com> #10
Passing arguments to the start destination of your graph was added in alpha07: https://issuetracker.google.com/issues/110300470
an...@gmail.com <an...@gmail.com> #11
Am I right in determining from that, that the starting fragment can only have it's title passed during initial creation[$1] or direct navigation[$2]?
Assuming I have a starting fragment labelled "A", and a second fragment somewhere in the graph labelled "B".
If I wish to change the title for the starting fragment "A" due to some action taken (e.g button press) on that ("A") fragment then I will need to find some other means of setting it, is this correct?
Same if I want the title on fragment "A" to change based on a choice made on fragment "B", but navigation back to "A" (from "B") occurs via the back/up button or popping from the nav stack?
At the moment I'm setting the title for my starting fragment through a navigation listener (addOnNavigatedListener), and observing a LiveData change from my view model, as doing only one or the other results in situations where the title is not set. This means that for the majority of the time the title is set at least twice [$3].
$1) Presumably any one of these is only called once during the app's active lifetime, likely in `[Main]Activity.onCreate`, is that correct? As I understand it, using this feature would also require some alteration to how navigation is set up in the layout (.xml) files, right?
* `NavHostFragment.create(R.navigation.graph, args)`, or
* `navController.setGraph(R.navigation.graph, args)`, or
* `navController.setGraph(navGraph, args)`
$2) `navController.navigate(action)`
$3) The title is set by
1. the navigation component to whatever is in the label, and
2. the onNavigated listener, and
3. the observer on the LiveData.
While it's light work this repetition in setting the title seems wasteful.
I cannot quite put my finger on it but it seems strange to me that the navigation source gets to set the title as opposed to the destination setting the title based on some internal state.To make this work for me I would have to ensure that every source a fragment can be navigated from knows how to correctly determine the expected title, as opposed to that determination only being made in one place (the destination fragment).
il...@google.com <il...@google.com> #12
Parameterized labels only pull from the arguments of the destination, yes.
If you need dynamic titles or more complicated logic, you should not use android:label in your graph and instead set the title yourself - NavigationUI only sets a title if the android:label exists.
If you need dynamic titles or more complicated logic, you should not use android:label in your graph and instead set the title yourself - NavigationUI only sets a title if the android:label exists.
an...@gmail.com <an...@gmail.com> #13
Thanks, I've removed the label from the fragment in the nav graph resource file. It was used as the default/fallback title until the title was set through the user's action.
se...@google.com <se...@google.com> #14
This API was reviewed as part of "mass-review" of Navigation APIs at Donut Pod on December 4th.
jo...@gmail.com <jo...@gmail.com> #15
Sorry to come up and notify you all but this is marked as fixed even though I found an issue with this approach:
<fragment
android:id="@+id/exampleFragment"
android:name="com.example.ExampleFragment"
android:label="{title}"
tools:layout="@layout/fragment_examplel">
<argument
android:name="title"
app:argType="string"/>
</fragment>
This works fine, but as soon as the activity/view gets destroyed (e.g. rotating the device) the title gets cleared. let me know if I'm missing something but I'm using the safe args direction and passing the text as getString(R.string.example_title).
<fragment
android:id="@+id/exampleFragment"
android:name="com.example.ExampleFragment"
android:label="{title}"
tools:layout="@layout/fragment_examplel">
<argument
android:name="title"
app:argType="string"/>
</fragment>
This works fine, but as soon as the activity/view gets destroyed (e.g. rotating the device) the title gets cleared. let me know if I'm missing something but I'm using the safe args direction and passing the text as getString(R.string.example_title).
il...@google.com <il...@google.com> #16
Re #15 - please file a new bug with a sample that reproduces your issue.
st...@gmail.com <st...@gmail.com> #17
can i use argument from a parcelable type for the label ?
something like this ?
<fragment
android:id="@+id/displayFragment"
android:name="com.commonsware.jetpack.sampler.nav.DisplayFragment"
android:label="{User.userName}" >
<argument
android:name="User"
app:argType="com.example.app.User"/>
</fragment>
something like this ?
<fragment
android:id="@+id/displayFragment"
android:name="com.commonsware.jetpack.sampler.nav.DisplayFragment"
android:label="{User.userName}" >
<argument
android:name="User"
app:argType="com.example.app.User"/>
</fragment>
il...@google.com <il...@google.com> #18
Re #17 - no, that's not supported, the only supported format is {argName}
which calls toString()
on the argument. You'd want to file a new feature request.
Description
The navigation components use the value in `android:label` to set the [toolbar] title depending on where in the navigation graph the use has navigated to. This works fine except I want to have the title reflect the category of things the screen is referring to, but the specific thing that the user is about to edit.
Normally I would have piece of code like `updateTitle` below on the activity and then call it from the fragment (onResume) with:
``` kotlin
updateTitle(getString(R.string.book_title, bookNumber))
```
but this does not work unless I also remove the `android:label` attribute from the nav_graph.xml.
Component used:
'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha01'
'android.arch.navigation:navigation-ui-ktx:1.0.0-alpha01'
Version used:
as above
Devices/Android versions reproduced on:
All
- Sample project to trigger the issue.
``` xml
// snippet from nav_graph.xml
<fragment
android:id="@+id/bookFragment"
android:name="com.example.bookFragment"
android:label="@string/book_title" >
<argument
android:name="bookId"
app:type="integer" />
</fragment>
// snippet from sttrings.xml
<string name="book_title">book %1$d</string>
```
Some snippets from the way I would otherwise do it.
``` kotlin
// actrivity, implements updateTitle( from an interface (Foo)
override fun updateTitle(title: String) {
toolbar?.title = title
}
// fragment
override fun onResume() {
super.onResume()
// foo is the activity cast to Foo
foo?.updateTitle(getString(R.string.reading_title, 1))
}
```
- A screenrecord or screenshots showing the issue (if UI related).
Actual title vs expected