I am trying to create a getInstance function inside a companion object in a Fragment class called "ListFragment" which returns an instance of ListFragment. I am trying to get the instance of this fragment from a viewModel class which is being used in the same fragment. By doing this, I am getting following error:
java.lang.IllegalStateException: Fragment ListFragment{b13aa81} (7e6b48f5-7ae5-4b8b-b19f-13b3df28c66c) not attached to an activity.
at androidx.fragment.app.Fragment.requireActivity(Fragment.java:995)
at in.machine_test.arun.viewmodels.ListFragmentViewModel.showToast(ListFragmentViewModel.kt:106)
at in.machine_test.arun.viewmodels.ListFragmentViewModel$insert$1$2.emit(ListFragmentViewModel.kt:60)
at in.machine_test.arun.viewmodels.ListFragmentViewModel$insert$1$2.emit(ListFragmentViewModel.kt:59)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt$catchImpl$2.emit(Errors.kt:158)
at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:87)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:66)
at in.machine_test.arun.data.repository.LocalRepository$insert$1.invokeSuspend(LocalRepository.kt:24)
at in.machine_test.arun.data.repository.LocalRepository$insert$1.invoke(Unknown Source:8)
at in.machine_test.arun.data.repository.LocalRepository$insert$1.invoke(Unknown Source:4)
at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61)
at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:230)
at kotlinx.coroutines.flow.internal.ChannelFlowOperatorImpl.flowCollect(ChannelFlow.kt:195)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collect$suspendImpl(ChannelFlow.kt:167)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collect(Unknown Source:0)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt.catchImpl(Errors.kt:156)
at kotlinx.coroutines.flow.FlowKt.catchImpl(Unknown Source:1)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt$catch$$inlined$unsafeFlow$1.collect(SafeCollector.common.kt:113)
at in.machine_test.arun.viewmodels.ListFragmentViewModel$insert$1.invokeSuspend(ListFragmentViewModel.kt:59)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@66b95d6, Dispatchers.IO]
Below is the code which I have written in LoginFragment.
companion object {
@JvmStatic
fun getInstance(): ListFragment = ListFragment()
}
Trying to get the instance from ViewModel
private fun showToast(message: String) {
ListFragment.getInstance().requireActivity().runOnUiThread {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
}
}
To get the instance, I know there are other ways like passing the instance of this class in viewModel constructor or use a dependency injection.
But I am wondering how to get an instance using this getInstance approach.
Any help will be greatly appreciated.
CodePudding user response:
The error's telling you the problem:
java.lang.IllegalStateException: Fragment ListFragment{b13aa81} ...
not attached to an activity.
You're getting this not attached to an activity
error because you're trying to use this Fragment
that you've just created to get access to an Activity
ListFragment.getInstance().requireActivity()
And that's not how Android works. Usually when you have access to a Fragment
it's because the system provided it, e.g. by attaching it to an Activity
or ViewPager
or something.
The Fragment
gets created - and it's just a class, with no access to the app Context
or anything - and then the system connects it to the wider app framework by attaching it to another component, which is a hierarchy that is ultimately running in an Activity
(which is like a view of the app, running in a Window
).
If you just do MyFragment()
you're just creating that basic class - it has no connection to an Activity
, because you didn't give it one. And in the context of your code, a basic function in a companion object, you can't give it one because the function doesn't have access to one.
I'm just explaining this to make it clearer what the limitations are here - I think you're already aware of them, and that's why you're trying to get access to an Activity
somehow, because currently you don't have access to it. But just creating a ListFragment
can't give you access you don't already have. There's no magic to it.
You basically have two options for this situation, which comes up a lot! (Especially for Context
s):
- your code is running in a component that has (or usually has) access to an
Activity
,Context
etc, like a runningFragment
(i.e. in its lifecycle callbacks likeonCreate
), or it has a parent object it can get access through. Access it that way - your code doesn't have access, so you need to provide the
Context
etc, either as a parameter in a function call, or as a constructor parameter. The thing that calls the function or constructs the object needs to have access to theContext
, so it can pass it in
That's what I mean about there being no magic, no tricks to gain access to things. Either you have access, or something needs to provide it. To give your ListFragment
access to an Activity
, you need access to that Activity
yourself so you can attach the fragment to it! There's no workaround for that
As far as your specific problem goes, you just want to run your Toast
call on the main thread, right? Activity#runOnUiThread
is a convenience method for when you have access to an Activity
- but you can do the same thing like this:
// this lets you post runnables to the message queue on the main thread
val handler = Handler(Looper.getMainLooper())
// now you can run your code on it
handler.post { Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show() }
I see you're using coroutines in your stacktrace, so maybe you just want to switch to the Main
dispatcher instead:
withContext(Dispatchers.Main) {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
}
Depends on what you're doing!