Is there a way to not inherit a specific property? - kotlin

I'm trying to group my classes like images.
It is a structure in which one header and several items are gathered in one group to form a list.
If the Header property and Item list properties are placed in the sealed class, the subclass inherits this, so the structure becomes strange.
So, I want to make it impossible to inherit only Header property and Item list property in sealed class. Is there a way?
GroupedItem
sealed class GroupedItem(val layoutId : Int = 1) {
val header: Header = Header() // I want to make inheritance impossible
val itemList: List<Item> = listOf() // I want to make inheritance impossible
data class Header(
val id: String = "",
) : GroupedItem(someValue)
data class Item(
val id: String = "",
val set: Int = 1,
) : GroupedItem(someValue)
}
Grouping example
for (i in 0 until headerNumber) {
val headerText = "Header $i"
val header = GroupedItem.Header(headerText)
val itemList = arrayListOf<GroupedItem.Item>()
val indexNumber = Random.nextInt(2, 5)
for (j in 0 until indexNumber) {
val itemText = "Item $j"
itemList.add(GroupedItem.Item(itemText))
}
val groupedItem = GroupedItem(header, itemList)
groupedItemList.add(groupedItem)
}
ADDED
Main
fun main() {
val test = RoutineItem.Header()
println(test.id)
}
ERROR
Exception in thread "main" java.lang.StackOverflowError
at RoutineItem$Header.<init>(hello.kt)
at RoutineItem$Header.<init>(hello.kt:18)
at RoutineItem.<init>(hello.kt:15)
at RoutineItem.<init>(hello.kt)
at RoutineItem$Header.<init>(hello.kt:22)
at RoutineItem$Header.<init>(hello.kt:18)
at RoutineItem.<init>(hello.kt:15)
at RoutineItem.<init>(hello.kt)
``

The way you're defining it, a Header is a GroupedItem, and every GroupedItem has a Header, which is incorrect, right? It seems like you want one of these:
GroupedItem represents a category of items that go in your groups, i.e. Headers and ItemLists:
You can use an interface, but a sealed class is fine for this (and gives you the GroupedItem.Thing namespacing). Since GroupedItem is a general category, it shouldn't contain a Header or anything else, just like a Vehicle shouldn't have a Car property
GroupedItem represents a structure that contains a Header and an ItemList (possibly optional):
In this case, Header isn't a GroupedItem at all (same as a Wheel isn't a Car) and shouldn't inherit from it - it has no relation to that class. GroupedItem just happens to include a Header as a property.
If you want the GroupedItem.Thing naming scheme to keep things organised, you can just make Header a class within GroupedItem (not an inner class though):
class GroupedItem(
val header: Header = Header(),
val itemList: List<Item> = listOf()
){
data class Header(
val id: String = "",
)
data class Item(
val id: String = "",
val set: Int = 1,
)
}
you'll have to sort out where layoutId is coming from though.
honestly though, I think you probably want both of these things:
GroupedItem to represent things that go in groups
Group for an actual concrete group of those things
class Group(
val header: Header = Header()
val itemList: List<Item> = listOf()
)
sealed class GroupedItem(val layoutId : Int = 1) {
data class Header(
val id: String = "",
) : GroupedItem(someValue)
data class Item(
val id: String = "",
val set: Int = 1,
) : GroupedItem(someValue)
}

Related

Kotlin pass through constructor parameters to parent without declaring in child

My use case:
I have a large number of POJO models that are different types of requests for a third-party API. All of them have several common fields and a couple unique ones.
I was hoping to build something that conceptually looks like this
class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
commonField1 = "1",
commonField2 = "2",
...
uniqueFieldA = "A"
)
}
I can of course override the common fields in every child request and then pass them to the parent constructor, but this ends up producing a lot of boilerplate code and bloats the model. Are there any options I can explore here?
Notice that what you are doing in the parentheses that follow a class declaration is not "declaring what properties this class has", but "declaring the parameters of this class' primary constructor". The former is just something you can do "along the way", by adding var or val.
Each class can have its own primary constructor that take any number and types of parameters that it likes, regardless of what class its superclass is. Therefore, it is not unreasonable to have to specify all the parameters of the constructor:
open class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
// notice that the parameters for the inherited properties don't have the
// "val" prefix, because you are not declaring them in the subclass again.
// These are just constructor parameters.
commonField1: String,
commonField2: String,
...
commonFieldX: String,
val uniqueFieldA: String,
): RequestBase(
commonField1,
commonField2,
...
commonFieldX,
)
If you find this unpleasant, there are a bunch of ways to work around this.
One way is to use composition and delegation - create an interface having the common properties. The specific requests' primary constructors will take a RequestBase and their unique properties, and implement the interface by delegating to the RequestBase:
interface Request {
val commonField1: String
val commonField2: String
val commonFieldX: String
}
open class RequestBase(
override val commonField1: String,
override val commonField2: String,
override val commonFieldX: String
): Request
class RequestA(
val requestBase: RequestBase,
val uniqueField: String
): Request by requestBase
This allows you to access someRequestA.commonFieldX directly, without doing someRequestA.requestBase.commonFieldX, but to create a RequestA, you need to create a RequestBase first:
RequestA(
RequestBase(...),
uniqueField = ...
)
Another way is to change your properties to vars, give them default values, and move them out of the constructor parameters:
open class RequestBase {
var commonField1: String = ""
var commonField2: String = ""
var commonFieldX: String = ""
}
class RequestA: RequestBase() {
var uniqueField: String = ""
}
Then to create an instance of RequestA, you would just call its parameterless constructor, and do an apply { ... } block:
RequestA().apply {
commonField1 = "foo"
commonField2 = "bar"
commonFieldX = "baz"
uniqueField = "boo"
}
The downside of this is of course that the properties are all mutable, and you have to think of a default value for every property. You might have to change some properties to nullable because of this, which might not be desirable.
You can't do it with constructors of base class. Without constructors it's possible:
open class RequestBase {
lateinit var commonField1: String
lateinit var commonField2: String
...
lateinit var commonFieldX: String
}
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
uniqueFieldA = "A"
).apply {
commonField1 = "1"
commonField2 = "2"
...
commonFieldX = "X"
}
}

Retrieve Inheritance class full name from Kotlin Psi API

I’m trying to develop a codegen IDEA-Plugin. This plugin should analyze KtClass Inheritance and get all inheritance class full name (like com.example.config.TestConfig)
I have tried to find any useful information by viewing PsiViewer. I find that all
inheritance info of KtClass is stored in KtSuperTypeEntry, and I try my best to get full name of inheritance class.
for class Dest:
data class Dest(
val stringValue: String = "123",
override val stringConfig: String = "123",
override val iConfigStr: String = "123",
val b: B = B(),
val c: List<List<Set<Map<String, out Any?>>>> = listOf(),
val config: Config? = Config()
) : Config()
superTypeListEntry.typeAsUserType.referenceExpression.getReferencedName() -return-> "Config"
superTypeListEntry.importReceiverMembers() -return-> null
Seemingly SuperTypeListEntry just contain inheritance class simple name info.
I also try to find inheritance class full name by KtFile, but there is no idea when inheritance class was imported in this KtFile as wildcards:
fun KtSuperTypeListEntry.getType(ktFile: KtFile): String {
val simpleName = superTypeListEntry.text
// try to find by declared KtClass ...
ktFile.children.filterIsInstance<KtClass>().filter { it.name == simpleName }
// try to find by import ...
ktFile.importDirectives.filter { it.importPath.toString().contains(simpleName) }
// try to find by import wildcards ...
ktFile.importDirectives.filter { it.importPath.toString().endWith("*") }.forEach {
val split = it.importPath.split(".")
split.set(split.size - 1, simpleName)
val maybeFullName = split.joinToString(",") { it }
// confused on how to detect "maybeFullName" is correct ...
}
}
Question
How can I retrieve all inheritance class full name from Kotlin Psi API? Thank you!
After thousand of investigations and debugging, I find that it is possible to find a class's inheritance classes by BindingContext. BindingContext can analyze a TypeReference and find the reference of KotlinType. The code might be like this:
ktClass.superTypeListEntries.map { superTypeEntry ->
val typeReference = superTypeEntry.typeReference
val bindingContext = typeReference.analyze()
bindingContext.get(BindingContext.TYPE, typeReference)
}.forEach { kotlinType ->
val classId = kotlinType.constructor.declarationDescriptor.classId
val packageName = classId.packageFqName
val simpleName = classId.relativeClassName
// It can also get the generics of this class by KotlinType.arguments
val generics = kotlinType.arguments
}
Also, you can get super types full name of the class by KtLightClass, the code might be like this:
val ktLightClass = ktClass.toLightClass()
val superTypesFullName = ktLightClass?.supers?.forEach { superType ->
val packageName = superType.qualifiedName
val simpleName = superType.name
// you can get as KtClass by this, which can help you unify design of interface.
val ktClass = superType.kotlinOrigin
}

Generic Base Respose models in Kotlin

Everyone following is my json response:
{
"requestResponse": {
"status": 1,
"result": true,
"msg": "Success"
},
"userId": 5504
}
And following is my Base Response class:
class BaseResponses<T>{
lateinit var requestResponse: RequestResponse
}
and following are my User data class parameters.
data class User(val userId:Int)
And below as implementation:
#POST(ApiUrls.CREATE_USER)
fun createUser(#Body body: CreateUser): Single<BaseResponses<User>>
my question is that how can I access T type which is User in the Base class would highly appreciate the help.
Thanks
You don't need a genetic type - you need to inherit the properties.
data class BaseResponses { // Remove T, it's doing nothing
lateinit var requestResponse: RequestResponse
}
// Extend base class to inherit common `requestResponse` field
data class User(val userId:Int) : BaseResponses()
// User now will contain requestResponse object
#POST(ApiUrls.CREATE_USER)
fun createUser(#Body body: CreateUser): Single<User>
I might be understanding you wrong, you just want to re-use the RequestResponse class since it is generic and will be common in all your APIs. So just have it as a parameter in User data class.
So it will be like this
data class User(
val requestResponse: RequestResponse,
val userId: Int
)
Now you can simply access it directly from User object. You can even go a step further and assign it default values like this
data class User(
val requestResponse: RequestResponse = RequestResponse(),
val userId: Int = 0
)
data class RequestResponse(
val msg: String = "",
val result: Boolean = false,
val status: Int = 0
)

Parcelize and ObjectBox clash

I'm using kotlin and ObjectBox in my application. My object box entity looks something like
#Entity
class Order {
#Id var id: Long = 0
lateinit var customer: ToOne<Customer>
}
#Entity
class Customer {
#Id var id: Long = 0
#Backlink
lateinit var orders: List<Order>
}
But when I use #Parcelize, the properties are being ignored in the parcel. How do I use #Parcelize but still include these properties? I tried overriding writeToParcel but I am not allowed to override it due to #Parcelize.
According to docs, you have to declare all properties in primary constructor, which should be serialized via #Parcelize. All other ones are ignored.
ObjectBox doesn't support ToOne so you have to write custom Parceler. In the end your solution should look like this:
#Entity
#Parcelize
#TypeParceler<ToOne<Customer>, ToOneCustomerParceler>
class Order(
#Id var id: Long = 0,
var customer: ToOne<Customer>
) : Parcelable
#Entity
#Parcelize
class Customer(
#Id var id: Long = 0,
#Backlink var orders: List<Order>
) : Parcelable
object ToOneCustomerParceler : Parceler<ToOne<Customer>> {
override fun create(parcel: Parcel): ToOne<Customer> {
//Somehow recreate ToOne instance
...
}
override fun ToOne<Customer>.write(parcel: Parcel, flags: Int) {
val customer = target
...
}
}
Also don't forget to include correct dependencies:
dependencies {
compile "io.objectbox:objectbox-android:$objectboxVersion"
compile "io.objectbox:objectbox-kotlin:$objectboxVersion"
}
P.S. Use different models for each purpose (#Entity and #Parcelize) even if both are the same. It is much easier to manage them since you separate your intentions into 2 models, rather than trying to push everything into single one.

Kotlin generate constructor that sets default values to nulled arguments

Let's take the class of a data class:
data class User(
val userNumber: Int = -1,
val name: String,
val userGroups; List<String> = emptyList(),
val screenName: String = "new-user"
)
When calling this function from Kotlin, it is pretty straightforward. I can simply use the named-argument syntax to do so. Calling from Java, I have to specify all values, or use the #JvmOverloads annotation, which generates the following constructors (in addition to the constructor that kotlin generates with the bit-mask for default values):
User(int userNumber, #NotNull String name, #NotNull List userGroups,
#NotNull String screenName)
User(int userNumber, #NotNull String name, #NotNull List userGroups)
User(int userNumber, #NotNull String name)
User(#NotNull String name)
Now, if I want to create a User object in Java equivalent to User(name="John Doe", userGroups=listOf("admin", "super") I can't do it with the above constructors. I CAN however do it if I put val userNumber: Int = -1 at the end in the data class declaration (the generation of constructors seems to depend on the order the optional arguments are defined in). Which is fine, because expecting kotlin to generate all permutations is going to heavily bloat some classes.
The biggest problem that tools like Jackson simply don't work as they have no idea which constructor to use (and not like I can annotate one of the generated ones specially).
So, is there a way to generate a (single) constructor like:
User(Integer userNumber, String name, List<String> userGroups, String screenName) {
this.userNumber = (userNumber == null) ? -1 : userNumber;
this.userGroups = (userGroups == null) ? Collections.emptyList() : userGroups;
//...
}
Currently I am using the above approach, but manually defining the constructors where I need them.
EDIT
I should clarify, creating a similar constructor doesn't work, obviously because both the signatures would clash on the JVM. This is what it would like in my case:
data class User(
val userNumber: Int = -1,
val name: String,
val userGroups; List<String> = emptyList(),
val screenName: String = "new-user"
) {
companion object {
#JvmStatic
#JsonCreator
fun constructionSupport(
#JsonProperty("userNumber") userNumber : Int?,
#JsonProperty("name") name : String,
#JsonProperty("userGroups") userGroups : List<String>?,
#JsonProperty("screenName") screenName : String?
) = User(
userNumber = userNumber ?: -1,
name = name,
userGroups = userGroups ?: emptyList(),
screenName = screenName ?: "new-user"
)
}
}
Also note the redundancy where I have to write the default values for the properties twice. I Now that I look at it, I doubt there exists a solution for this. Maybe this is a good use-case for a kapt based side-project of mine :)
Better solution is to add possibility to library understand Kotlin functional. For example, for Jackson exists jackson-module-kotlin. With this library we can use default arguments in data classes.
Example:
data class User(
val userNumber: Int = -1,
val name: String,
val userGroups: List<String> = emptyList(),
val screenName: String = "new-user"
)
fun main(args: Array<String>) {
val objectMapper = ObjectMapper()
.registerModule(KotlinModule())
val testUser = User(userNumber = 5, name = "someName")
val stringUser = objectMapper.writeValueAsString(testUser)
println(stringUser)
val parsedUser = objectMapper.readValue<User>(stringUser)
println(parsedUser)
assert(testUser == parsedUser) {
println("something goes wrong")
}
}
After kicking this around for a minute, I think I found a solution that may work well here. Simply define a top level function in the same source file, that will build the object. Perhaps like so:
fun build_user(userNumber: Int?, name: String, userGroups: List<String>?, screenName: String?) : User {
return User(if(userNumber !== null) userNumber else -1, name, if(userGroups !== null) userGroups else emptyList(),
if(screenName !== null) screenName else "new-user")
}
Then when you need it, you simply call it from Java:
User user = UserKt.build_user(null, "Hello", null, "Porterhouse Steak");
System.out.println(user);
Output from the example:
User(userNumber=-1, name=Hello, userGroups=[], screenName=Porterhouse Steak)
The method is somewhere between a constructor and a builder. It beats hammering out a full-blown Builder object, and avoids cluttering your data class with unnecessary Java-interop glue code messiness.
See Package Level Functions for more information.