Is it possible to use newInstance() on an Activity in Android Kotlin

Viewed 1120

I'm new to android actually. I'm now confused about how to implementing a newInstance() inside a fragment. I have 2 fragments and 1 Activity here :

  • Fragment A as an Abstract Class
  • Fragment B as a Main Fragment Class that contain parameters
  • MainActivity.kt as the Activity

So I got an error log like this :

Caused by androidx.fragment.app.Fragment$e: Unable to instantiate fragment : could not find Fragment constructor
............
Caused by java.lang.NoSuchMethodException: <init> []
       at java.lang.Class.getConstructor0

Based on the log, I think the problem is on the fragment that I haven't initialized any 0-arg constructor. So because of this, I want to add a companion object for newInstance() inside my fragment but I can't, here's my FragmentA.kt and FragmentB.kt :

FragmentA.kt

abstract class FragmentA: BottomSheetDialogFragment() {

companion object {
fun newInstance(): FragmentA{
    return FragmentA()  //Got a red mark here, because I cannot create newInstance() in an abstract class
}
}
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setStyle(DialogFragment...,getStyle)
    }

}

FragmentB.kt

class FragmentB(val Parameterss: AnyParametersHere) : FragmentA() {
    
    companion object {
    fun newInstance(): FragmentB{
        return FragmentB()  //Got a red mark here, because it requires parameter inside the FragmentB()
    }
    }
        override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState)
        ..................................................................
        return dialog

        }
    
    }

MainActivity.kt In the MainActivity.kt, can I add newInstance() to its companion object or not? If not, what's the best solution for this case?

class MainActivity : AppCompatActivity() {

companion object {
        fun newInstance(): MainActivity {
            return newInstance()
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    ...................................
    val fragmentB= FragmentB(this)
        fragmentB.show(supportFragmentManager, FragmentB.TAG)

    }

}

I'm confused tho, please if you have any better solutions I would like to appreciate it. Thank you

2 Answers

There are many different things wrong here, I would suggest you read more on this topic. I will help clear your doubts. Let us start with your code.

Firstly FragmentA, FragmentA is an Abstract class and thus when you are getting the error

Unable to instantiate fragment

You cannot create an instance of an abstract class because it does not have a complete implementation. If it does, it should not be marked abstract in the first place. You should extend from Abstract Classes which you are doing so in FragmentB

Now coming to FragmentB

class FragmentB(val Parameterss: AnyParametersHere) : FragmentA() {

As you can see inside you are accepting parameters inside its constructor. Now coming to your newInstance() method. The code is as follows:

fun newInstance(): FragmentB{
        return FragmentB()
    }

And you rightly got an error or a read mark here because you created an instance of FragmentB but you did not pass any parameters in its constructors which you put in its constructor.

Now coming to the right pattern for it, we use newInstance usually in Fragment when we need to do pass arguments from one fragment to the other and we do by using Bundles which we assign via newInstance

Here is a sample code for that.

companion object {
        fun newInstance() = FragmentB().apply {
            arguments = Bundle().apply {
                // Put values here in a key value pair.
            }
        }
    }

And then you get an instance of FragmentB as FragmentB.newInstance(). You will have to read more on this topic. There are more than one ways to transfer data between Fragments.

Coming to your MainActivity you do not need newInstance here as between Activities you can directly use Intents and transfer data that way if needed.

FragmentB’s constructor must not have parameters. Fragments are recreated by the OS using the empty constructor. Your newInstance() function can pass data to the Fragment via a bundle, and the OS will restore that same bundle if it recreates your fragment. Use the requireBundle() to get your parameters and assign them to properties in your first entry point in your Fragment (onCreateView(), onViewCreated(), or onCreateDialog()).

A common mistake is to give your Fragment two constructors, one empty and one with the parameters you need, and using the one with parameters in newInstance(). The problem with this is your parameters will be lost when the OS recreates your Fragment with the empty constructor. The OS only restores the Bundle, not the parameters of a constructor, because it exclusively uses the empty constructor.

Note that bundle arguments must be one of the supported types, which are all the "primitive" classes, Strings, Parcelables, arrays of any of the above, and Serializeables.

Example of how you would do this with a fragment that needs some values:

class MyFragment: Fragment() {

    companion object {
        private const val SOME_ARG_KEY = "SOME_ARG_KEY"
        private const val SOME_OTHER_ARG_KEY = "SOME_OTHER_ARG_KEY"

        fun newInstance(someArgument: Long, someOtherArgument: String)
            = MyFragment().apply { 
                arguments = bundleOf(
                    SOME_ARG_KEY to someArgument,
                    SOME_OTHER_ARG_KEY to someOtherArgument,
                )
            }
    }

    lateinit var someArgument: Long
    lateinit var someOtherArgument: String

    //...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        with (requireArguments()) {
            someArgument = getLong(SOME_ARG_KEY)
            someOtherArgument = getString(SOME_OTHER_ARG_KEY)
        }
    }
}
Related