JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
15.9k stars 1.16k forks source link

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

Open viratshukla opened 2 years ago

viratshukla commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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()

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

val paramList2 = arrayListOf(Color.Black)

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

viratshukla commented 2 years ago

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.

akurasov commented 2 years ago

'@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?

viratshukla commented 2 years ago

@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 commented 2 years ago

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?

kirill-grouchnikov commented 2 years ago

// 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.

viratshukla commented 2 years ago

@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.

kirill-grouchnikov commented 2 years ago

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

viratshukla commented 2 years ago

@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.

kirill-grouchnikov commented 2 years ago

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

akurasov commented 2 years ago

@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.

viratshukla commented 2 years ago

@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.

akurasov commented 2 years ago

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

okushnikov commented 2 weeks ago

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