Search code examples
androidandroid-jetpack-composeandroid-viewbinding

@composable invocations can only happen from the context of an @composable function for Composable with LaunchedEffect and AndroidViewBinding


I have a composable function

@Composable
fun SomeComposeView(){
     
    AndroidViewBinding(SomefragactBinding::inflate) {
        val myFragment = fragmentContainerView.getFragment<SomeFragment>()
      myFragment.somefunction()  
   }

I want to call it only once so I am trying this in my Activity

binding.SomeComposeViewLayout.setContent {
             OwnTheme() {
                 LaunchedEffect(Unit){
                    SomeComposeView()
                }
            }
        }
    }

I have a function inside the fragment like myFragment.somefunction() It gets called twice, I want to ensure its called only once so I am trying to use LaunchedEffect how can we call the function only once

However I am getting the error @composable invocations can only happen from the context of an @composable function


Solution

  • The LaunchedEffect error

    You can't call a composable inside a LaunchedEffect. According to documentation, LaunchedEffect is meant to run suspend functions in the scope of a composable but not to render a composable.

    To avoid your error you simply have to remove the LaunchedEffect an call your composable directly:

    binding.SomeComposeViewLayout.setContent {
                 OwnTheme() {
                    
                        SomeComposeView()
                   
                }
            }
        }
    

    AndroidViewBinding interop

    The AndroidViewBinding composable has 3 parameters :

    • The inflater
    • A modifier
    • A callback on update.

    You placed the call of myFragment.somefunction()in the update block without any conditions so it will run at each recomposition.

    Solution 1: Using a state

    Create a boolean state that tells you if you need to call myFragment.somefunction()

    
    @Composable
    fun SomeComposeView(){
        
    var needToCallSomeFunction by remember { mutableStateOf(true) }
    
    AndroidViewBinding(SomefragactBinding::inflate) {
            val myFragment = fragmentContainerView.getFragment<SomeFragment>()
        if(needToCallSomeFunction) {
            myFragment.somefunction()
            needToCallSomeFunction = false
        }
       }
    
    }
    

    Solution 2: Call it in factory

    You could create the viewBinding in the factory and before returning it, using it to get your fragment and call your function :

        AndroidViewBinding(
            factory = { inflater, parent, attachToParent ->
                SomefragactBinding.inflate(inflater, parent, attachToParent).also {
                    it.fragmentContainerView.getFragment<SomeFragment>().somefunction()
                }
            }
        )
    
    

    Because it is in the factory, it will just run once