Hi! I tried using response-based codegen. It seems...
# apollo-kotlin
k
Hi! I tried using response-based codegen. It seems that in response-based, I can receive a fragment via a method, and the return value is Optional. In what situations would it return null? Should I always perform a null check? Is it dangerous to force unwrap it?
b
Hi! In responseBased, fragments usually result in interfaces. Would you mind showing your query and the generated code so we can have a look?
k
Copy code
query StorefrontAccountPage($customerAccessToken: String!, $countryCode: CountryCode!, $languageCode: LanguageCode!) @inContext(country: $countryCode, language: $languageCode) {
    ... StorefrontAccountPageContentFragment
}

fragment StorefrontAccountPageContentFragment on QueryRoot {
    customer(customerAccessToken: $customerAccessToken) {
        id
        displayName
    }
}

// need force unwrap or null check
 AccountPageContent(storefrontQueryRoot = storefrontQuery.data.storefrontAccountPageContentFragment()!!)

fun AccountPageContent(
    storefrontQueryRoot: StorefrontAccountPageContentFragment,
) {
storefrontQueryRoot.customer?.let {
        Text(text = it.displayName)
        
    }
}
generated code
Copy code
public interface StorefrontAccountPageContentFragment {
  /**
   * The customer associated with the given access token. Tokens are obtained by using the
   * [`customerAccessTokenCreate`
   * mutation](<https://shopify.dev/docs/api/storefront/latest/mutations/customerAccessTokenCreate>).
   */
  public val customer: Customer?

  public interface Customer {
    /**
     * A unique ID for the customer.
     */
    public val id: ShopifyApiId

    /**
     * The customer's name, email or phone number.
     */
    public val displayName: String
  }
}
// // AUTO-GENERATED FILE. DO NOT MODIFY. // // This class was automatically generated by Apollo GraphQL version '4.0.0'. // public data class StorefrontAccountPageQuery( public val customerAccessToken: String, public val countryCode: CountryCode, public val languageCode: LanguageCode, ) : Query<StorefrontAccountPageQuery.Data> { override fun id(): String = OPERATION_ID override fun document(): String = OPERATION_DOCUMENT override fun name(): String = OPERATION_NAME override fun serializeVariables( writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, withDefaultValues: Boolean, ) { StorefrontAccountPageQuery_VariablesAdapter.serializeVariables(writer, this, customScalarAdapters, withDefaultValues) } override fun adapter(): Adapter<Data> = StorefrontAccountPageQuery_ResponseAdapter.Data.obj() override fun rootField(): CompiledField = CompiledField.Builder( name = "data", type = CompiledQueryRoot.type ) .selections(selections = StorefrontAccountPageQuerySelections.__root) .build() @ApolloAdaptableWith(StorefrontAccountPageQuery_ResponseAdapter.Data::class) public data class Data( public val __typename: String, /** * The customer associated with the given access token. Tokens are obtained by using the * [
customerAccessTokenCreate
* mutation](https://shopify.dev/docs/api/storefront/latest/mutations/customerAccessTokenCreate). */ override val customer: Customer?, ) : StorefrontAccountPageContentFragment, Query.Data { public companion object { @Suppress("USELESS_CAST") public fun Data.storefrontAccountPageContentFragment(): StorefrontAccountPageContentFragment? = this as? StorefrontAccountPageContentFragment } } public data class Customer( /** * A unique ID for the customer. */ override val id: ShopifyApiId, /** * The customer's name, email or phone number. */ override val displayName: String, ) : StorefrontAccountPageContentFragment.Customer public companion object { public const val OPERATION_ID: String = "4cbe5927a1b420f403182e7fa4377ffd48f5608792a8341ae2fb214caa64ec74" /** * The minimized GraphQL document being sent to the server to save a few bytes. * The un-minimized version is: * *
Copy code
* query StorefrontAccountPage($customerAccessToken: String!, $countryCode: CountryCode!,
     * $languageCode: LanguageCode!) @inContext(country: $countryCode, language: $languageCode) {
     *   __typename
     *   ...StorefrontAccountPageContentFragment
     * }
     *
     * fragment StorefrontAccountPageContentFragment on QueryRoot {
     *   customer(customerAccessToken: $customerAccessToken) {
     *     id
     *     displayName
     *   }
     * }
     *
*/ public val OPERATION_DOCUMENT: String get() = "query StorefrontAccountPage(${'$'}customerAccessToken: String!, ${'$'}countryCode: CountryCode!, ${'$'}languageCode: LanguageCode!) @inContext(country: ${'$'}countryCode, language: ${'$'}languageCode) { __typename ...StorefrontAccountPageContentFragment } fragment StorefrontAccountPageContentFragment on QueryRoot { customer(customerAccessToken: ${'$'}customerAccessToken) { id displayName } }" public const val OPERATION_NAME: String = "StorefrontAccountPage" } } ``````
b
Thanks for sharing! In general, you may have multiple possible shapes due to polymorphism, e.g.
Copy code
{
  pet {
    ...CatFields
    ...DogFields
  }
}
which will result in a
Pet
interface and
CatPet
and
DogPet
(and
OtherPet
) implementations. The accessors
pet.catFields()
,
pet.dogFields()
are there for convenience and will return
null
if the pet is not of this type. In your case there is only one possible type, so it will never return
null
. You can even just pass
storefrontQuery.data
since it is already a
StorefrontAccountPageContentFragment
. Usually, you should use a
when
to know which case you’re in, instead of testing
null
from the accessors:
Copy code
when (pet) {
  is CatPet -> {}
  is DogPet -> {}
  else -> {}
}
PS: it could be a small improvement for the codegen to generate a non nullable accessor when there’s only one possible case like here - if you’d like to see this, don’t hesitate to open an issue!
k
Copy code
AccountPageContent(
    storefrontQueryRoot = storefrontQuery.data,
The compilation error above has been resolved.
Copy code
query StorefrontAccountPage($customerAccessToken: String!, $countryCode: CountryCode!, $languageCode: LanguageCode!) @inContext(country: $countryCode, language: $languageCode) {
    ... StorefrontAccountPageContentFragment
    ... StorefrontAccountPageContentFragment2
}

fragment StorefrontAccountPageContentFragment on QueryRoot {
    customer(customerAccessToken: $customerAccessToken) {
        id
        ... MemberCardFragment
    }
}
fragment StorefrontAccountPageContentFragment2 on QueryRoot {
    customer(customerAccessToken: $customerAccessToken) {
        id
    }
}
Copy code
when(storefrontQuery.data) {
   is StorefrontAccountPageContentFragment -> null
   is StorefrontAccountPageContentFragment2 -> null
   else -> null
}
However, I don’t understand the
when
. Both conditions in this code will always be true.
b
that's because they're both on
QueryRoot
it will be useful only in cases where your schema has e.g. an interface and multiple types implementing it
k
Copy code
{
  pet {
    ...CatFields
    ...DogFields
  }
}
Copy code
when (pet) {
  is CatPet -> {}
  is DogPet -> {}
  else -> {}
}
So, is my understanding correct that in the example above, both conditions will be true?
b
Well, this is the schema I had in mind: Schema
Copy code
interface Pet {
  id: ID!
}

type Cat implements Pet {
  id: ID!
  whiskers: Int!
}

type Dog implements Pet {
  id: ID!
  woof: Boolean
}

type Query {
  pet: Pet!
}
Fragments:
Copy code
fragment CatFields on Cat {
  whiskers
}

fragment DogFields on Dog {
  woof
}
In that case, only one condition should be true
k
I now understand that it is effective in the case of a GraphQL interface (and possibly union?). Thank you so much for your detailed response!
🙏 1
b
yes it's the same with unions 🙂
👍 1