Status Update
Comments
se...@google.com <se...@google.com> #2
Is this not a bug in AGP? It doesnt feel like an androidx issue.
yb...@google.com <yb...@google.com> #3
Probably yes, feel free to reroute or CC the right people. I was not 100% sure since the project uses the androidXMultiplatform
DSL, but looking at the DSL it simply forwards things to AGP.
yb...@google.com <yb...@google.com> #4
Do you have an example of what a broken consuming project looks like? which plugins are they applying?
yb...@google.com <yb...@google.com> #5
I've uploaded a sample project, to repro run: ./gradlew test
It is a simple Kotlin JVM project, it only uses plugins { kotlin("jvm") version "2.1.10" }
I was also able confirm that without AGP KMP (
da...@gmail.com <da...@gmail.com> #6
Looking at the Gradle metadata diff ("org.gradle.libraryelements": "jar"
? Should that be an aar
instead?
`
yb...@google.com <yb...@google.com> #7
yes, androidApiElements/runtimeElements variant publications should have aar value on the libraryelements attribute. What version of Gradle, AGP, KGP are you using in your library?
What's bizarre is that the compileClasspath is using the jvm vairant (in the test project you shared), it's only at runtime that it resolves the -android artifact of your library...
ap...@google.com <ap...@google.com> #8
It's very confusing, though why the android variant is chosen.
./gradlew dependencyInsight --configuration runtimeClasspath --dependency androidx.sqlite:sqlite:2.5.0-beta01
> Task :dependencyInsight
androidx.sqlite:sqlite:2.5.0-beta01
Variant androidRuntimeElements-published:
| Attribute Name | Provided | Requested |
|------------------------------------|--------------|--------------|
| org.gradle.status | release | |
| org.gradle.category | library | library |
| org.gradle.jvm.environment | android | standard-jvm |
| org.gradle.libraryelements | jar | jar |
| org.gradle.usage | java-runtime | java-runtime |
| org.jetbrains.kotlin.platform.type | jvm | jvm |
| org.gradle.dependency.bundling | | external |
| org.gradle.jvm.version | | 21 |
org.gradle.jvm.environment=standard-jvm is obviously requested and it doesn't match with the provided value for android. Looking at the jvmRuntimeElements from the diff you supplied of the gradle meta file, we see
{
"name": "jvmRuntimeElements-published",
"attributes": {
"org.gradle.category": "library",
"org.gradle.jvm.environment": "standard-jvm",
"org.gradle.usage": "java-runtime",
"org.jetbrains.kotlin.platform.type": "jvm"
},
"available-at": {
"url": "../../sqlite-jvm/2.5.0-beta01/sqlite-jvm-2.5.0-beta01.module",
"group": "androidx.sqlite",
"module": "sqlite-jvm",
"version": "2.5.0-beta01"
}
}
So every attribute in the jvmRuntimeElements matches the requested ones.
Description
Version used: 2.2.0-alpha01
Devices/Android versions reproduced on: Android 9
I'm having some random crashes due to CoroutineLiveData
```
Fatal Exception: java.lang.IllegalArgumentException: This source was already added with the different observer
at androidx.lifecycle.MediatorLiveData.addSource + 89(MediatorLiveData.java:89)
at androidx.lifecycle.CoroutineLiveDataKt.addDisposableSource + 102(CoroutineLiveDataKt.java:102)
at androidx.lifecycle.CoroutineLiveData.emitSource$lifecycle_livedata_ktx_release + 200(CoroutineLiveData.java:200)
at androidx.lifecycle.LiveDataScopeImpl$emitSource$2.invokeSuspend + 89(LiveDataScopeImpl.java:89)
at androidx.lifecycle.LiveDataScopeImpl$emitSource$2.invoke(LiveDataScopeImpl.java:11)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn + 91(UndispatchedKt.java:91)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext + 156(BuildersKt__Builders_commonKt.java:156)
at kotlinx.coroutines.BuildersKt.withContext + 1(BuildersKt.java:1)
at androidx.lifecycle.LiveDataScopeImpl.emitSource + 88(LiveDataScopeImpl.java:88)
at com.geekorum.ttrss.articles_list.FeedsViewModel$refreshed$1.invokeSuspend + 90(FeedsViewModel.java:90)
at com.geekorum.ttrss.articles_list.FeedsViewModel$refreshed$1.invoke(FeedsViewModel.java:8)
at androidx.lifecycle.BlockRunner$maybeRun$1.invokeSuspend + 147(BlockRunner.java:147)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith + 33(BaseContinuationImpl.java:33)
at kotlinx.coroutines.DispatchedTask.run + 241(DispatchedTask.java:241)
at android.os.Handler.handleCallback + 873(Handler.java:873)
at android.os.Handler.dispatchMessage + 99(Handler.java:99)
at android.os.Looper.loop + 193(Looper.java:193)
at android.app.ActivityThread.main + 6898(ActivityThread.java:6898)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run + 537(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main + 858(ZygoteInit.java:858)
```
From what I understand of CoroutineLiveData source code, when calling `LiveDataScope.emitSource()` the previous source is removed before adding the new one on MediatorLiveData.
```
@MainThread
internal fun emitSource(source: LiveData<T>): DisposableHandle {
clearSource()
val newSource = addDisposableSource(source)
emittedSource = newSource
return newSource
}
```
However the removing is done by launching a new coroutine
```
internal fun <T> MediatorLiveData<T>.addDisposableSource(
source: LiveData<T>
): DisposableHandle {
val disposed = AtomicBoolean(false)
addSource(source) {
if (!disposed.get()) {
value = it
} else {
removeSource(source)
}
}
return object : DisposableHandle {
override fun dispose() {
if (disposed.compareAndSet(false, true)) {
CoroutineScope(Dispatchers.Main).launch {
removeSource(source)
}
}
}
}
}
```
So there is no guarantee that `MediatorLiveData.addSource()` will be called after the removing coroutine is executed. This leads to the IllegalArgumentException in `MediatorLiveData.addSource()`
```
@MainThread
public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
Source<S> e = new Source<>(source, onChanged);
Source<?> existing = mSources.putIfAbsent(source, e);
if (existing != null && existing.mObserver != onChanged) {
throw new IllegalArgumentException(
"This source was already added with the different observer");
}
if (existing != null) {
return;
}
if (hasActiveObservers()) {
e.plug();
}
}
```