How to use Compose inside Fragment?

26,531

Solution 1

setContent on ViewGroup is now deprecated.

The below is accurate as of Compose v1.0.0-alpha01.

For pure compose UI Fragment:

class ComposeUIFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return ComposeView(requireContext()).apply {
            setContent {
                Text(text = "Hello world.")
            }
        }
    }
}

For hybrid compose UI Fragment - add ComposeView to xml layout, then:

class ComposeUIFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_compose_ui, container, false).apply {
            findViewById<ComposeView>(R.id.composeView).setContent {
                Text(text = "Hello world.")
            }
        }
    }
}

Solution 2

You don't need Fragments with Compose. You can navigate to another screen without needing a Fragment or an Activity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            NavHost(navController, startDestination = "welcome") {
                composable("welcome") { WelcomeScreen(navController) }
                composable("secondScreen") { SecondScreen() }
            }
        }
    }
}

@Composable
fun WelcomeScreen(navController: NavController) {
    Column {
        Text(text = "Welcome!")
        Button(onClick = { navController.navigate("secondScreen") }) {
            Text(text = "Continue")
        }
    }
}

@Composable
fun SecondScreen() {
    Text(text = "Second screen!")
}

Solution 3

Found it:

class LoginFragment : Fragment() {

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    val fragmentView = inflater.inflate(R.layout.fragment_login, container, false)

    (fragmentView as ViewGroup).setContent {
        Hello("Jetpack Compose")
    }
    return fragmentView
}

@Composable
fun Hello(name: String) = MaterialTheme {
    FlexColumn {
        inflexible {
            // Item height will be equal content height
            TopAppBar( // App Bar with title
                title = { Text("Jetpack Compose Sample") }
            )
        }
        expanded(1F) {
            // occupy whole empty space in the Column
            Center {
                // Center content
                Text("Hello $name!") // Text label
            }
        }
    }
 }
}

Solution 4

With 1.0.x you can :

- Define a ComposeView in the xml-layout.

  • add a androidx.compose.ui.platform.ComposeView in your layout-xml files:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        android:orientation="vertical"
         ...>
    
        <TextView ../>
    
        <androidx.compose.ui.platform.ComposeView
            android:id="@+id/compose_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </LinearLayout>
    class ExampleFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            _binding = FragmentExampleBinding.inflate(inflater, container, false)
            val view = binding.root
            view.composeView.apply {
                // Dispose the Composition when viewLifecycleOwner is destroyed
                setViewCompositionStrategy(
                    DisposeOnLifecycleDestroyed(viewLifecycleOwner)
                )
                setContent {
                    // In Compose world
                    MaterialTheme {
                        Text("Hello Compose!")
                    }
                }
            }
            return view
        }
    
        /** ... */
    }

- Include a ComposeView directly in a fragment.

    class ExampleFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            return ComposeView(requireContext()).apply {
                // Dispose the Composition when viewLifecycleOwner is destroyed
                setViewCompositionStrategy(
                    DisposeOnLifecycleDestroyed(viewLifecycleOwner)
                )
    
                setContent {
                    MaterialTheme {
                        // In Compose world
                        Text("Hello Compose!")
                    }
                }
            }
        }
    }

Solution 5

On my mind if you want to use Jetpack Compose with fragments in a pretty way like this

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
) = contentView {
    Text("Hello world")
}

you can create you own extension functions for Fragments

fun Fragment.requireContentView(
    compositionStrategy: ViewCompositionStrategy = DisposeOnDetachedFromWindow,
    context: Context = requireContext(),
    content: @Composable () -> Unit
): ComposeView {
    val view = ComposeView(context)
    view.setViewCompositionStrategy(compositionStrategy)
    view.setContent(content)
    return view
}

fun Fragment.contentView(
    compositionStrategy: ViewCompositionStrategy = DisposeOnDetachedFromWindow,
    context: Context? = getContext(),
    content: @Composable () -> Unit
): ComposeView? {
    context ?: return null
    val view = ComposeView(context)
    view.setViewCompositionStrategy(compositionStrategy)
    view.setContent(content)
    return view
}

I like this approach because it looks similar to Activity's setContent { } extension

Also you can define another CompositionStrategy

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
) = contentView(DisposeOnLifecycleDestroyed(viewLifecycleOwner)) {
    Text("Hello world")
}
Share:
26,531

Related videos on Youtube

Nurseyit Tursunkulov
Author by

Nurseyit Tursunkulov

Updated on January 25, 2022

Comments

  • Nurseyit Tursunkulov
    Nurseyit Tursunkulov over 2 years

    The documentation describes how to create UI (Jetpack Compose https://developer.android.com/jetpack/compose) inside Activity.

    class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
    

    }

    But how can I use it inside fragment?

    • Kaushik Burkule
      Kaushik Burkule over 4 years
      Please edit your question to provide a minimal reproducible example. Up to now your question is kind of vague, making it hard to see what you are doing and where the problem is.
    • Nouman Shah
      Nouman Shah over 4 years
      if am not sure if i got you , use framelayout inside your activity the fragment have there own life cycle and xml file you can use that here is offical documentation link developer.android.com/guide/components/fragments
    • Admin
      Admin over 4 years
      ok i give you a upvote now enjoy the code.. i have posted the code..
  • oiyio
    oiyio over 3 years
    can you share the content of fragment_login.xml ?
  • Przemo
    Przemo over 3 years
    it's deprecated (tested on alpha12)
  • Gabriel Vasconcelos
    Gabriel Vasconcelos over 3 years
    While that is true, it does not invalidate the use case of having a fragment serving as the backbone, probably a common scenario for migrating codebases. So that does not answer the question.
  • Jason Crosby
    Jason Crosby almost 3 years
    While this is possible it won't be feasible in apps with more than a couple screens.
  • Cristan
    Cristan almost 3 years
    @JasonCrosby I don't see why you wouldn't use this in apps with a lot of screens. Yes, the NavHost will grow, but you can easily extract this to a separate file so your main activity will stay lean.
  • Jason Crosby
    Jason Crosby almost 3 years
    Putting all of your compose code in one activity would end up with an activity thousands of lines long. Maybe even more.
  • Cristan
    Cristan almost 3 years
    True. That's why in the real world, you shouldn't actually code like in my example. Each screen (like WelcomeScreen and SecondScreen) should be in its own file instead of in MainActivity.kt. Same applies for the navigation.
  • BoD
    BoD almost 3 years
    What about app logic (which you would usually put in ViewModels)?
  • Cristan
    Cristan almost 3 years
    App logic should remain in ViewModels: developer.android.com/jetpack/compose/state#viewmodel-state
  • Melvynx
    Melvynx over 2 years
    Thanks, the setViewCompositionStrategy resolve my problem
  • klenki
    klenki over 2 years
    setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroy‌​ed) in addition the composition strategy should be reviewed when doing this. developer.android.com/jetpack/compose/interop/…
  • Sam
    Sam over 2 years
    How to get the NavController in Fragment ?
  • EpicPandaForce
    EpicPandaForce almost 2 years
    This might apply if you're using navigation-compose, but the question was about Fragments. As Fragments, unlike Navigation-Compose, support screen transitions and type-safe argument passing, etc.
  • EpicPandaForce
    EpicPandaForce almost 2 years
    You can also use setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroy‌​ed)