1. Introduction
Kotlin is celebrated for its concise syntax and enhanced safety features. However, like any programming language, it is not immune to errors. One common error developers encounter is the “Primary constructor call expected“. This error typically arises when the Kotlin compiler expects a primary constructor to be invoked but finds none. Understanding how to resolve this error is crucial for maintaining clean and functional code.
In this tutorial, we’ll explore various approaches to resolving this error, illustrating each with problematic code examples, their solutions, unit tests, and detailed explanations.
2. Correctly Defining Constructors
Let’s consider the following code:
class Student(var name: String) {
var age: Int = 14
constructor(name: String, age: Int) {
this.age = age
}
fun printMsg() {
println("Name is $name. Age is $age.")
}
}
In this example, the Student class has a secondary constructor that doesn’t call the primary constructor. Consequently, when we attempt to create an instance of Student using two parameters (name and age), the compiler raises an error because it expects a primary constructor call.
2.1. Solution
To fix this issue, we need to ensure that the secondary constructor properly calls the primary constructor using the this keyword:
class Student(var name: String) {
var age: Int = 14
constructor(name: String, age: Int) : this(name) {
this.age = age
}
fun printMsg() {
println("Name is $name. Age is $age.")
}
}
In the corrected code, the secondary constructor now correctly invokes the primary constructor with : this(name). This ensures the correct initialisation of all properties, resolving the compilation error.
To confirm that this updated code resolves the compiler error, let’s unit test our class and attempt to instantiate the Student class:
@Test
fun `correctly resolve compile error by properly defining constructors`() {
val student = Student("Martial", 15)
assertEquals("Martial", student.name)
assertEquals(15, student.age)
}
The unit test confirms that the Student class works as we expected. Specifically, it verifies that the constructor correctly assigns the name and age properties when creating a Student instance. Moreover, by performing these checks early, the test ensures that the constructors function properly.
3. Using Secondary Constructors Properly
Next, let’s consider the following code:
open class Animal(val species: String)
class Dog : Animal {
constructor(species: String) : super(species)
}
In this code snippet, we attempt to subclass Animal with Dog but don’t provide a proper primary constructor for Dog. The compiler expects a primary constructor call but finds none, leading to an error.
3.1. Solution
To resolve this issue, we need to explicitly define a primary constructor for Dog that calls the superclass’s constructor:
open class Animal(val species: String)
class Dog(species: String) : Animal(species)
Specifically, we define a primary constructor in the Dog class. This primary constructor accepts a parameter species and passes it to the superclass Animal.
Similarly, let’s confirm this resolution with a unit test:
@Test
fun `correctly resolve compile error by properly using the secondary constructor`() {
val dog = Dog("Max")
assertEquals("Max", dog.species)
}
The unit test for the Dog class checks the creation of a Dog instance with the correct species. By ensuring that our constructors are set up correctly, we can avoid such compiler errors and maintain code integrity.
4. Using Default Parameter Values
Lastly, let’s consider the following problematic code:
class Car(val make: String) {
var model: String
constructor(make: String, model: String) {
this.make = make
this.model = model
}
}
In this example, the Car class defines a primary constructor that initializes only the make property. The secondary constructor attempts to initialize both make and model, but this approach is flawed because it introduces unnecessary complexity without properly utilizing the primary constructor. As a result, it leads to the “Primary constructor call expected” error.
4.1. Solution
To resolve this issue, we can define default values for the parameters in the primary constructor. This way, we can create instances of Car with just the make, or with both make and model:
class Car(val make: String, val model: String = "Unknown")
In this corrected code, we assign a default value of “Unknown” to the model parameter in the primary constructor. This allows us to create a Car object with just make, while still providing flexibility to specify a model whenever we need.
As usual, we should equally unit test this class for correctness:
@Test
fun `correctly resolve compile error by properly using default parameters`() {
val car1 = Car("Toyota", "Corolla")
assertEquals("Toyota", car1.make)
assertEquals("Corolla", car1.model)
val car2 = Car("Honda")
assertEquals("Honda", car2.make)
assertEquals("Unknown", car2.model)
}
The unit test validates that our Car class behaves as expected. It confirms that both instances of Car are created correctly—one with both parameters and one using the default value for the model. This demonstrates how using default values in constructors can enhance flexibility and prevent errors related to constructor calls.
5. Conclusion
In this article, we’ve explored the “Primary constructor call expected” error in Kotlin, a common issue with class constructors. We highlighted the importance of correctly defining constructors to prevent compilation errors and demonstrated the effective use of primary and secondary constructors to ensure proper initialization of properties. We also discussed how default parameter values can enhance flexibility in object creation, simplifying instantiation and avoiding errors.