karatekraft
karatekraft

Reputation: 195

How to preview an Android Glance App Widget in Android Studio?

I am following the Android Glance documentation (https://developer.android.com/jetpack/compose/glance/create-app-widget) to create a widget. The widget UI is defined in composable function called MyContent.

I try to preview the UI by adding a composable preview...

@Preview(showBackground = true)
@Composable
fun ContentPreview() {
    MyContent()
}

But I get an error stating that it "Failed to instantiate Composition Local."

This preview was unable to find a CompositionLocal. You might need to define it so it can render correctly.

What is the correct way to preview a Glance App Widget?

I am able to build and run the widget on my physical device, but I would like the ability to preview the widget in Android Studio similar to how we can preview other Composable functions

Full StackTrace:

java.lang.IllegalStateException: Invalid applier
    at androidx.compose.runtime.ComposablesKt.invalidApplier(Composables.kt:472)
    at androidx.glance.layout.ColumnKt.Column-K4GKKTE(Column.kt:102)
    at com.example.myproject.MyAppWidgetKt.MyContent(MyAppWidget.kt:46)
    at com.example.myproject.MyAppWidgetKt.ContentPreview(MyAppWidget.kt:69)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at androidx.compose.ui.tooling.ComposableInvoker.invokeComposableMethod(ComposableInvoker.kt:163)
    at androidx.compose.ui.tooling.ComposableInvoker.invokeComposable(ComposableInvoker.kt:203)
    at androidx.compose.ui.tooling.ComposeViewAdapter$init$3$1$composable$1.invoke(ComposeViewAdapter.kt:509)
    at androidx.compose.ui.tooling.ComposeViewAdapter$init$3$1$composable$1.invoke(ComposeViewAdapter.kt:507)
    at androidx.compose.ui.tooling.ComposeViewAdapter$init$3$1.invoke(ComposeViewAdapter.kt:544)
    at androidx.compose.ui.tooling.ComposeViewAdapter$init$3$1.invoke(ComposeViewAdapter.kt:502)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.tooling.InspectableKt.Inspectable(Inspectable.kt:61)
    at androidx.compose.ui.tooling.ComposeViewAdapter$WrapPreview$1.invoke(ComposeViewAdapter.kt:449)
    at androidx.compose.ui.tooling.ComposeViewAdapter$WrapPreview$1.invoke(ComposeViewAdapter.kt:448)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.tooling.ComposeViewAdapter.WrapPreview(ComposeViewAdapter.kt:443)
    at androidx.compose.ui.tooling.ComposeViewAdapter.access$WrapPreview(ComposeViewAdapter.kt:127)
    at androidx.compose.ui.tooling.ComposeViewAdapter$init$3.invoke(ComposeViewAdapter.kt:502)
    at androidx.compose.ui.tooling.ComposeViewAdapter$init$3.invoke(ComposeViewAdapter.kt:499)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:428)
    at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:252)
    at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:251)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:195)
    at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:119)
    at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:118)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:110)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(Wrapper.android.kt:158)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(Wrapper.android.kt:157)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:157)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:142)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:78)
    at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3340)
    at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3273)
    at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:588)
    at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:1013)
    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:21291)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3491)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3498)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3498)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3498)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3498)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3498)
    at android.view.AttachInfo_Accessor.setAttachInfo(AttachInfo_Accessor.java:54)
    at com.android.layoutlib.bridge.impl.RenderSessionImpl.inflate(RenderSessionImpl.java:372)
    at com.android.layoutlib.bridge.Bridge.createSession(Bridge.java:450)
    at com.android.tools.idea.layoutlib.LayoutLibrary.createSession(LayoutLibrary.java:122)
    at com.android.tools.rendering.RenderTask.createRenderSession(RenderTask.java:742)
    at com.android.tools.rendering.RenderTask.lambda$inflate$7(RenderTask.java:889)
    at com.android.tools.rendering.RenderExecutor$runAsyncActionWithTimeout$3.run(RenderExecutor.kt:202)
    at com.android.tools.rendering.RenderExecutor$PriorityRunnable.run(RenderExecutor.kt:316)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)

MyAppWidget.kt Full Code

import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import androidx.glance.layout.Column
import androidx.glance.text.Text
import androidx.glance.Button
import androidx.glance.action.actionStartActivity
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.padding
import androidx.glance.text.Text

/* Import Glance Composables
 In the event there is a name clash with the Compose classes of the same name,
 you may rename the imports per https://kotlinlang.org/docs/packages.html#imports
 using the `as` keyword.
*/
class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // Load data needed to render the AppWidget.
        // Use `withContext` to switch to another thread for long running
        // operations.

        provideContent {
            // create your AppWidget here
            MyContent()
        }
    }
}


@Composable
private fun MyContent() {
    Column(
        modifier = GlanceModifier.fillMaxSize(),
        verticalAlignment = Alignment.Top,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
        Row(horizontalAlignment = Alignment.CenterHorizontally) {
            Button(
                text = "Home",
                onClick = actionStartActivity<MainActivity>()
            )
            Button(

                text = "Work",
                onClick = actionStartActivity<MainActivity>()
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun ContentPreview() {
    MyContent()
}

Upvotes: 4

Views: 2702

Answers (1)

istrocode
istrocode

Reputation: 81

This feature is fully supported in Android Studio Koala Feature Drop | 2024.1.2

You just need to use glance 1.1.0-rc01 or newer

implementation("androidx.glance:glance:1.1.0-rc01")
implementation("androidx.glance:glance-appwidget:1.1.0-rc01")
implementation("androidx.glance:glance-appwidget-preview:1.0-rc01")
implementation("androidx.glance:glance-preview:1.0-rc01")

And annotate your function with (example of dimensions)

@OptIn(ExperimentalGlancePreviewApi::class)
@Preview(widthDp = 200, heightDp = 150)

Then you will see your widget preview when navigating to your GlanceAppWidget class and opening the split screen or design view.

Upvotes: 2

Related Questions