1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.navigation.fragment.compose 18 19 import android.os.Bundle 20 import android.view.LayoutInflater 21 import android.view.View 22 import android.view.ViewGroup 23 import androidx.compose.runtime.CompositionLocalProvider 24 import androidx.compose.runtime.currentComposer 25 import androidx.compose.runtime.reflect.getDeclaredComposableMethod 26 import androidx.compose.ui.platform.ComposeView 27 import androidx.compose.ui.platform.ViewCompositionStrategy 28 import androidx.core.os.bundleOf 29 import androidx.fragment.app.Fragment 30 31 /** 32 * This class provides a [Fragment] wrapper around a composable function that is loaded via 33 * reflection. The composable function has access to this fragment instance via [LocalFragment]. 34 * 35 * This class is constructed via a factory method: make sure you add `import 36 * androidx.navigation.fragment.compose.ComposableFragment.Companion.ComposableFragment` 37 */ 38 public class ComposableFragment internal constructor() : Fragment() { 39 <lambda>null40 private val composableMethod by lazy { 41 val arguments = requireArguments() 42 val fullyQualifiedName = 43 checkNotNull(arguments.getString(FULLY_QUALIFIED_NAME)) { 44 "Instances of ComposableFragment must be created with the factory function " + 45 "ComposableFragment(fullyQualifiedName)" 46 } 47 val (className, methodName) = fullyQualifiedName.split("$") 48 val clazz = Class.forName(className) 49 clazz.getDeclaredComposableMethod(methodName) 50 } 51 onCreateViewnull52 override fun onCreateView( 53 inflater: LayoutInflater, 54 container: ViewGroup?, 55 savedInstanceState: Bundle? 56 ): View { 57 // Consider using Fragment.content from fragment-compose once it is stable 58 return ComposeView(requireContext()).apply { 59 setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) 60 setContent { 61 CompositionLocalProvider(LocalFragment provides this@ComposableFragment) { 62 composableMethod.invoke(currentComposer, null) 63 } 64 } 65 } 66 } 67 68 public companion object { 69 internal const val FULLY_QUALIFIED_NAME = 70 "androidx.navigation.fragment.compose.FULLY_QUALIFIED_NAME" 71 72 /** 73 * Creates a new [ComposableFragment] instance that will wrap the Composable method loaded 74 * via reflection from [fullyQualifiedName]. 75 * 76 * @param fullyQualifiedName the fully qualified name of the static, no argument Composable 77 * method that this fragment should display. It should be formatted in the format 78 * `com.example.NameOfFileKt/$MethodName`. 79 */ 80 @JvmStatic ComposableFragmentnull81 public fun ComposableFragment(fullyQualifiedName: String): ComposableFragment { 82 return ComposableFragment().apply { 83 arguments = bundleOf(FULLY_QUALIFIED_NAME to fullyQualifiedName) 84 } 85 } 86 } 87 } 88