The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Interfaces, Unions, and Inline Fragments

Earlier in the book, we introduced the concept of interfaces and unions. To recap:

The problem in both cases is that we do not know, at the time of executing a query, what we are going to get back, in terms of types. Both interface and union fields can hold any candidate type, and so our results might well have a mix of different types in the results. However, not all of the fields of those types are necessarily in common:

So… if we are trying to get data back from interface or union fields, and we do not know when we are writing the query what we are going to get back, how do we ask for those fields’ contents?

The answer lies in what are known as “inline fragments”. These effectively amount to a Java switch statement, identifying field sets to return for an interface or union type, based on the actual type for a given field.

So, let’s dive into how we actually query on interfaces and unions, so we can see inline fragments in action.

Interfaces

As was noted earlier in the book, in terms of our “trips” sample, we have an interface named Plan:

GraphiQL Documentation, Showing Plan
Figure 34: GraphiQL Documentation, Showing Plan

There are three types that implement Plan: Trip, Lodging (representing something like a hotel stay), and Flight. And, a Trip has a plans field that is a list of Plan objects, representing the individual elements in that trip’s itinerary. Those objects could be Lodging objects, Flight objects, or even other Trip objects (e.g., for modeling a complex trip as a series of sub-trips).

So, if we want to get the plans for a Trip, how do we do that?

Fields In Common

We can easily query for the fields that are in the Plan interface, as all three Plan implementations will have those fields. We did this, in effect, back in the chapter on fragments. There, we defined a tripFields fragment, saying that the fields were from Trip. In reality, all those fields are defined on Plan. So, we can revise the fragment to reference Plan instead of Trip, and now we can use that fragment for querying a trip’s plans:

query getAllTrips {
  allTrips {
    ...planFields
    plans {
      ...planFields
    }
  }
}

fragment planFields on Plan {
  id
  title
  startTime
  priority
  duration
  creationTime
}

Running this against the public GraphQL demo server will give you something like:

{
  "data": {
    "allTrips": [
      {
        "id": "2c494055-78bc-430c-9ab7-19817f3fc060",
        "title": "Vacation!",
        "startTime": "2017-12-20T13:14:00-05:00",
        "priority": "MEDIUM",
        "duration": 10080,
        "creationTime": "2017-02-19T15:21:58.547Z",
        "plans": [
          {
            "id": "319185bd-fab0-49e3-86ce-251d2aaa5d23",
            "title": "Flight to Chicago",
            "startTime": "2017-12-20T13:14:00-05:00",
            "priority": "HIGH",
            "duration": 150,
            "creationTime": "2017-02-19T15:21:58.547Z"
          },
          {
            "id": "319185bd-fab0-49e3-86ce-251d2aaa5d23",
            "title": "House of Munster",
            "startTime": "2017-12-20T15:00:00-05:00",
            "priority": "MEDIUM",
            "duration": 9900,
            "creationTime": "2017-02-19T15:21:58.547Z"
          }
        ]
      },
      {
        "id": "e323fed5-6805-4bcf-8cb6-8b7a5014a9d9",
        "title": "Business Trip",
        "startTime": "2018-01-14T11:45:00-05:00",
        "priority": "HIGH",
        "duration": 4320,
        "creationTime": "2017-02-19T15:21:58.547Z",
        "plans": [
          {
            "id": "d40eb2e7-3211-422e-858c-403cbe3fa680",
            "title": "Flight to Denver",
            "startTime": "2018-01-14T11:45:00-05:00",
            "priority": "HIGH",
            "duration": 257,
            "creationTime": "2017-02-19T15:21:58.547Z"
          },
          {
            "id": "e28a591b-cdc9-4328-9e79-9e4ed60ae7d2",
            "title": "Hotel Von",
            "startTime": "2018-01-14T15:00:00-05:00",
            "priority": "MEDIUM",
            "duration": 4140,
            "creationTime": "2017-02-19T15:21:58.547Z"
          }
        ]
      }
    ]
  }
}

Distinct Fields

But, different Plan implementations also have distinct fields:

We could just try asking for airlineCode all the time:

query getAllTrips {
  allTrips {
    ...planFields
    plans {
      ...planFields
      airlineCode
    }
  }
}

fragment planFields on Plan {
  id
  title
  startTime
  priority
  duration
  creationTime
}

However, this results in an error, since not every Plan has an airlineCode:

{
  "errors": [
    {
      "message": "Cannot query field \"airlineCode\" on type \"Plan\". Did you mean to use an inline fragment on \"Flight\"?",
      "locations": [
        {
          "line": 6,
          "column": 7
        }
      ]
    }
  ]
}

So, we need some way to tell the GraphQL server to give us airlineCode, but only for Flight objects, not for Trip and Lodging objects. That is where inline fragments come into play, as is hinted at by the error message.

The “inline” of “inline fragments” refers to having the fragment be defined directly in the query or mutation, rather than being declared separately the way that planFields is in the GraphQL documents shown earlier in this chapter.

We could rewrite that first GraphQL document to use an inline fragment:

query getAllTrips {
  allTrips {
    ... on Plan {
      id
      title
      startTime
      priority
      duration
      creationTime
    }
    plans {
      ... on Plan {
        id
        title
        startTime
        priority
        duration
        creationTime
      }
    }
  }
}

Note that inline fragments are not named, akin to how lambdas or anonymous inner classes in Java are not named. The JSON output does not change, because the fragment names never appear in the JSON output anyway. However, this does not really solve our problem, in that we are still limiting ourselves to common fields on Plan. Plus, we are duplicating field references now.

The key to inline fragments, for use with interfaces and unions, is that for objects not matching the fragment’s type, that fragment is merely skipped, rather than generating an error.

So, for example, we can grab the unique fields from Flight using an inline fragment, affecting only those plans that are Flight objects:

query getAllTrips {
  allTrips {
    ...planFields
    plans {
      ...planFields
      ... on Flight {
        airlineCode
        flightNumber
        departingAirport
        arrivingAirport
      }
    }
  }
}

fragment planFields on Plan {
  id
  title
  startTime
  priority
  duration
  creationTime
}

Here, for any object in plans that is a Flight, in addition to the planFields, we also retrieve four fields defined on Flight. For any object in plans that is not a Flight, the Flight inline fragment is ignored. The resulting JSON gives us a mix of objects with full Flight details and those with the planFields subset:

{
  "data": {
    "allTrips": [
      {
        "id": "2c494055-78bc-430c-9ab7-19817f3fc060",
        "title": "Vacation!",
        "startTime": "2017-12-20T13:14:00-05:00",
        "priority": "MEDIUM",
        "duration": 10080,
        "creationTime": "2017-02-19T15:21:58.547Z",
        "plans": [
          {
            "id": "319185bd-fab0-49e3-86ce-251d2aaa5d23",
            "title": "Flight to Chicago",
            "startTime": "2017-12-20T13:14:00-05:00",
            "priority": "HIGH",
            "duration": 150,
            "creationTime": "2017-02-19T15:21:58.547Z",
            "airlineCode": "UAL",
            "flightNumber": "321",
            "departingAirport": "EWR",
            "arrivingAirport": "ORD"
          },
          {
            "id": "319185bd-fab0-49e3-86ce-251d2aaa5d23",
            "title": "House of Munster",
            "startTime": "2017-12-20T15:00:00-05:00",
            "priority": "MEDIUM",
            "duration": 9900,
            "creationTime": "2017-02-19T15:21:58.547Z"
          }
        ]
      },
      {
        "id": "e323fed5-6805-4bcf-8cb6-8b7a5014a9d9",
        "title": "Business Trip",
        "startTime": "2018-01-14T11:45:00-05:00",
        "priority": "HIGH",
        "duration": 4320,
        "creationTime": "2017-02-19T15:21:58.547Z",
        "plans": [
          {
            "id": "d40eb2e7-3211-422e-858c-403cbe3fa680",
            "title": "Flight to Denver",
            "startTime": "2018-01-14T11:45:00-05:00",
            "priority": "HIGH",
            "duration": 257,
            "creationTime": "2017-02-19T15:21:58.547Z",
            "airlineCode": "UAL",
            "flightNumber": "456",
            "departingAirport": "EWR",
            "arrivingAirport": "IAD"
          },
          {
            "id": "e28a591b-cdc9-4328-9e79-9e4ed60ae7d2",
            "title": "Hotel Von",
            "startTime": "2018-01-14T15:00:00-05:00",
            "priority": "MEDIUM",
            "duration": 4140,
            "creationTime": "2017-02-19T15:21:58.547Z"
          }
        ]
      }
    ]
  }
}

There is no limit to the number of inline fragments that you use, though the type of the inline fragment has to be a candidate for the field in question. In the case of plans, we can have inline fragments based on Plan or any type that implements the Plan interface, but we cannot have inline fragments for something that does not implement Plan, such as Comment:

query getAllTrips {
  allTrips {
    ...planFields
    plans {
      ...planFields
      ... on Flight {
        airlineCode
        flightNumber
        departingAirport
        arrivingAirport
      }
      ... on Comment {
        text
      }
    }
  }
}

fragment planFields on Plan {
  id
  title
  startTime
  priority
  duration
  creationTime
}

This results in an error:

{
  "errors": [
    {
      "message": "Fragment cannot be spread here as objects of type \"Plan\" can never be of type \"Comment\".",
      "locations": [
        {
          "line": 12,
          "column": 7
        }
      ]
    }
  ]
}

But we are welcome to get distinct fields both for Flight objects and for Lodging objects:

query getAllTrips {
  allTrips {
    ...planFields
    plans {
      ...planFields
      ... on Flight {
        airlineCode
        flightNumber
        departingAirport
        arrivingAirport
      }
      ... on Lodging {
        address
      }
    }
  }
}

fragment planFields on Plan {
  id
  title
  startTime
  priority
  duration
  creationTime
}

Now, Lodging objects give us address fields:

{
  "data": {
    "allTrips": [
      {
        "id": "2c494055-78bc-430c-9ab7-19817f3fc060",
        "title": "Vacation!",
        "startTime": "2017-12-20T13:14:00-05:00",
        "priority": "MEDIUM",
        "duration": 10080,
        "creationTime": "2017-02-19T15:21:58.547Z",
        "plans": [
          {
            "id": "319185bd-fab0-49e3-86ce-251d2aaa5d23",
            "title": "Flight to Chicago",
            "startTime": "2017-12-20T13:14:00-05:00",
            "priority": "HIGH",
            "duration": 150,
            "creationTime": "2017-02-19T15:21:58.547Z",
            "airlineCode": "UAL",
            "flightNumber": "321",
            "departingAirport": "EWR",
            "arrivingAirport": "ORD"
          },
          {
            "id": "319185bd-fab0-49e3-86ce-251d2aaa5d23",
            "title": "House of Munster",
            "startTime": "2017-12-20T15:00:00-05:00",
            "priority": "MEDIUM",
            "duration": 9900,
            "creationTime": "2017-02-19T15:21:58.547Z",
            "address": "1313 Mockingbird Lane, Springfield, IL, USA 62701"
          }
        ]
      },
      {
        "id": "e323fed5-6805-4bcf-8cb6-8b7a5014a9d9",
        "title": "Business Trip",
        "startTime": "2018-01-14T11:45:00-05:00",
        "priority": "HIGH",
        "duration": 4320,
        "creationTime": "2017-02-19T15:21:58.547Z",
        "plans": [
          {
            "id": "d40eb2e7-3211-422e-858c-403cbe3fa680",
            "title": "Flight to Denver",
            "startTime": "2018-01-14T11:45:00-05:00",
            "priority": "HIGH",
            "duration": 257,
            "creationTime": "2017-02-19T15:21:58.547Z",
            "airlineCode": "UAL",
            "flightNumber": "456",
            "departingAirport": "EWR",
            "arrivingAirport": "IAD"
          },
          {
            "id": "e28a591b-cdc9-4328-9e79-9e4ed60ae7d2",
            "title": "Hotel Von",
            "startTime": "2018-01-14T15:00:00-05:00",
            "priority": "MEDIUM",
            "duration": 4140,
            "creationTime": "2017-02-19T15:21:58.547Z",
            "address": "10 Backfield Place, Denver, CO 81023"
          }
        ]
      }
    ]
  }
}

In this fashion, we can get whatever fields we need, from whatever types might be in plans. In some cases, we may not need those distinct fields right away (e.g., building a list of the plans), so we grab the common fields on Plan and that’s it.

Unions

Working with unions is much the same as working with interfaces, except that there are no common fields. You have to use inline fragments for everything that you want to retrieve.

As was noted in a previous chapter, a Plan has a notes field, of type Note. A Note is a Union, consisting of two possible types: Comment and Link:

GraphiQL Documentation, Showing Note
Figure 35: GraphiQL Documentation, Showing Note

GraphiQL Documentation, Showing Comment
Figure 36: GraphiQL Documentation, Showing Comment

GraphiQL Documentation, Showing Link
Figure 37: GraphiQL Documentation, Showing Link

To be able to retrieve anything from notes, we need to use inline fragments, as there are no fields in common between Comment and Link:

query getAllTrips {
  allTrips {
    ...planFields
    notes {
      ... on Comment {
        text
      }
      ... on Link {
        url
        title
      }
    }
  }
}

fragment planFields on Plan {
  id
  title
  startTime
  priority
  duration
  creationTime
}

Here, we are retrieving the notes, specifically the text field from any Comment and the url and title fields from any Link. For trips with notes, we get those results:

{
  "data": {
    "allTrips": [
      {
        "id": "2c494055-78bc-430c-9ab7-19817f3fc060",
        "title": "Vacation!",
        "startTime": "2017-12-20T13:14:00-05:00",
        "priority": "MEDIUM",
        "duration": 10080,
        "creationTime": "2017-02-19T15:21:58.547Z",
        "notes": [
          {
            "text": "It's gonna be great!"
          },
          {
            "url": "http://www.miragrant.com/",
            "title": "Source of some reading material for the trip"
          }
        ]
      },
      {
        "id": "e323fed5-6805-4bcf-8cb6-8b7a5014a9d9",
        "title": "Business Trip",
        "startTime": "2018-01-14T11:45:00-05:00",
        "priority": "HIGH",
        "duration": 4320,
        "creationTime": "2017-02-19T15:21:58.547Z",
        "notes": null
      }
    ]
  }
}

Interfaces, Unions, and Apollo-Android

The preview of this section was lost in the sofa cushions.