Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Would it be possible to add a sample for the new ViewNode in Compose (in v2.1.0)? #456

Open
wilfredbtan opened this issue Apr 4, 2024 · 1 comment

Comments

@wilfredbtan
Copy link

wilfredbtan commented Apr 4, 2024

Previously, I would use the ViewRenderable to render an AndroidView. However, as ViewNode has changed significantly in v2.1.0, I am unable to get my AndroidView to show properly.

This is my old code:

// Dummy AndroidView
 val androidView =
      android.view.View(context)
          .apply {
              measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
              layout(0, 0, 10, 5)
              setBackgroundColor(Color.WHITE)
          }

ViewRenderable.builder()
    .setView(context, androidView)
    .build(engine)
    .thenAccept {
        val viewAttachmentManager = ViewAttachmentManager(context, sceneView)

        viewAttachmentManager.onResume()

        val childNode = ViewNode(
            engine = engine,
            modelLoader = modelLoader,
            viewAttachmentManager = viewAttachmentManager,
        ).apply {
            setRenderable(it)
        }

        childNodes.add(childNode)

Scene(
    modifier = Modifier.fillMaxSize(),
    engine = engine,
    modelLoader = modelLoader,
    cameraNode = cameraNode,
    childNodes = childNodes,
    view = view,
    // ...
)

This is my broken attempt at trying to render the a child node:

val viewNodeWindowManager = rememberViewNodeManager()
val materialLoader = rememberMaterialLoader(engine)

// ...
rememberNode {
    ViewNode(
        engine = engine,
        windowManager = viewNodeWindowManager,
        materialLoader = materialLoader,
        content = {
            Text(text = context.getString("Some text"))
        }
    )
    .also {
        childNodes.add(it)
    }
}


Scene(
    modifier = Modifier.fillMaxSize(),
    engine = engine,
    modelLoader = modelLoader,
    cameraNode = cameraNode,
    childNodes = childNodes,
    view = view,
    // ...
    viewNodeWindowManager = viewNodeWindowManager   // added
)

Running the above results in the following crash:

FATAL EXCEPTION: main
Process: com.wilfredbtan.choreographic, PID: 31606
java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.FrameLayout{68df235 V.E...... ......I. 0,0-0,0}
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer(WindowRecomposer.android.kt:352)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer$default(WindowRecomposer.android.kt:325)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion.LifecycleAware$lambda$0(WindowRecomposer.android.kt:168)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion.$r8$lambda$FWAPLXs0qWMqekhMr83xkKattCY(Unknown Source:0)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion$$ExternalSyntheticLambda0.createRecomposer(D8$$SyntheticClass:0)
at androidx.compose.ui.platform.WindowRecomposerPolicy.createAndInstallWindowRecomposer$ui_release(WindowRecomposer.android.kt:224)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:300)
at androidx.compose.ui.platform.AbstractComposeView.resolveParentCompositionContext(ComposeView.android.kt:244)
at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:251)
at androidx.compose.ui.platform.AbstractComposeView.onAttachedToWindow(ComposeView.android.kt:283)
at android.view.View.dispatchAttachedToWindow(View.java:22479)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3686)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3695)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3695)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3669)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:3116)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:10885)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1301)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1309)
at android.view.Choreographer.doCallbacks(Choreographer.java:923)
at android.view.Choreographer.doFrame(Choreographer.java:852)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1283)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8757)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

Could anyone guide me in the right direction to render a ViewNode properly in v2.1.0?

@wilfredbtan wilfredbtan changed the title Would it be possible to add a sample for the new ViewNode (in v2.1.0)? Would it be possible to add a sample for the new ViewNode in Compose (in v2.1.0)? Apr 10, 2024
@NikitaGvozdilko
Copy link

NikitaGvozdilko commented Apr 26, 2024

The problem is in ViewAttachmentManager. It adds FrameLayout to WindowManager and non of them have reference on LifecycleOwner.

Here is workaround I created temporary. Replace lib's ViewAttachmentManager on this one:

`import android.content.Context
import android.graphics.PixelFormat
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.google.ar.sceneform.rendering.ViewAttachmentManager

/**

  • Updated implementation of ViewAttachmentManager with access to frameLayout

  • Don't forget to add to lifecycle observers otherwise View will not be rendered
    */
    class ComposableViewAttachmentManager(
    context: Context,
    private val lifecycleOwner: LifecycleOwner,
    private val viewModelStoreOwner: ViewModelStoreOwner,
    private val savedStateRegistryOwner: SavedStateRegistryOwner,
    private val ownerView: View // Used to post callbacks onto the UI thread.
    ): ViewAttachmentManager(context, ownerView), DefaultLifecycleObserver {
    private val windowManager: WindowManager
    private val windowLayoutParams: WindowManager.LayoutParams
    private val frameLayout: FrameLayout
    private val viewLayoutParams: ViewGroup.LayoutParams

    init {
    windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    windowLayoutParams = createWindowLayoutParams()
    frameLayout = FrameLayout(context)
    viewLayoutParams = createViewLayoutParams()

     frameLayout.setViewTreeLifecycleOwner(lifecycleOwner)
     frameLayout.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
     frameLayout.setViewTreeViewModelStoreOwner(viewModelStoreOwner)
    

    }

    override fun onResume(owner: LifecycleOwner) {
    onResume()
    }

    override fun onPause(owner: LifecycleOwner) {
    onPause()
    }

    override fun onResume() {
    // A ownerView can only be added to the WindowManager after the activity has finished resuming.
    // Therefore, we must use post to ensure that the window is only added after resume is finished.
    ownerView.post {
    if (frameLayout.parent == null && ownerView.isAttachedToWindow) {
    windowManager.addView(frameLayout, windowLayoutParams)
    }
    }
    }

    override fun onPause() {
    // The ownerView must be removed from the WindowManager before the activity is destroyed, or the
    // window will be leaked. Therefore we add/remove the ownerView in resume/pause.
    if (frameLayout.parent != null) {
    windowManager.removeView(frameLayout)
    }
    }

    /**

    • Add a ownerView as a child of the [FrameLayout] that is attached to the [ ].
    • Used by [RenderViewToExternalTexture] to ensure that the ownerView is drawn with all
    • appropriate lifecycle events being called correctly.
      */
      override fun addView(view: View) {
      if (view.parent === frameLayout) {
      return
      }
      frameLayout.addView(view, viewLayoutParams)
      }

    /**

    • Remove a ownerView from the [FrameLayout] that is attached to the [WindowManager].
    • Used by [RenderViewToExternalTexture] to remove ownerView's that no longer need to be
    • drawn.
      */
      override fun removeView(view: View) {
      if (view.parent !== frameLayout) {
      return
      }
      frameLayout.removeView(view)
      }

    companion object {
    private const val VIEW_RENDERABLE_WINDOW = "ViewRenderableWindow"
    private fun createWindowLayoutParams(): WindowManager.LayoutParams {
    val params = WindowManager.LayoutParams(
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
    or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    PixelFormat.TRANSLUCENT
    )
    params.title = VIEW_RENDERABLE_WINDOW
    return params
    }

     private fun createViewLayoutParams(): ViewGroup.LayoutParams {
         return ViewGroup.LayoutParams(
             ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
         )
     }
    

    }
    }
    `

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants