HelloCW
HelloCW

Reputation: 2335

Is this a good way to instantiate an object in a class with Kotlin?

I always find the code just like Code A in CameraX project. It creates instances using a object inside companion object within a class.

If I write the same code, I will use Code B.

Is it a good way to instantiate an object in a class with Kotlin? Is Code A better than Code B ?

BTW, I don't imagine that "Every Fragments class must have an empty constructor". Would you please consider a normal class instead of Fragments class?

Code A

class UIFragmentPhoto internal constructor() : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?) = ImageView(context)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val args = arguments ?: return
        val resource = args.getString(FILE_NAME_KEY)?.let { File(it) } ?: R.drawable.ic_photo
        Glide.with(view).load(resource).into(view as ImageView)
    }

    companion object {
        private const val FILE_NAME_KEY = "file_name"

        fun create(image: File) = UIFragmentPhoto().apply {
            arguments = Bundle().apply {
                putString(FILE_NAME_KEY, image.absolutePath)
            }
        }
    }
}

Invoke A

override fun getItem(position: Int): Fragment = UIFragmentPhoto.create(mediaList[position])

Code B (Modified)

class UIFragmentPhoto internal constructor() : Fragment() {
    val FILE_NAME_KEY = "file_name"

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?) = ImageView(context)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val args = arguments ?: return
        val resource = args.getString(FILE_NAME_KEY)?.let { File(it) } ?: R.drawable.ic_photo
        Glide.with(view).load(resource).into(view as ImageView)
    }


    constructor(image: File):this(){
        arguments = Bundle().apply {
            putString(FILE_NAME_KEY, image.absolutePath)
        }
    }

}

Invoke B (Modified)

override fun getItem(position: Int): Fragment = UIFragmentPhoto(mediaList[position])

Upvotes: 3

Views: 1444

Answers (5)

Leo Aso
Leo Aso

Reputation: 12513

Fragments must have a public no-arguments constructor, and should not have other constructors besides that one; instead, any arguments that the fragment needs should be put in its arguments Bundle. This is because when Android needs to dynamically construct fragments, it does so by calling the empty constructor.

As such, even if you wanted to use your code B (and it is not generally recommended), your default constructor should be public, in which case your secondary constructor simply serves the same role as the create method in code A.

Upvotes: 0

aminography
aminography

Reputation: 22832

In case of Fragments:

According to the source code of the Fragment class on android.googlesource.com where the default constructor is defined, we see that:

Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments and later retrieved by the Fragment with getArguments.

Applications should generally not implement a constructor. Prefer onAttach(Context) instead. It is the first place application code can run where the fragment is ready to be used - the point where the fragment is actually associated with its context. Some applications may also want to implement onInflate to retrieve attributes from a layout resource, although note this happens when the fragment is attached.


Referring to the above reason, adding non-default constructors in a Fragment is PROHIBITED !!!

After that, using setArguments and getArguments methods is the alternative way to avoid adding extra constructors. Code B written in question, uses these two approaches simultaneously. You should use one of them i.e. like Code A. (Because when you pass the parameter to the constructor, it is possible to access it in class. So there is no need for the [set/get]Arguments pattern)

However, if we want to rewrite the Code B without using arguments (Disclaimer: I emphasize that this approach is not true), we can do it like the following:

class UIFragmentPhoto internal constructor(private val image: File?) : Fragment() {

    constructor() : this(null)

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?) = ImageView(context)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val resource = image ?: R.drawable.ic_photo
        Glide.with(view).load(resource).into(view as ImageView)
    }

}


In general:

We all know that an object is created by calling the constructor method of the class in Java/Kotlin. For example:

val obj = MyClass()

When you want to create an object, there is no need for wrapping the constructor call in another function unless the nature of the object must be changed according to the nature of the program. Because it leads to imposing the burden of one extra function call to create an object without any advantages.

In the case of the changing objects according to the nature of the program, we must get the help of creational design patterns to provide more general and flexible approach (like: Factory Method, Abstract Factory, etc. Patterns).


Conclusion:

  1. When you are dealing with the creation of an object from Fragment classes, the Code A style should be used. (Because of the reason described in android.googlesource.com)

  2. When you are dealing with the creation of an object from non-Fragment classes, the Code B style is better to be used. (Because of avoiding extra function call which has no advantage)


Upvotes: 4

A S M Sayem
A S M Sayem

Reputation: 2080

In kotlin, object property denotes a collection Singleton variables and methods by default.

object Singleton { // or whatever you name it
   //singleton members
}

It is lazy and thread-safe, it initializes upon first call, much as Java's static initializers.

You can declare an object at top level or inside a class or another object.

So, your object declaration inside a class can be marked with the companion keyword:

class UIFragmentPhoto {
    companion object Factory {
        fun create(): UIFragmentPhoto = UIFragmentPhoto()
    }
}

Members of the companion object can be called by using simply the class name as the qualifier:

val instance = UIFragmentPhoto.create()

If you only use object without companion, you have to do like this:

val instance = UIFragmentPhoto.Factory.create()

In my understanding, companion means this object is companion with the outer class. So, yes you can obviously use the instance created in you Code A into Code B. For any confusion let me know. I will try to help you further. Thanks

Upvotes: 0

dominicoder
dominicoder

Reputation: 10185

Is a good way to instance a object in a class with Kotlin?

Not really. Also, this has nothing to do with Kotlin, it's a language-agnostic pattern.

If you initialize the Fragment your way (B) then you have three problems:

1) It would be easy to forget to call "create" on your instance:

override fun getItem(position: Int): Fragment = UIFragmentPhoto() // Oops - bug

2) If you make create an instance method you could call it repeatedly on an existing fragment:

fun someFunction() {
    UIFragmentPhoto fragment = getExistingFragment()
    fragment.create() // Oops - just overwrote the fragment state
}

3) Having a method to "create" an instance that has already been created is just confusing and a non-standard way to initialize a fragment. The point of that static / companion way of doing it is that you have a function who's job it is to create and initialize the object.

A factory method also gives you the flexibility to do error handling / verification before returning the object and the opportunity to create a wholly different object type that extends / implements the return type if you so choose:

companion object {
    fun create(arg: Int): UIFragmentPhoto {
        if (arg == 0) throw IllegalStateException("Wasn't expecting 0!")
        if (arg == 1) return FragmentThatExtendsUIFragmentPhoto()
        if (arg == 2) return UIFragmentPhoto()
    }
}

Upvotes: 0

Tenfour04
Tenfour04

Reputation: 93872

No, it is inefficient. Your Code B requires you to instantiate a throw-away copy of your fragment, just so you can call a factory method to create the actual fragment that you want.

Upvotes: 0

Related Questions