apollo-kotlin
apollo-kotlin copied to clipboard
Kotlin Codegen Set Default Null Value
I haven't found a way to do this yet. I want to have the generated code for Kotlin data classes to have a set null on a nullable field i.e. ( extra caveat would be to have it recognize the type and set the default but I'd be happy with it as null for any and all fields with ?)
public data class Reviews(
public val averageRating: String? = null,
public val ratingsBreakdown: List<RatingsBreakdown>? = emptyList(),
public val reviewCount: Int? = 0,
public val totalRecommended: Double? = 0.0
)
Instead it generates this and then it is very hard to work with when creating a new data class because you have to set every field when calling Reviews( null, null, null, null) when the data class looks like this:
public data class Reviews(
public val averageRating: Double?,
public val ratingsBreakdown: List<RatingsBreakdown>?,
public val reviewCount: Int?,
public val totalRecommended: Double?
)
Thanks for sending this 🙏 . We're aware that's a pain point and historically (see also https://github.com/apollographql/apollo-kotlin/issues/3028), we've historically avoided adding default parameters because that's more bytecode in the models that is usually only used for tests. There are more issues associated with this like what default values to chose (null would make sense for nullable values but for others, 0.0 might not be the desired default) and also for some polymorphic cases/fragments, it's harder to build because of __typename and merged fields.
Moving forward, we'd like to offer Test Builders to make it easier to build the models. It's still experimental and we have ideas how to improve them but would that work for you?
Yeah I hope they address this as it is writing kotlin so it should account for a developers wanting fields set to default values, I mean its an option for Kotlin methods for either
val myVariable // non null
val myVariable? // potential null
val myVariable? = someValue // could be "null" or its set to some val.
Hopefully this gets addressed for Kotlin since its not an option for Java.
So I wanted to try and use reflection and have that set the default values by being able to copy a mock and insert values for the whenever in the mock on the copy. Not quite there but maybe this can spark something your team wants to do to help us write test cases and insert mocked Apollo data.
@Suppress("UNCHECKED_CAST")
fun <T : Any> clone (obj: T): T? {
if (!obj::class.isData) {
println(obj)
throw Error("clone is only supported for data classes")
}
val copy = obj::class.memberFunctions.first { it.name == "copy" }
val instanceParam = copy.instanceParameter!!
val map = mapOf<KParameter, Any?>(instanceParam to obj)
return copy.callBy(map) as T?
}
Here I wanted to use it in a test but I was only able to get this working where I created a real object with a mocked inventory class. I'd like do mock(SomeDataClass) with specific whenever and then be able to copy that but the clone turns out to be null and fails the first assertion.
@Test
fun genericDataClasses() {
val someDataClass = SomeQuery.SomeDataClass("1234", true, "", mock{
on { price }.thenReturn(10000)
})
val cloned = clone(someDataClass)
assertNotNull(cloned)
cloned?.let {
assertEquals("1234", it.id)
assertEquals(true, it.mybool)
assertEquals("", it.badge)
it.inventory?.let { p ->
assertEquals(10000, p.listPrice)
assertEquals(null, p.soldPrice)
}
}
}
@grndvl1 3.6.0 introduces data builders: https://www.apollographql.com/docs/kotlin/testing/data-builders
It uses a builtin FakeResolver by default, providing default values for all fields so you can do something as simple as:
val data = SomeQuery.Data()
And it will return a valid data instance. If you need control over some values, you can do so:
val data = SomeQuery.Data {
price = 10000
}
And it will use 10000 for price and fake values for everything else.
Note: this only works for the top-level Data field for now. If you need it for other classes, do you mind sharing more details about the use case?
@grndvl1 did you get a chance to look at data builders?
I'm going to go ahead and assume data builders worked. We don't have plans to introduce default values to model parameters at the moment.
Sorry man seems my notifications was off and in the process of changing my life from house to RV life. We just upgraded to 3.6.0 and I reference the val data = OurCompanyQuery.Data() and the IDE errors and says I don't have a constructor for that. In the Apollo file generated it has
@ApolloAdaptableWith(OurCompanyQuery_ResponseAdapter.Data::class)
public data class Data(
/**
* Get paginated listings with search options for the provided query parameter string
*/
public val listingSearchDeepLink: ListingSearchDeepLink?,
) : Query.Data
No worries, let us know how that goes!
Yeah did not work. I went to a more complex object and it lets me only mock the first field the rest it can't even find the reference.
it lets me only mock the first field the rest it can't even find the reference.
I'm guessing you bumped into this?
Note: this only works for the top-level Data field for now.
I'll investigate another solution based on low level compiler APIs. I'll update this thread if it turns out promising
@grndvl1 You can know use the KotlinCompilerHooks API from 3.7.0 to do so. You can find an example in the integration tests: https://github.com/apollographql/apollo-kotlin/blob/fb51dafaa4b02a55b6926546927d176078513543/tests/compiler-hooks/build.gradle.kts#L79
Let us know how that works for you!
I'm going to go ahead and assume this works. Fell free to leave a message if you want us to reopen this issue.