blob: a77097f584623cf796376ca3c9aa14d89866bc05 [file] [log] [blame] [edit]
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.navigation.fragment.compose
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.reflect.getDeclaredComposableMethod
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
/**
* This class provides a [Fragment] wrapper around a composable function that is loaded via
* reflection. The composable function has access to this fragment instance via [LocalFragment].
*
* This class is constructed via a factory method: make sure you add `import
* androidx.navigation.fragment.compose.ComposableFragment.Companion.ComposableFragment`
*/
class ComposableFragment internal constructor() : Fragment() {
private val composableMethod by lazy {
val arguments = requireArguments()
val fullyQualifiedName =
checkNotNull(arguments.getString(FULLY_QUALIFIED_NAME)) {
"Instances of ComposableFragment must be created with the factory function " +
"ComposableFragment(fullyQualifiedName)"
}
val (className, methodName) = fullyQualifiedName.split("$")
val clazz = Class.forName(className)
clazz.getDeclaredComposableMethod(methodName)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Consider using Fragment.content from fragment-compose once it is stable
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
CompositionLocalProvider(LocalFragment provides this@ComposableFragment) {
composableMethod.invoke(currentComposer, null)
}
}
}
}
companion object {
internal const val FULLY_QUALIFIED_NAME =
"androidx.navigation.fragment.compose.FULLY_QUALIFIED_NAME"
/**
* Creates a new [ComposableFragment] instance that will wrap the Composable method loaded
* via reflection from [fullyQualifiedName].
*
* @param fullyQualifiedName the fully qualified name of the static, no argument Composable
* method that this fragment should display. It should be formatted in the format
* `com.example.NameOfFileKt/$MethodName`.
*/
@JvmStatic
fun ComposableFragment(fullyQualifiedName: String): ComposableFragment {
return ComposableFragment().apply {
arguments = bundleOf(FULLY_QUALIFIED_NAME to fullyQualifiedName)
}
}
}
}