91

The documentation describes how to create UI 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?

Kishan Viramgama
  • 893
  • 1
  • 11
  • 23
Nurseyit Tursunkulov
  • 8,012
  • 12
  • 44
  • 78
  • 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. – Kaushik Burkule Dec 17 '19 at 06:10
  • 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 https://developer.android.com/guide/components/fragments – Nouman Shah Dec 17 '19 at 06:43
  • ok i give you a upvote now enjoy the code.. i have posted the code.. –  Dec 17 '19 at 06:47

5 Answers5

166

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.")
            }
        }
    }
}
veritas1
  • 8,740
  • 6
  • 27
  • 37
  • 13
    `setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)` in addition the composition strategy should be reviewed when doing this. https://developer.android.com/jetpack/compose/interop/interop-apis#compose-in-fragments – klenki Jan 17 '22 at 10:43
  • How to get the NavController in Fragment ? – Sam Jan 21 '22 at 03:23
21

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!")
                    }
                }
            }
        }
    }
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
20

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!")
}

Cristan
  • 12,083
  • 7
  • 65
  • 69
  • 88
    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. – Gabriel Vasconcelos Mar 04 '21 at 21:44
  • 5
    While this is possible it won't be feasible in apps with more than a couple screens. – Jason Crosby Aug 04 '21 at 02:21
  • @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. – Cristan Aug 04 '21 at 08:17
  • 2
    Putting all of your compose code in one activity would end up with an activity thousands of lines long. Maybe even more. – Jason Crosby Aug 04 '21 at 17:13
  • 6
    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. – Cristan Aug 05 '21 at 09:12
  • 1
    What about app logic (which you would usually put in ViewModels)? – BoD Aug 24 '21 at 13:13
  • App logic should remain in ViewModels: https://developer.android.com/jetpack/compose/state#viewmodel-state – Cristan Aug 24 '21 at 15:16
  • 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 Jun 30 '22 at 03:23
  • This seems like a promising approach I was not aware of. How would you do Hilt view model injection with this approach though? – Anonymous Jan 08 '23 at 17:14
  • Quite easily, in your composable screen constructor. like @Composable fun SplashScreen( navController: NavController, splashViewModel: SplashViewModel = hiltViewModel(), ) – galaxigirl Feb 09 '23 at 10:44
17

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
            }
        }
    }
 }
}
Nurseyit Tursunkulov
  • 8,012
  • 12
  • 44
  • 78
9

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")
}
Yevhen Railian
  • 451
  • 5
  • 5