rive-app / rive-android

A runtime for interactive animations on Android
https://rive.app
MIT License
332 stars 30 forks source link

java.lang.UnsatisfiedLinkError: No implementation found for long app.rive.runtime.kotlin.core.FileAssetLoader.constructor #316

Closed kelvinwatson closed 4 months ago

kelvinwatson commented 4 months ago

I'm trying to run an androidTest using a RiveAnimationView wrapped in a AndroidView for Compose. Note that I've also tried this as a unitTest with Robolectric but I get the same result.

Here is the Composable:

@[Composable VisibleForTesting]
internal fun RiveAnimationViewInternal(
    @RawRes animation: Int,
    modifier: Modifier = Modifier,
    alignment: Alignment = Alignment.CENTER,
    animationName: String? = null,
    artboardName: String? = null,
    autoplay: Boolean = true,
    fit: Fit = Fit.CONTAIN,
    loop: Loop = Loop.AUTO,
    stateMachineName: String? = null,
    fileAssetLoader: FileAssetLoader? = null
) {
    AndroidView(
        modifier = modifier,
        factory = { context ->
            RiveAnimationView.Builder(context)
                .setResource(animation)
                .setAlignment(alignment)
                .applyNullable(animationName) { setAnimationName(it) }
                .applyNullable(artboardName) { setArtboardName(it) }
                .setAutoplay(autoplay)
                .setFit(fit)
                .setLoop(loop)
                .applyNullable(fileAssetLoader) { setAssetLoader(it) }
                .applyNullable(stateMachineName) { setStateMachineName(it) }
                .applyNullable(fileAssetLoader) { setAssetLoader(it) }
                .build()
        }
    )
}

inline fun <reified T, R> R.applyNullable(nullable: T?, block: R.(T) -> R): R = nullable?.let { block(it) } ?: this

Here is the test:

class RiveAndroidViewTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun validateRiveAndroidView() {
        composeTestRule.setContent {
            MaterialTheme {
                RiveAndroidView(animation = testLoop)
            }
        }
    }
}

And the exception:

java.lang.UnsatisfiedLinkError: No implementation found for long app.rive.runtime.kotlin.core.FileAssetLoader.constructor() (tried Java_app_rive_runtime_kotlin_core_FileAssetLoader_constructor and Java_app_rive_runtime_kotlin_core_FileAssetLoader_constructor__)
at app.rive.runtime.kotlin.core.FileAssetLoader.constructor(Native Method)
at app.rive.runtime.kotlin.core.FileAssetLoader.<init>(FileAssetLoader.kt:17)
at app.rive.runtime.kotlin.core.FallbackAssetLoader.<init>(FileAssetLoader.kt:45)
at app.rive.runtime.kotlin.RiveAnimationView.<init>(RiveAnimationView.kt:318)
at app.rive.runtime.kotlin.RiveAnimationView.<init>(RiveAnimationView.kt:64)
at com.example.app.rive.RIveAndroidViewKt$RiveAndroidView$1$1.invoke(RIveAndroidView.kt:44)
at com.example.app.rive.RIveAndroidViewKt$RiveAndroidView$1$1.invoke(RIveAndroidView.kt:43)
at androidx.compose.ui.viewinterop.ViewFactoryHolder.<init>(AndroidView.android.kt:337)
at androidx.compose.ui.viewinterop.AndroidView_androidKt$createAndroidViewNodeFactory$1.invoke(AndroidView.android.kt:271)
at androidx.compose.ui.viewinterop.AndroidView_androidKt$createAndroidViewNodeFactory$1.invoke(AndroidView.android.kt:270)
at androidx.compose.ui.viewinterop.AndroidView_androidKt$AndroidView$$inlined$ComposeNode$1.invoke(Composables.kt:254)
at androidx.compose.runtime.ComposerImpl$createNode$2.invoke(Composer.kt:1610)
at androidx.compose.runtime.ComposerImpl$createNode$2.invoke(Composer.kt:1608)
at androidx.compose.runtime.ComposerImpl$recordInsert$2.invoke(Composer.kt:3546)
at androidx.compose.runtime.ComposerImpl$recordInsert$2.invoke(Composer.kt:3543)
at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:818)
at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:849)
at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:1041)
at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:520)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:142)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:133)
at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1191)
at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:133)
at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:183)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.kt:314)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.kt:192)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:140)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:133)
at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1266)
at android.view.View.dispatchAttachedToWindow(View.java:20753)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3490)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
at android.view.ViewGroup.addViewInner(ViewGroup.java:5290)
at android.view.ViewGroup.addView(ViewGroup.java:5076)
at android.view.ViewGroup.addView(ViewGroup.java:5048)
at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:492)
at android.app.Activity.setContentView(Activity.java:3556)
at androidx.activity.ComponentActivity.setContentView(ComponentActivity.java:481)
at androidx.activity.compose.ComponentActivityKt.setContent(ComponentActivity.kt:70)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$AndroidComposeUiTestImpl$setContent$3.invoke(ComposeUiTest.android.kt:475)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$AndroidComposeUiTestImpl$setContent$3.invoke(ComposeUiTest.android.kt:474)
at androidx.compose.ui.test.junit4.AndroidSynchronization_androidKt.runOnUiThread$lambda$0(AndroidSynchronization.android.kt:37)
at androidx.compose.ui.test.junit4.AndroidSynchronization_androidKt.$r8$lambda$SUUsAclwsAzFrTMk57EPm0y6pV0(Unknown Source:0)
at androidx.compose.ui.test.junit4.AndroidSynchronization_androidKt$$ExternalSyntheticLambda0.call(Unknown Source:2)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.app.Instrumentation$SyncRunnable.run(Instrumentation.java:2266)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)∂
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

Expected behavior

Should be testable as a Composable. We need some way to stub the RendererAttributes' assetLoader initialization in the RiveAnimationView#init block, or you could defer initialization of the rendererAttributes until you have access to the user's provided fileAssetLoader from the RiveAnimationView.Builder.

Device & Versions

Additional context

I've tried mocking the fileAssetLoader but the RiveAnimationView defaults to the FallbackAssetLoader in its init block

kelvinwatson commented 4 months ago

I realized I forgot to initalize Rive:

    @Before
    fun beforeEach() {
        Rive.init(InstrumentationRegistry.getInstrumentation().targetContext.applicationContext)
    }