Android Killer
Android Killer

Reputation: 18499

How to add existing fragment from inside Compose

I want to open an existing fragment from compose or if i can add inside the same compose in full screen, that will also work. Also how to navigate from one existing fragment to another existing fragment from compose.

Upvotes: 17

Views: 19539

Answers (5)

Chris Arriola
Chris Arriola

Reputation: 1774

Our recommendation for using Fragments in Compose is documented here.

Specifically, you should use the AndroidViewBinding composable to inflate an XML containing a FragmentContainerView hosting the fragment you want to use in Compose. AndroidViewBinding has fragment-specific handling which is why you want to use this over AndroidView. Note that you need to have view binding enabled for this.

Example XML file:

<androidx.fragment.app.FragmentContainerView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/fragment_container_view"
  android:layout_height="match_parent"
  android:layout_width="match_parent"
  android:name="com.example.MyFragment" />

And then in your Composable function:

@Composable
fun FragmentInComposeExample() {
    AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
        val myFragment = fragmentContainerView.getFragment<MyFragment>()
        // ...
    }
}

Upvotes: 21

4gus71n
4gus71n

Reputation: 4083

Since version 1.8.1 there is a AndroidFragment composable similar to the AndroidView composable to do this kind of thing.

https://developer.android.com/jetpack/androidx/releases/fragment#1.8.0

Example:

AndroidFragment(clazz = SomeExampleFragment::class.java)

As simple as the above, and you have other parameters in there to pass whatever bundle arguments you need or if you need to do something when it updates through the FragmentManager.

Here you have a few more examples:

https://github.com/search?q=AndroidFragment++language%3AKotlin&type=code

Upvotes: 7

bronze man
bronze man

Reputation: 1607

Document

I solved this as following step:

  • you activity need extends AppCompatActivity to use fragment in the activity.
  • you themes need set parent as Theme.MaterialComponents.DayNight.NoActionBar in res/values/themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="Theme.xxx" parent="Theme.MaterialComponents.DayNight.NoActionBar"/>
</resources>
  • notice the android:theme in your activity in your AndroidManifest.xml
    <activity android:name="com.xxx"
            android:exported="false"
            android:theme="@style/Theme.xxx"
            />
  • add appcompat to your build.gradle

     implementation 'androidx.appcompat:appcompat:1.6.1'
    
  • In compose

      AndroidView(factory = cb@{ context->
          val view = FragmentContainerView(context)
          view.id = ViewCompat.generateViewId()
          val selfId = view.id
          supportFragmentManager.commit {
              setReorderingAllowed(true)
              add<MyFragment>(selfId)
          }
          return@cb view
      })
    
  • full activity kt file

      package com.xxx
    
      import android.os.Bundle
      import androidx.activity.compose.setContent
      import androidx.appcompat.app.AppCompatActivity
      import androidx.compose.ui.viewinterop.AndroidView
      import androidx.core.view.ViewCompat
      import androidx.fragment.app.FragmentContainerView
      import androidx.fragment.app.commit
      import androidx.fragment.app.add
      import com.xxx.MyFragment
    
      class demoFragmentActivity: AppCompatActivity() {
          override fun onCreate(savedInstanceState: Bundle?){
              super.onCreate(savedInstanceState)
              setContent{
                  AndroidView(factory = cb@{ context->
                      val view = FragmentContainerView(context)
                      view.id = ViewCompat.generateViewId()
                      val selfId = view.id
                      supportFragmentManager.commit {
                          setReorderingAllowed(true)
                          add<MyFragment>(selfId)
                      }
                      return@cb view
                  })
              }
          }
      }
    

Upvotes: 6

Mike
Mike

Reputation: 580

This helped me:

@Composable
fun MyFragmentView(
    fragmentManager: FragmentManager,
    modifier: Modifier = Modifier
) {
    AndroidView(
        modifier = modifier.fillMaxSize(),
        factory = { context ->
            val containerId = R.id.container // some unique id
            val fragmentContainerView = FragmentContainerView(context).apply {
                id = containerId
            }

            val fragment = MyFragment()
            fragmentManager.beginTransaction()
                .replace(containerId, fragment, fragment.javaClass.simpleName)
                .commitAllowingStateLoss()

            fragmentContainerView
        }
    )
}

ids.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="container" type="id"/>
</resources>

Upvotes: 1

Hylke
Hylke

Reputation: 705

I solved this as following:

@Composable
fun ReusableFragmentComponent(
    someArgumentForFragment: FragmentArgument,
    fragmentManager: FragmentManager,
    modifier: Modifier = Modifier,
    tag: String = "ReusableFragmentTag"
) {
    AndroidView(
        modifier = modifier,
        factory = { context ->
            FrameLayout(context).apply {
                id = ViewCompat.generateViewId()
            }
        },
        update = {
            val fragmentAlreadyAdded = fragmentManager.findFragmentByTag(tag) != null

            if (!fragmentAlreadyAdded) {
                fragmentManager.commit {
                    add(it.id, ReusableFragment.newInstance(someArgumentForFragment), tag)
                }
            }
        }
    )
}

In my case I called this from a fragment (hosted by navigation component), in an effort to make our reusable fragments compose compatible. I did that like so:

class ReusableFragments : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                Column {
                    Text("ReusableFragment 1")
                    ReusableFragmentComponent(someArgument1, childFragmentManager, tag = "ReusableFragmentTag1")

                    Text("ReusableFragment 2")
                    ReusableFragmentComponent(someArgument2, childFragmentManager, tag = "ReusableFragmentTag2")
                }
            }
        }
    }

}

By making the tag customizable it's possible to add multiple different instances of the same fragment to the same fragment manager.

Upvotes: 6

Related Questions