最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

android - findNavController().navigate from composable in fragment leads to exception - Stack Overflow

programmeradmin0浏览0评论

I have a normal fragment subclass that used to be based on views & used viewbinding. I'm migrating to compose slowly so I've basically removed the view-related code and now use the onCreateView() to set the fragment's content to a composable.

Here is the code:

class LoginFragment : BaseFragment() {
    override val layoutRes: Int = R.layout.fragment_login
    private val binding: FragmentLoginBinding by viewBinding()

    private val viewModel: LoginViewModel by viewModel()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                AppTheme {
                    LoginContentPortrait()
                }
            }
        }
    }

    @OptIn(ExperimentalLayoutApi::class)
    @Composable
    private fun LoginContentPortrait(modifier: Modifier = Modifier) {
        val uiState by viewModel.uiState.collectAsState()
        val configuration = LocalConfiguration.current
        val isInPortraitMode by remember {
            derivedStateOf { configuration.orientation == Configuration.ORIENTATION_PORTRAIT }
        }

        viewModel.subscribeToEvents().collectWithLifecycle(viewLifecycleOwner) { event ->
            when (event) {
                is UIEvent.Navigation.Register -> {
                    findNavController().navigate(
                        LoginFragmentDirections.actionLoginFragmentToRegisterFragment()
                    )
                }

                (other similar cases)
            }
        }

        ...

        Column(
            Modifier
                .fillMaxSize()
                .padding(
                    start = 24.dp,
                    end = 24.dp,
                    bottom = 16.dp,
                    top = if (isInPortraitMode) 96.dp else 16.dp
                )
                .verticalScroll(rememberScrollState()),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Row {
                Column(
                    Modifier
                        .weight(0.5f)
                        .then(modifier),
                    horizontalAlignment = Alignment.CenterHorizontally,
                ) {
                    ...(rest of the UI layout)...
                SecondaryButton(
                    modifier = Modifier.fillMaxWidth(if (isInPortraitMode) 1f else 0.3f),
                    borderStroke = BorderStroke(1.dp, colorResource(CoreR.color.core_dark20)),
                    onClick = {
                        viewModel.navigateRegister()
                    }
                ) {
                    ButtonLabel(
                        text = stringResource(R.string.auth_createAccount),
                        textColor = colorResource(CoreR.color.core_black)
                    )
                }
            }
        }
    }
}

As you can see, once the register button is pressed, it calls the viewmodel which then emits the respective event that is then caught by the fragment. Now, once the findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToRegisterFragment()) runs, the app crashes with an IllegalStateException stating that the destination can't be found.

What confuses me is that in my nav.xml file all destinations are defined properly so they should be visible:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android=";
    xmlns:app=";
    xmlns:tools=";
    android:id="@+id/nav_auth"
    app:startDestination="@id/splashScreenFragment">
    <fragment
        android:id="@+id/loginFragment"
        android:name="package.LoginFragment"
        android:label="LoginFragment"
        tools:layout="@layout/fragment_login">
        <action
            android:id="@+id/action_loginFragment_to_registerFragment"
            app:destination="@id/registerFragment"
            app:enterAnim="@anim/nav_default_enter_anim" />
        ...
    </fragment>
 <fragment
        android:id="@+id/registerFragment"
        android:name="package.RegisterFragment"
        android:label="RegisterFragment"
        tools:layout="@layout/fragment_register">
        ...
    </fragment>
</navigation>

The RegisterFragment is also a subclass of BaseFragment but doesn't use Compose at all as of right now. Any ideas what I'm missing?

Exception:

java.lang.IllegalArgumentException: Navigation action/destination package:id/action_loginFragment_to_registerFragment cannot be found from the current destination Destination(package:id/registerFragment) label=RegisterFragment class=package.RegisterFragment

P.S. I have checked the various seemingly similar posts here about similar issues but none worked for my case or seemed to cover this exact case, hence why I'm posting.

I have a normal fragment subclass that used to be based on views & used viewbinding. I'm migrating to compose slowly so I've basically removed the view-related code and now use the onCreateView() to set the fragment's content to a composable.

Here is the code:

class LoginFragment : BaseFragment() {
    override val layoutRes: Int = R.layout.fragment_login
    private val binding: FragmentLoginBinding by viewBinding()

    private val viewModel: LoginViewModel by viewModel()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                AppTheme {
                    LoginContentPortrait()
                }
            }
        }
    }

    @OptIn(ExperimentalLayoutApi::class)
    @Composable
    private fun LoginContentPortrait(modifier: Modifier = Modifier) {
        val uiState by viewModel.uiState.collectAsState()
        val configuration = LocalConfiguration.current
        val isInPortraitMode by remember {
            derivedStateOf { configuration.orientation == Configuration.ORIENTATION_PORTRAIT }
        }

        viewModel.subscribeToEvents().collectWithLifecycle(viewLifecycleOwner) { event ->
            when (event) {
                is UIEvent.Navigation.Register -> {
                    findNavController().navigate(
                        LoginFragmentDirections.actionLoginFragmentToRegisterFragment()
                    )
                }

                (other similar cases)
            }
        }

        ...

        Column(
            Modifier
                .fillMaxSize()
                .padding(
                    start = 24.dp,
                    end = 24.dp,
                    bottom = 16.dp,
                    top = if (isInPortraitMode) 96.dp else 16.dp
                )
                .verticalScroll(rememberScrollState()),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Row {
                Column(
                    Modifier
                        .weight(0.5f)
                        .then(modifier),
                    horizontalAlignment = Alignment.CenterHorizontally,
                ) {
                    ...(rest of the UI layout)...
                SecondaryButton(
                    modifier = Modifier.fillMaxWidth(if (isInPortraitMode) 1f else 0.3f),
                    borderStroke = BorderStroke(1.dp, colorResource(CoreR.color.core_dark20)),
                    onClick = {
                        viewModel.navigateRegister()
                    }
                ) {
                    ButtonLabel(
                        text = stringResource(R.string.auth_createAccount),
                        textColor = colorResource(CoreR.color.core_black)
                    )
                }
            }
        }
    }
}

As you can see, once the register button is pressed, it calls the viewmodel which then emits the respective event that is then caught by the fragment. Now, once the findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToRegisterFragment()) runs, the app crashes with an IllegalStateException stating that the destination can't be found.

What confuses me is that in my nav.xml file all destinations are defined properly so they should be visible:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android/apk/res/android"
    xmlns:app="http://schemas.android/apk/res-auto"
    xmlns:tools="http://schemas.android/tools"
    android:id="@+id/nav_auth"
    app:startDestination="@id/splashScreenFragment">
    <fragment
        android:id="@+id/loginFragment"
        android:name="package.LoginFragment"
        android:label="LoginFragment"
        tools:layout="@layout/fragment_login">
        <action
            android:id="@+id/action_loginFragment_to_registerFragment"
            app:destination="@id/registerFragment"
            app:enterAnim="@anim/nav_default_enter_anim" />
        ...
    </fragment>
 <fragment
        android:id="@+id/registerFragment"
        android:name="package.RegisterFragment"
        android:label="RegisterFragment"
        tools:layout="@layout/fragment_register">
        ...
    </fragment>
</navigation>

The RegisterFragment is also a subclass of BaseFragment but doesn't use Compose at all as of right now. Any ideas what I'm missing?

Exception:

java.lang.IllegalArgumentException: Navigation action/destination package:id/action_loginFragment_to_registerFragment cannot be found from the current destination Destination(package:id/registerFragment) label=RegisterFragment class=package.RegisterFragment

P.S. I have checked the various seemingly similar posts here about similar issues but none worked for my case or seemed to cover this exact case, hence why I'm posting.

Share Improve this question edited Feb 4 at 12:19 Stelios Papamichail asked Feb 3 at 12:38 Stelios PapamichailStelios Papamichail 1,2904 gold badges27 silver badges70 bronze badges 11
  • 1 Hi, could you add please all the stacktrace about IllegalStateException – Yurii Commented Feb 3 at 13:29
  • @Yurii just added it, done! – Stelios Papamichail Commented Feb 4 at 12:19
  • I fot to mention, could you please add the versions of the libraries you use in the project? (navigation library) – Yurii Commented Feb 4 at 12:31
  • @Yurii I'm using version 2.6.0 for navigation-fragment, navigation-ui-ktx and 2.5.3 for safeArgs. – Stelios Papamichail Commented Feb 4 at 12:40
  • 1 Correct. Try to avoid subscribing to some event on the composition body. – Yurii Commented Feb 4 at 14:06
 |  Show 6 more comments

1 Answer 1

Reset to default 2

The main issue in the current question is recomposition during handling the click button state. Better to keep handling using the Side-Effect mechanism: Side Effects. Also here the additional source to read about handling user interactions: User Interactions

发布评论

评论列表(0)

  1. 暂无评论