data class User(val person: Person, val transport: Transport)
That bit in the parentheses there is the primary constructor - in fact that's shorthand, you can also write the class declaration like this:
data class User constructor(val person: Person, val transport: Transport)
When you have a primary constructor, any secondary constructors have to call through to the primary one.
// need to somehow create a Transport to call the primary with constructor(person: Person, activity: Activity): this(person, someTransport) // can't do this - the primary constructor doesn't take (Person, Activity) constructor(person: Person, activity: Activity): this(person, activity)
That's because to actually create an instance of a class, you need to call its main constructor at some point - the secondaries can just do other stuff as well, but they need to actually instantiate the object.
You can omit the primary constructor (so the class can be instantiated with no arguments) and then your secondary constructors can do whatever they want:
class User { constructor(person: Person, activity: Activity) { // initialisation stuff } constructor(person: Person, absent: Absent) { // other initialisation stuff } }
but at the end of the day it's still calling that no-args constructor to actually create the User - it's up to you to do something with those different arguments passed into the different constructors, and create the same type of object no matter which is called.
Do you have all the possible properties, and just leave them null if no value was provided? Do you have a special set of classes representing the different combos of data for each constructor, and assign an instance of one of those to a userData property? You need to work out how to have a single class that can be instantiated with different combinations of data.
Data classes are special, and require a primary constructor. That's actually what defines the data in the class, and all its handy overridden and generated methods (like equals, hashCode, copy) all work with those properties defined in the primary constructor.
The flipside of that is any property not defined in or derived from the primary constructor's parameters is not part of its "data". If you copy a data class, only the properties in the primary constructor are copied. It doesn't know anything about the rest of the class - so if you're relying on any of the data class features, be aware that other properties are not included by default.
So with that in mind, a typical data class approach would be to have all your data in the primary constructor:
data class User( val person: Person, val transport: Transport? = null, val activity: Activity? = null, val absent: Absent? = null ) { constructor(person: Person, activity: Activity): this(person, null, activity, null) constructor(person: Person, absent: Absent): this(person, null, null, absent) }
That way every secondary constructor calls the main one, and all your data is defined and contained in the primary constructor. Some things just might be missing!
This is a bit awkward though:
this(person, null, activity, null)
We have named arguments, so you could try this:
constructor(person: Person, activity: Activity): this(person, activity = activity)
But that will actually call the same secondary constructor again, because its signature (takes a Person and an Activity) matches the call you're making. (That's why you're getting the cyclic error.) But if we're doing things this way, with default arguments, you can avoid the secondary constructors completely:
data class User( val person: Person, val transport: Transport? = null, val activity: Activity? = null, val absent: Absent? = null ) // create an instance User(somePerson, absent = someAbsent)
But this way limits your ability to restrict it to certain combinations, like a Person and one of Transport/Activity/Absent. That's a problem with data classes in general - you can make the primary constructor private and force users to go through secondary constructors or other functions that generate an instance, but the copy function allows people to mess with that data however they like.
In this case, you probably want a sealed class like IR42 mentions in the comments - a type which allows for a defined set of completely different subclasses. I just wanted to give an overview of how this all works and why maybe you'd want to try a different approach
Userinitialized with aTransportand then I dox.activity? Is itnull? An exception? You need to write a class (not a data class) that encapsulates whichever behavior you want.Userclass