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.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/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.
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