Home > Enterprise >  Cannot create an Instance for a ListFragment class in Kotlin
Cannot create an Instance for a ListFragment class in Kotlin

Time:08-24

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 Contexts):

  • your code is running in a component that has (or usually has) access to an Activity, Context etc, like a running Fragment (i.e. in its lifecycle callbacks like onCreate), 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 the Context, 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!

  • Related