compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

androidx.compose.ui.graphics.Color causing error during reflection

Open viratshukla opened this issue 4 years ago • 15 comments

Hi, I am using reflection to invoke a composable method in some file. If that file has any method, which takes androidx.compose.ui.graphics.Color as a parameter, reflection fails.

Please find complete stack trace for the same.

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.composeplayground, PID: 30706 kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Inconsistent number of parameters in the descriptor and Java reflection object: 3 != 1 Calling: public fun Widget2(a: androidx.compose.ui.graphics.Color): kotlin.Unit defined in com.example.composeplayground.dsLibrary[DeserializedSimpleFunctionDescriptor@ec84127] Parameter types: [long, interface androidx.compose.runtime.Composer, int]) Default: false at kotlin.reflect.jvm.internal.calls.InlineClassAwareCaller.(InlineClassAwareCaller.kt:102) at kotlin.reflect.jvm.internal.calls.InlineClassAwareCallerKt.createInlineClassAwareCallerIfNeeded(InlineClassAwareCaller.kt:159) at kotlin.reflect.jvm.internal.calls.InlineClassAwareCallerKt.createInlineClassAwareCallerIfNeeded$default(InlineClassAwareCaller.kt:149) at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:89) at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:61) at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(ReflectProperties.java:63) at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(KFunctionImpl.kt:61) at kotlin.reflect.jvm.ReflectJvmMapping.getJavaMethod(ReflectJvmMapping.kt:63) at kotlin.reflect.jvm.ReflectJvmMapping.getKotlinFunction(ReflectJvmMapping.kt:122) at com.example.composeplayground.MainActivityKt.UseReflection(MainActivity.kt:50) at com.example.composeplayground.ComposableSingletons$MainActivityKt$lambda-1$1.invoke(MainActivity.kt:35) at com.example.composeplayground.ComposableSingletons$MainActivityKt$lambda-1$1.invoke(MainActivity.kt:34) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.material.SurfaceKt$Surface$6.invoke(Surface.kt:267) at androidx.compose.material.SurfaceKt$Surface$6.invoke(Surface.kt:254) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:215) at androidx.compose.material.SurfaceKt.Surface-F-jzlyU(Surface.kt:251) at androidx.compose.material.SurfaceKt.Surface-F-jzlyU(Surface.kt:110) at com.example.composeplayground.ComposableSingletons$MainActivityKt$lambda-2$1.invoke(MainActivity.kt:34) at com.example.composeplayground.ComposableSingletons$MainActivityKt$lambda-2$1.invoke(MainActivity.kt:32) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:215) at androidx.compose.material.TextKt.ProvideTextStyle(Text.kt:252) at androidx.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:81) at androidx.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:80) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:215) at androidx.compose.material.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:72) at com.example.composeplayground.ui.theme.ThemeKt.ComposePlaygroundTheme(Theme.kt:41) at com.example.composeplayground.ComposableSingletons$MainActivityKt$lambda-3$1.invoke(MainActivity.kt:32) at com.example.composeplayground.ComposableSingletons$MainActivityKt$lambda-3$1.invoke(MainActivity.kt:31) E/AndroidRuntime: at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:384) at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:228) at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:227) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:215) at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:148) at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:114) at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:113) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:215) at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:106) at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:162) at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:161) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:215) at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:161) at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:144) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.ComposerKt.invokeComposable(Composer.kt:3330) at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:2577) at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:2573) at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(SnapshotState.kt:540) at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:2566) at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:2517) at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:476) at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:727) at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:432) at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:144) at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:135) at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:727) at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:135) at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:187) at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354) at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:196) at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:142) E/AndroidRuntime: at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:135) at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:814) at android.view.View.dispatchAttachedToWindow(View.java:18347) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3397) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3404) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3404) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3404) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3404) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1761) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1460) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7183) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949) at android.view.Choreographer.doCallbacks(Choreographer.java:761) at android.view.Choreographer.doFrame(Choreographer.java:696) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935) at android.os.Handler.handleCallback(Handler.java:873) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6669) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) I/Process: Sending signal. PID: 30706 SIG: 9

viratshukla avatar Jan 11 '22 08:01 viratshukla

Please find sample code snippets.

MainActivity.kt file content

@Composable fun UseReflection() { val packageName = "com.example.composeplayground.dsLibrary.MyComposeWidgets" val functionName = "Widget1"

val c = Class.forName("${packageName}Kt")

c.declaredMethods.forEach { method ->
    if (method.kotlinFunction?.name == functionName) {
        InvokeMethod(method)
    }
}

}

@Composable fun InvokeMethod(method : Method) { val dataToBeInvoked = arrayListOf<Any?>()

method.kotlinFunction?.parameters?.forEachIndexed { index, param ->
    dataToBeInvoked.add(paramList1[index])
}

// Since compiler adds currentCompose in signature
dataToBeInvoked.add(currentComposer)

// Some random zeroes are also added during compile time, hence this logic
val javaParamCount = method.parameterCount
val numOfZeroesToBeAdded = javaParamCount - dataToBeInvoked.size
for (x in 1..numOfZeroesToBeAdded) {
    dataToBeInvoked.add(0)
}

method.kotlinFunction?.call(*dataToBeInvoked.toTypedArray())

}

viratshukla avatar Jan 11 '22 08:01 viratshukla

File 2 MyComposeWidgets.kt content

package com.example.composeplayground.dsLibrary

import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color

val paramList1 = arrayListOf<Any>()

@Composable fun Widget1() { Text("Some Random Text - Widget 1") }

val paramList2 = arrayListOf<Any>(Color.Black)

@Composable fun Widget2(a : Color) { Text("Some Random Text - Widget 2") }

viratshukla avatar Jan 11 '22 08:01 viratshukla

If I comment-out Widget2 method, then reflection works great, but if its not commented, then reflection fails.

Similar issue happens, If I pass dp as parameter in a composable function.

viratshukla avatar Jan 11 '22 08:01 viratshukla

'@Composable' functions are modified by compiler plugin, so their real signatures are different from the source code.

Could you clarify, what is the business goal to use reflection with Composable functions?

akurasov avatar Jan 11 '22 11:01 akurasov

@akurasov I need to generate some generic code, so that on runtime, I can show correct UI based on response. Yes, I am aware, that on compile time, currentComposer and some integers are added. And this reflection is working fine in all the cases, except this Color param or Dp param in method signature.

Can you please let me know the solution, if possible?

viratshukla avatar Jan 11 '22 13:01 viratshukla

Although, there is a workaround, that if I wrap this Color param with my data class, it works fine.

But question is, why this androidx.compose.ui.graphics.Color and androidx.compose.ui.unit.Dp are not working in the first place?

viratshukla avatar Jan 11 '22 13:01 viratshukla

// Some random zeroes are also added during compile time, hence this logic

This is just not deterministic, since Compose compiler does not make any guarantees how many additional parameters it's adding to composable functions, where it's adding them, what is the type, and most importantly, what the values should be. So randomly adding zeroes as integers is inviting a really big trouble to begin with.

This is not a desktop-specific issue. I'd recommend either revisiting the approach to not use reflection at all, or to open a discussion on the Compose main tracker / #compose channel in Kotlin discord.

kirill-grouchnikov avatar Jan 11 '22 13:01 kirill-grouchnikov

@kirill-grouchnikov Currently I am following this article. https://commonsware.com/blog/2020/05/29/reflection-composables.html

Please share the link of Compose main tracker, so that I can open an issue there as well.

viratshukla avatar Jan 11 '22 14:01 viratshukla

That article already has a link to an existing tracker bug for official reflection support that you can follow

kirill-grouchnikov avatar Jan 11 '22 14:01 kirill-grouchnikov

@kirill-grouchnikov Thanks, that I will check. But main issue is still - androidx.compose.ui.graphics.Color causing error during reflection.

Same code is working for other params, if I replace that param with some other param. Or wrap my Color property with another data class.

viratshukla avatar Jan 11 '22 14:01 viratshukla

And again, you're trying to do something that is very explicitly not supported.

kirill-grouchnikov avatar Jan 11 '22 15:01 kirill-grouchnikov

@viratshukla So basically you want to send request exactly at rendering time? Could you give some more details about your task?

I would rather update State in a "request" thread and use simple if's in the Composition code.

akurasov avatar Jan 11 '22 15:01 akurasov

@akurasov
Hi, Basically, I have created all my Composable components and I am writing a wrapper using reflection, which will decide in run-time, which component should be displayed.

viratshukla avatar Jan 12 '22 04:01 viratshukla

Why not just use simple composable function, that will decide this based on some State?

akurasov avatar Jan 12 '22 11:01 akurasov

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.

okushnikov avatar Aug 26 '24 16:08 okushnikov