The scenario is when the app is on Fragment A and then kept the app in the background for some time. After resuming from the background the initial state of the app is in Fragment A but then suddenly app restarts from startDestination in the navigation component causing the below crash.
Fatal Exception: java.lang.IllegalArgumentException: Navigation action/destination com.app.gulfcraftv2:id/action_splashFragment_to_nav_home cannot be found from the current destination Destination(com.app.gulfcraftv2:id/companyFragment) class=com.precise.gulfcraft.ui.companydetail.CompanyDetailFragment
at androidx.navigation.NavController.navigate(NavController.kt:1540)
at androidx.navigation.NavController.navigate(NavController.kt:1472)
at androidx.navigation.NavController.navigate(NavController.kt:1930)
at com.precise.gulfcraft.ui.splash.SplashFragment.onCreate$lambda-6(SplashFragment.kt:75)
at com.precise.gulfcraft.ui.splash.SplashFragment.lambda$Aso8YI02DbFhfWptH-qrBUU0ulU()
at com.precise.gulfcraft.ui.splash.-$$Lambda$SplashFragment$Aso8YI02DbFhfWptH-qrBUU0ulU.run(:2)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7562)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
Note - The launch mode used is singleTop
The crash is happening in the below section.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
Handler(Looper.getMainLooper()).postDelayed({
findNavController().navigate(SplashFragmentDirections.actionSplashFragmentToNavHome())
}, 3000)
} catch (e: Exception) {
}
}
check first if this is your current destination with following function
fun navigate(destination: NavDirections) = with(findNavController()) {
currentDestination?.getAction(destination.actionId)
?.let { navigate(destination) }
}
Related
I have dialog fragment containes list of users so When my dialog fragment is showing and I change let's say the language or activate or disactivate dark mode or any other config change my dialog fragment dissmiss and lose state
so this how i create the dialog :
CheckableContactsDialog.newInstance(object : CheckableContactListener {
override fun onPositiveClick(selected: MutableList<String>) {
checkableContactsDialog?.dismiss()
if (mVCall != null) {
val (phones, phoneFlat) = extractPhonesNumberFromContacts(
context, selected
)
if (phones.isEmpty()) {
return
}
inviteNewUsers(phoneFlat)
}
}
override fun onNegativeClick() {
checkableContactsDialog?.dismiss()
}
})
After configchange like language change or activate or desactivate dark mode and trigger the onPositiveClick event and checkableContactsDialog?.dismiss() called this error appear
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.zetabox.totrapp, PID: 4368
java.lang.IllegalStateException: Fragment CheckableContactsDialog{58e7d9c} (77fb6c1e-6986-4eb0-8e35-c1ee51f4e0c9) not associated with a fragment manager.
at androidx.fragment.app.Fragment.getParentFragmentManager(Fragment.java:1040)
at androidx.fragment.app.DialogFragment.dismissInternal(DialogFragment.java:350)
at androidx.fragment.app.DialogFragment.dismiss(DialogFragment.java:307)
at com.google.android.material.bottomsheet.BottomSheetDialogFragment.dismiss(BottomSheetDialogFragment.java:47)
at com.zetabox.totrapp.ui.call.CallActivity$showAddUserDialog$1.onPositiveClick(CallActivity.kt:2123)
at com.zetabox.totrapp.ui.dialogs.CheckableContactsDialog.init$lambda-7(CheckableContactsDialog.kt:231)
at com.zetabox.totrapp.ui.dialogs.CheckableContactsDialog.$r8$lambda$BXYvl1NNrhMJYwo5PbYyZJ1LQtM(Unknown Source:0)
at com.zetabox.totrapp.ui.dialogs.CheckableContactsDialog$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7171)
at android.view.View.performClickInternal(View.java:7148)
at android.view.View.access$3500(View.java:802)
at android.view.View$PerformClick.run(View.java:27409)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7650)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:503)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
I have an activity which loads a list usign paging 3.
I use a channel to emit search text and then consume it as flow to apply transformations to get a flow of List of items
when the app under goes a configuration change I get the following error
ReceiveChannel.consumeAsFlow can be collected just once
Here is my psuedo code
fun onCreate() {
lifecycleScope.launch {
viewModel.skuList.collectLatest {
adapter.submitData(it)
}
}
}
In viewModel
val skuList = _searchChannel.consumeAsFlow().flatMapLatest {
skuRepository.fetchSku(it.activeOrderId, it.supplierId,
it.name, it.fetchDiscounted, it.skuCategoryIdCombinedText)
}
}
Fatal Exception: java.lang.IllegalStateException: ReceiveChannel.consumeAsFlow can be collected just once
at kotlinx.coroutines.flow.ChannelAsFlow.markConsumed(Channels.kt:130)
at kotlinx.coroutines.flow.ChannelAsFlow.collect(Channels.kt:153)
at kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3.invokeSuspend(Merge.kt:101)
at kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3.invoke(:8)
at kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3.invoke(:4)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
at kotlinx.coroutines.flow.internal.FlowCoroutineKt.flowScope(FlowCoroutine.kt:33)
at kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest.flowCollect(Merge.kt:25)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collectTo$suspendImpl(ChannelFlow.kt:157)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collectTo()
at kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun$1.invokeSuspend(ChannelFlow.kt:60)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:375)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(:1)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
at kotlinx.coroutines.BuildersKt.launch$default(:1)
at com.awantunai.app.home.cart.ordering.add_to_cart.AddToCartV2Activity.setupRecyclerView(AddToCartV2Activity.kt:272)
at com.awantunai.app.home.cart.ordering.add_to_cart.AddToCartV2Activity.initViews(AddToCartV2Activity.kt:207)
at com.awantunai.app.home.cart.ordering.add_to_cart.AddToCartV2Activity.onCreate(AddToCartV2Activity.kt:105)
at android.app.Activity.performCreate(Activity.java:8054)
at android.app.Activity.performCreate(Activity.java:8034)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1341)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3688)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3864)
at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:5811)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:5703)
at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:71)
at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2253)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7870)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
You should be using repeatOnLifeCycle in order to keep collecting/emiting to your UI after a configuration change
// Create a new coroutine since repeatOnLifecycle is a suspend function
lifecycleScope.launch {
// The block passed to repeatOnLifecycle is executed when the lifecycle
// is at least STARTED and is cancelled when the lifecycle is STOPPED.
// It automatically restarts the block when the lifecycle is STARTED again.
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Safely collect from skuList when the lifecycle is STARTED
// and stops collection when the lifecycle is STOPPED
lviewModel.skuList.collectLatest {
// New skuList
}
}
}
You can also take a look at flowWithLifecycle
As a workaround used receiveAsFlow instead of consumeAsFlow
I have a MainActivity using a DrawerLayout and tabs with 2 fragments.
My first fragment contains a list of elements in a RecyclerView, and I can click on each element to "select" it (which calls a SDK function to login to a hardware device). When selected, this triggers a change on the Fragment's ViewModel:
// Selected device changes when an item is clicked
private val _devices = MutableLiveData<List<DeviceListItemViewModel>>()
private val _selectedDevice = MutableLiveData<ConnectedDevice>()
val devices: LiveData<List<DeviceListItemViewModel>> by this::_devices
val selectedDevice: LiveData<ConnectedDevice> by this::_selectedDevice
Then I have a shared ViewModel between both fragments, which also has a currentDevice variable like this:
private val _currentDevice = MutableLiveData<ConnectedDevice>()
val currentDevice: LiveData<ConnectedDevice> by this::_currentDevice
So in the Fragment that contains the list, I have the following code to update the shared ViewModel variable:
private val mViewModel: DeviceManagementViewModel by viewModels()
private val mSharedViewModel: MainActivityViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Log.d(classTag, "Fragment view created")
val binding = ActivityMainDevicesManagementFragmentBinding.bind(view)
binding.apply {
viewModel = mViewModel
lifecycleOwner = viewLifecycleOwner
}
// Observe fragment ViewModel
// If any device is clicked on the list, do the login on the shared ViewModel
mViewModel.selectedDevice.observe(this, {
mSharedViewModel.viewModelScope.launch {
if (it != null) {
mSharedViewModel.setCurrentDevice(videoDevice = it)
} else mSharedViewModel.unsetCurrentDevice()
}
})
}
The problem is that if the shared ViewModel's currentDevice variable is set, I get exceptions whenever I try to open a Dialog or start a new activity. If I modify the setCurrentDevice function in the shared ViewModel, then it works fine (or if I don't select any device).
The exceptions I see are this when starting a new Activity:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.placeholder.easyview/com.example.myapp.activities.settings.SettingsActivity}: java.lang.IllegalArgumentException: display must not be null
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3430)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3594)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2067)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7698)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952)
Caused by: java.lang.IllegalArgumentException: display must not be null
at android.app.ContextImpl.createDisplayContext(ContextImpl.java:2386)
at android.content.ContextWrapper.createDisplayContext(ContextWrapper.java:977)
at com.android.internal.policy.DecorContext.<init>(DecorContext.java:50)
at com.android.internal.policy.PhoneWindow.generateDecor(PhoneWindow.java:2348)
at com.android.internal.policy.PhoneWindow.installDecor(PhoneWindow.java:2683)
at com.android.internal.policy.PhoneWindow.getDecorView(PhoneWindow.java:2116)
at androidx.appcompat.app.AppCompatActivity.initViewTreeOwners(AppCompatActivity.java:219)
at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:194)
at com.example.myapp.activities.settings.SettingsActivity.onCreate(SettingsActivity.kt:34)
at android.app.Activity.performCreate(Activity.java:8000)
at android.app.Activity.performCreate(Activity.java:7984)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1310)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3403)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3594)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2067)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7698)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952)
And this if I try to open a Dialog:
java.lang.ArrayIndexOutOfBoundsException: length=16; index=2448
at android.view.InsetsState.peekSource(InsetsState.java:374)
at android.view.InsetsSourceConsumer.updateSource(InsetsSourceConsumer.java:291)
at android.view.InsetsController.updateState(InsetsController.java:654)
at android.view.InsetsController.onStateChanged(InsetsController.java:621)
at android.view.ViewRootImpl.setView(ViewRootImpl.java:1058)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:409)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:110)
at android.app.Dialog.show(Dialog.java:340)
at android.app.AlertDialog$Builder.show(AlertDialog.java:1131)
at com.example.myapp.activities.main.fragments.DeviceManagementFragment.showAddDeviceMethodDialog(DeviceManagementFragment.kt:151)
at com.example.myapp.activities.main.fragments.DeviceManagementFragment.access$showAddDeviceMethodDialog(DeviceManagementFragment.kt:33)
at com.example.myapp.activities.main.fragments.DeviceManagementFragment$onViewCreated$$inlined$apply$lambda$1.onClick(DeviceManagementFragment.kt:52)
at android.view.View.performClick(View.java:7448)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28309)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7698)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952)
EDIT: Looks like the problem actually lies in the other fragment, where I have the following code (in onViewCreated method):
// If the shared view model device changes, this must change too
mSharedViewModel.currentDevice.observe(this, {
if (it != null) {
mViewModel.setCurrentDevice(it)
} else mViewModel.unsetCurrentDevice()
})
mViewModel.currentDevice.observe(this, {
if (it != null) {
mViewModel.fetchChannels()
}
})
If I comment out the second part (where the fetchChannels occurs), it works well. Even if I comment out the fetchChannels call only, it works.
This is the code of the fetchChannels function:
fun fetchChannels() = viewModelScope.launch {
Log.d(classTag, "Getting channels for device ${currentDevice.value}")
currentDevice.value?.let {
val fetchedChannels = deviceLibManager.getChannels(it.videoDevice)
_currentDevice.value?.channels?.clear()
_currentDevice.value?.channels?.addAll(fetchedChannels)
if (fetchedChannels.isNotEmpty()) {
_currentDevice.value?.currentChannel = fetchedChannels[0]
}
}
}
The following line is the one giving me trouble:
val fetchedChannels = deviceLibManager.getChannels(it.videoDevice)
That function is just this:
suspend fun getChannels(videoDevice: VideoDevice): List<VideoChannel> {
try {
Log.i(classTag, "Getting channels from device ${videoDevice}")
val channels = videoDevice.getChannelsAsync()
return channels
} catch (exception: Exception) {
when (exception) {
is UnknownVendorException -> {
Log.w(classTag, "Device ${videoDevice} cannot get channels because the vendor is unknown")
}
is NetworkException -> {
Log.w(classTag, "Device ${videoDevice} cannot get channels because it is unreachable")
}
else -> {
Log.w(classTag, "Device ${videoDevice} cannot get channels, reason: ${exception.message}")
}
}
return emptyList()
}
}
And the implementation in the SDK is this:
override suspend fun getChannelsAsync(): List<VideoChannel> = withContext(Dispatchers.IO) {
Log.i(classTag, "Trying to get channels for device: $logName")
val channels = ArrayList<VideoChannel>()
getZeroChannel()?.let {
channels.add(it)
}
channels.addAll(getAllChannels())
if (channels.isNotEmpty()) {
Log.i(classTag, "Successfully retrieved ${channels.size} channels for device: $logName")
return#withContext channels
} else {
Log.w(classTag, "Error retrieving channels for device $logName or no channels exist")
throw Exception()
}
}
The other functions just make a network call and retrieve some data, it should not be messing with the UI at all.
I am testing with a Xiaomi Mi A3 using Android 10.
Can someone help me? Thank you.
So I don't really know why but I found the answer.
In the SDK, the functions getZeroChannel and getAllChannels were not suspending functions, although they make a network call. So what I did is:
Move the withContext(Dispatchers.IO) part to those two functions (the ones who actually make the network call), and make them suspend functions.
Remove the withContext(Dispatchers.IO) part from getChannelsAsync function. Keep it as a suspend function though.
After these changes everything works as expected. I still don't know why, so if someone could shed some light, that would be very much appreciated.
private fun setupGeckoView() {
val runtime = GeckoRuntime.create(this) // crashes on this line
geckoSession.open(runtime)
geckoView.setSession(geckoSession)
val url = String(Base64.decode(MYURL, Base64.DEFAULT))
geckoSession.loadUri(url)
geckoSession.progressDelegate = createProgressDelegate()
geckoSession.settings.allowJavascript = true
}
i call setUpGeckoView methon in onCreat() but when i click back and
reopen the app then app crashes with IllegalStateException saying
"Failed to initialize GeckoRuntime. It works first time only crashed when i click back and then open app again"
Logs are given below
Process: arholding.kargoshop.mk, PID: 16444
java.lang.RuntimeException: Unable to start activity ComponentInfo{arholding.kargoshop.mk/arholding.kargoshop.mk.SeckoActivity}: java.lang.IllegalStateException: Failed to initialize GeckoRuntime
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3447)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3594)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2146)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7762)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1047)
Caused by: java.lang.IllegalStateException: Failed to initialize GeckoRuntime
at org.mozilla.geckoview.GeckoRuntime.create(GeckoRuntime.java:458)
at org.mozilla.geckoview.GeckoRuntime.create(GeckoRuntime.java:333)
at arholding.kargoshop.mk.SeckoActivity.setupGeckoView(SeckoActivity.kt:23)
at arholding.kargoshop.mk.SeckoActivity.onCreate(SeckoActivity.kt:19)
at android.app.Activity.performCreate(Activity.java:7981)
at android.app.Activity.performCreate(Activity.java:7970)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
This exception will be thrown if there is already an active Gecko instance running. There are many ways to resolve this problem.
Solution 1: Get the default runtime for the given context.
Change your code from
val runtime = GeckoRuntime.create(this)
to
val runtime = GeckoRuntime.getDefault(this)
Solution 2: Kill the process when exit app by finishing activity, add this code into your activity.
override fun onDestroy() {
Process.killProcess(Process.myPid())
super.onDestroy()
}
Solution 3: Only create a new instance if there is no active instance running
private fun setupGeckoView() {
if (geckoRuntime == null) {
geckoRuntime = GeckoRuntime.create(this)
}
geckoSession.open(geckoRuntime!!)
geckoView.setSession(geckoSession)
val url = String(Base64.decode(MYURL, Base64.DEFAULT))
geckoSession.loadUri(url)
geckoSession.progressDelegate = createProgressDelegate()
geckoSession.settings.allowJavascript = true
}
companion object {
var geckoRuntime: GeckoRuntime? = null
}
I am getting an Error: java.lang.IllegalArgumentException, the error occurs only when i navigate to RegisterFragment from LoginFragment and then press back button to go to previous fragment(LoginFragment) and enter email and password and press login button. When i comment the line view!!.findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToHomeContainerFragment()), the error doesn't occur, but i have to navigate to HomeFragment. How to fix it?
Note: The Error doesn't occur when you don't navigate to RegisterFragment
LoginFragment:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
{
val binding: FragmentLoginBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_login, container, false)
val login = binding.loginBtn
val emailField = binding.inputEmail
val passwordField = binding.inputPassword
val signUp = binding.loginSignup
binding.viewModel = viewModel
binding.lifecycleOwner = this
signUp.setOnClickListener {
view!!.findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToRegisterFragment())
}
login.setOnClickListener {
val email = emailField.text.toString().trim()
val password = passwordField.text.toString().trim()
if(email.isEmpty())
{
.................
}
if(!Patterns.EMAIL_ADDRESS.matcher(email).matches())
{
.................
}
viewModel.userLogin(email,password)
}
viewModel.loginAuthData.observe(this, Observer { userAuthData ->
if(userAuthData.checkAuth != null)
{
if(userAuthData.checkAuth!!)
{
Snackbar.make(activity!!.findViewById(android.R.id.content), "Auth Pass", Snackbar.LENGTH_SHORT).show()
//The error doesn't occur if i comment this line >>
view!!.findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToHomeContainerFragment())
}
else
{
Snackbar.make(activity!!.findViewById(android.R.id.content), "Auth Fail", Snackbar.LENGTH_SHORT).show()
}
}
})
return binding.root
}
Error:
Process: com.example.---, PID: 24721
java.lang.IllegalArgumentException: navigation destination com.example.---:id/action_loginFragment_to_homeContainerFragment is unknown to this NavController
at androidx.navigation.NavController.navigate(NavController.java:789)
at androidx.navigation.NavController.navigate(NavController.java:730)
at androidx.navigation.NavController.navigate(NavController.java:716)
at androidx.navigation.NavController.navigate(NavController.java:907)
at com.example.---.ui.login.LoginFragment$onCreateView$3.onChanged(LoginFragment.kt:71)
at com.example.---.ui.login.LoginFragment$onCreateView$3.onChanged(LoginFragment.kt:20)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at com.example.---.viewModel.LoginViewModel.authCallBack(LoginViewModel.kt:37)
at com.example.---.viewModel.LoginViewModel.access$authCallBack(LoginViewModel.kt:12)
at com.example.---.viewModel.LoginViewModel$firebaseUserAuth$1.invoke(LoginViewModel.kt:15)
at com.example.---.viewModel.LoginViewModel$firebaseUserAuth$1.invoke(LoginViewModel.kt:12)
at com.example.---.data.FirebaseUserAuth$login$1.onComplete(FirebaseUserAuth.kt:27)
at com.google.android.gms.tasks.zzj.run(Unknown Source)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
You're using viewModel.loginAuthData.observe(this, Observer {}) in onCreateView(), i.e., using the Fragment (via this) as the LifecycleOwner for your observing. This is always wrong - you should instead use viewLifecycleOwner when observing from onCreateView():
viewModel.loginAuthData.observe(viewLifecycleOwner, Observer {
...
})
How LiveData works is that it continues to stay registered until the given LifecycleOwner is destroyed. When you pass this, it waits for the Fragment itself to be destroyed. However, Fragments are not destroyed while on the back stack (only their view and hence their viewLifecycleOwner is destroyed). This means that the original LiveData never unregisters its Observer.
This means that when onCreateView() happens a second time (i.e., you hit the back button and the view is recreated), a second Observer is created and registered. You now have two Observers running simultaneously. As they both run, the first one successfully runs and calls navigate(). When the second one runs, the NavController has already moved to the next destination, so any actions registered on the previous destination are no longer found, giving you an IllegalArgumentException.
By using viewLifecycleOwner, the original LiveData is correctly destroyed when the Fragment's view is destroyed. This means when you hit back and onCreateView is called again, only one Observer is active at a time and you won't run into this issue.
Try getting the NavController of the containing Activity Activity.findNavController(IdOfView) instead of view!!.findNavController()
For more on Navigation:
https://developer.android.com/reference/kotlin/androidx/navigation/package-summary#findnavcontroller