ComposableFragment.kt

/*
 * 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.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.
 *
 * 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 {
                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)
            }
        }
    }
}