Harshit Choudhary
Harshit Choudhary

Reputation: 49

Can't create secondary constructors in kotlin

I was trying to create a class

class TestingModel {

    companion object {
        const val IMAGE_SLIDER: Int = 0
        const val TRENDING_ADS: Int = 1
    }

    var viewType: Int? = 0
    var imageSliderList: List<SlideModel>? = null
    var adsList: List<HomeTrendingAdsModel>? = null
    var categoryList: List<HomeCategoryModel>? = null



    constructor(viewType: Int?, imageSliderList: List<SlideModel>? = null) {
        this.viewType = viewType
        this.imageSliderList = imageSliderList
    }

    constructor(viewType: Int?, adsList: List<HomeTrendingAdsModel>?) {
        this.viewType = viewType
        this.adsList = adsList
    }

    constructor(viewType: Int?, categoryList: List<HomeCategoryModel>?) {
        this.viewType = viewType
        this.categoryList = categoryList
    }
}

However, I got an error saying

Platform declaration clash: The following declarations have the same JVM signature ( (Ljava/lang/Integer;Ljava/util/List;)V): 
public constructor TestingModel(viewType: Int?, imageSliderList: List<SlideModel>? = ...) defined in com.example.theads.Model.TestingModel
public constructor TestingModel(viewType: Int?, categoryList: List<HomeCategoryModel>?) defined in com.example.theads.Model.TestingModel
public constructor TestingModel(viewType: Int?, adsList: List<HomeTrendingAdsModel>?) defined in com.example.theads.Model.TestingModel

I saw in a library doing exactly the same without error.

Edit: That other library is below:

class SlideModel {

    var imageUrl: String? = null
    var imagePath: Int? = 0
    var title: String? = null
    var scaleType: ScaleTypes? = null

    constructor(imageUrl: String?, title: String? = null, scaleType: ScaleTypes?  = null) {
        this.imageUrl = imageUrl
        this.title = title
        this.scaleType = scaleType
    }

    constructor(imagePath: Int?, title: String?  = null, scaleType: ScaleTypes?  = null) {
        this.imagePath = imagePath
        this.title = title
        this.scaleType = scaleType
    }

    constructor(imageUrl: String?, scaleType: ScaleTypes?) {
        this.imageUrl = imageUrl
        this.scaleType = scaleType
    }

    constructor(imagePath: Int?, scaleType: ScaleTypes?) {
        this.imagePath = imagePath
        this.scaleType = scaleType
    }
}

I'm creating a recycler view with multiple recyclerviews in it. So this class becomes a model class for that recyclerview and I want it to call viewType and differnet list from other model classes. Thank you

Upvotes: 2

Views: 1105

Answers (1)

aSemy
aSemy

Reputation: 7110

The example you provide, class SlideModel, has multiple constructors. Each constructor has a unique combination of parameter types. That's important - because Kotlin gets turned into Java, and in Java, the names of the parameters get ignored. The JVM compiler just doesn't care about the names - all it cares about is the order of the parameter types.

Conflicting constructors

Let's have a look at a simpler example (which I'll re-use for the rest of this answer, as I'm missing classes so I can't compile your code).

class Example has two constructors, each accepting a String? parameter. But it doesn't compile...

class Example {

  private var id: String? = null
  private var name: String? = null

  constructor(id: String?) {
    this.id = id
  }

  // ERROR
  // Conflicting overloads: 
  // public constructor Example(id: String?) defined in Example, 
  // public constructor Example(name: String?) defined in Example
  constructor(name: String?) {
    this.name = name
  }
}

This is a different error to the one you're seeing.

Unlike the constructors in class SlideModel, the constructors in class Example are not unique. The names of the parameters are ignored, and so all the compiler sees is two identical constructors - which is forbidden.

Conflicting generic constructors

Furthermore, because of type erasure, the JVM compiler can't distinguish based on generic types. For example, List<String> and List<Int> look the same after compilation, so again, you can't have two constructors that appear the same.

class ExampleLists {

  private var ids: List<Int>? = null
  private var names: List<String>? = null

  constructor(ids: List<Int>) {
    this.ids = ids
  }

  // ERROR
  // Platform declaration clash: 
  // The following declarations have the same JVM signature (<init>(Ljava/util/List;)V):
  //    constructor ExampleLists(ids: List<Int>) defined in ExampleLists
  //    constructor ExampleLists(names: List<String>) defined in ExampleLists
  constructor(names: List<String>) {
    this.names = names
  }
}

This is the same error you're seeing.

As @Zoe noted, this behaviour has been answered here: Kotlin thinks that two methods have the same JVM signature, but the actually don't

Solutions

@JvmName & static method constructors

Because it's a constructor, you can't directly use the @JvmName annotation to help.

  // ERROR
  // This annotation is not applicable to target 'constructor'
  @JvmName("idConstructors") 
  constructor(ids: List<Int>) {
    this.ids = ids
  }

You can, however, create some functions that act like constructors, faux constructors, in a companion object.

// The 'faux constructors' must be imported
import ExampleLists.Companion.ExampleLists


class ExampleLists {

  private var ids: List<Int>? = null
  private var names: List<String>? = null

  companion object {

    /** Faux constructor - creates [ExampleLists] and sets [ExampleLists.ids] */
    @JvmName("exampleListsIds") // manually set JvmName, so each 'constructor' doesn't clash
    fun ExampleLists(ids: List<Int>): ExampleLists {
      val instance = ExampleLists()
      instance.ids = ids // only set ids
      return instance
    }

    /** Faux constructor - creates [ExampleLists] and sets [ExampleLists.names] */
    @JvmName("exampleListsNames") // manually set JvmName
    fun ExampleLists(names: List<String>): ExampleLists {
      val instance = ExampleLists()
      instance.names = names // only set names
      return instance
    }
  }

  override fun toString() = "ExampleLists(ids=$ids, names=$names)"

}

fun main() {
  println(ExampleLists(listOf("test"))) 
  // prints: ExampleLists(ids=[], names=[test])
  println(ExampleLists(listOf(1)))
  // prints: ExampleLists(ids=[1], names=[])
}

However I don't think this is great. It's so much code! It's clunky and difficult to scale and adapt.

A single constructor with defaults

Because Kotlin has overloads and default parameters, and your fields are defaulting to null in this instance, I suggest you only need one (default) constructor.

class ExampleLists(
  private var ids: List<Int>? = null,
  private var names: List<String>? = null,
) {
  override fun toString() = "ExampleLists(ids=$ids, names=$names)"
}


fun main() {
  println(ExampleLists(names = listOf("test")))
  // prints: ExampleLists(ids=[], names=[test])
  println(ExampleLists(ids = listOf(1)))
  // prints: ExampleLists(ids=[1], names=[])
}

Much better - smaller, compact, clearer, and utilises great Kotlin features.

Upvotes: 1

Related Questions