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