$30 off During Our Annual Pro Sale. View Details »

Take Control Of Your APIs With GraphQL

Take Control Of Your APIs With GraphQL

Introducing GraphQL APIs and the benefits of using them, and how to interface with these APIs on Android.

Adam McNeilly

April 21, 2020
Tweet

More Decks by Adam McNeilly

Other Decks in Programming

Transcript

  1. Take Control Of Your APIs With
    GraphQL
    Adam McNeilly - @AdamMc331
    @AdamMc331
    #AndroidMakers 1

    View Slide

  2. What Is GraphQL?
    @AdamMc331
    #AndroidMakers 2

    View Slide

  3. What Is Why Use GraphQL?
    @AdamMc331
    #AndroidMakers 3

    View Slide

  4. Let's Examine An Existing API
    @AdamMc331
    #AndroidMakers 4

    View Slide

  5. PokeAPI1
    // https://pokeapi.co/api/v2/pokemon/
    {
    "count": 964,
    "next": "https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20",
    "previous": null,
    "results": [
    {
    "name": "bulbasaur",
    "url": "https://pokeapi.co/api/v2/pokemon/1/"
    },
    {
    "name": "ivysaur",
    "url": "https://pokeapi.co/api/v2/pokemon/2/"
    },
    {
    "name": "venusaur",
    "url": "https://pokeapi.co/api/v2/pokemon/3/"
    }
    ]
    }
    1 https://pokeapi.co/
    @AdamMc331
    #AndroidMakers 5

    View Slide

  6. PokeAPI1
    // https://pokeapi.co/api/v2/pokemon/squirtle
    // Originally 11,000 lines
    {
    "abilities": [],
    "base_experience": 63,
    "forms": [],
    "game_indices": [],
    "height": 5,
    "held_items": [],
    "id": 7,
    "is_default": true,
    "location_area_encounters": "https://pokeapi.co/api/v2/pokemon/7/encounters",
    "moves": [],
    "name": "squirtle",
    "order": 10,
    "species": {},
    "sprites": {},
    "stats": [],
    "types": [],
    "weight": 90
    }
    1 https://pokeapi.co/
    @AdamMc331
    #AndroidMakers 6

    View Slide

  7. What Are The Drawbacks Of REST APIs?
    @AdamMc331
    #AndroidMakers 7

    View Slide

  8. What Are The Drawbacks Of REST APIs?
    • Responses may not have enough data
    @AdamMc331
    #AndroidMakers 7

    View Slide

  9. What Are The Drawbacks Of REST APIs?
    • Responses may not have enough data
    • Responses may have too much data
    @AdamMc331
    #AndroidMakers 7

    View Slide

  10. What Are The Drawbacks Of REST APIs?
    • Responses may not have enough data
    • Responses may have too much data
    • Responses are not controlled by the client
    @AdamMc331
    #AndroidMakers 7

    View Slide

  11. What Are The Drawbacks Of REST APIs?
    • Responses may not have enough data
    • Responses may have too much data
    • Responses are not controlled by the client
    • Any changes to responses have to go through another developer
    @AdamMc331
    #AndroidMakers 7

    View Slide

  12. Let's Examine A GraphQL API 2
    2 https://github.com/lucasbento/graphql-pokemon
    @AdamMc331
    #AndroidMakers 8

    View Slide

  13. GraphQL Query Syntax
    {
    pokemon(name: "bulbasaur") {
    // Put All Fields Here
    }
    }
    @AdamMc331
    #AndroidMakers 9

    View Slide

  14. Only Get What You Ask For
    // Query
    {
    pokemon(name: "bulbasaur") {
    name
    }
    }
    // Response
    {
    "data": {
    "pokemon": {
    "name": "Bulbasaur"
    }
    }
    }
    @AdamMc331
    #AndroidMakers 10

    View Slide

  15. Another Example
    // Query
    {
    pokemon(name: "bulbasaur") {
    name
    number
    }
    }
    // Response
    {
    "data": {
    "pokemon": {
    "name": "Bulbasaur",
    "number": "001"
    }
    }
    }
    @AdamMc331
    #AndroidMakers 11

    View Slide

  16. What Are The Benefits Of GraphQL APIs?
    @AdamMc331
    #AndroidMakers 12

    View Slide

  17. What Are The Benefits Of GraphQL APIs?
    • Responses have just enough data
    @AdamMc331
    #AndroidMakers 12

    View Slide

  18. What Are The Benefits Of GraphQL APIs?
    • Responses have just enough data
    • Responses are controlled by the client
    @AdamMc331
    #AndroidMakers 12

    View Slide

  19. What Are The Benefits Of GraphQL APIs?
    • Responses have just enough data
    • Responses are controlled by the client
    • Any changes to responses can be done by the client
    @AdamMc331
    #AndroidMakers 12

    View Slide

  20. Understanding GraphQL
    @AdamMc331
    #AndroidMakers 13

    View Slide

  21. GraphiQL Interface
    @AdamMc331
    #AndroidMakers 14

    View Slide

  22. Documentation Explorer
    @AdamMc331
    #AndroidMakers 15

    View Slide

  23. Documentation Explorer
    • ! Indicates non-nullable
    @AdamMc331
    #AndroidMakers 15

    View Slide

  24. Documentation Explorer
    • ! Indicates non-nullable
    • Brackets are array syntax
    @AdamMc331
    #AndroidMakers 15

    View Slide

  25. Nested Objects
    @AdamMc331
    #AndroidMakers 16

    View Slide

  26. GraphQL On Android
    @AdamMc331
    #AndroidMakers 17

    View Slide

  27. Apollo GraphQL3
    3 https://github.com/apollographql/apollo-android
    @AdamMc331
    #AndroidMakers 18

    View Slide

  28. Adding Dependencies
    @AdamMc331
    #AndroidMakers 19

    View Slide

  29. Create A GraphQL Directory
    @AdamMc331
    #AndroidMakers 20

    View Slide

  30. Defining Your Schema
    @AdamMc331
    #AndroidMakers 21

    View Slide

  31. Introspection Query 4
    4 https://graphqlmastery.com/blog/graphql-introspection-and-introspection-queries
    @AdamMc331
    #AndroidMakers 22

    View Slide

  32. Introspection Query 4
    • A query that asks for meta data about a GraphQL Schema
    4 https://graphqlmastery.com/blog/graphql-introspection-and-introspection-queries
    @AdamMc331
    #AndroidMakers 22

    View Slide

  33. Introspection Query 4
    • A query that asks for meta data about a GraphQL Schema
    • Will return all of the possible queries and mutations
    4 https://graphqlmastery.com/blog/graphql-introspection-and-introspection-queries
    @AdamMc331
    #AndroidMakers 22

    View Slide

  34. Introspection Query 4
    • A query that asks for meta data about a GraphQL Schema
    • Will return all of the possible queries and mutations
    • Will return all of the models in a graph
    4 https://graphqlmastery.com/blog/graphql-introspection-and-introspection-queries
    @AdamMc331
    #AndroidMakers 22

    View Slide

  35. Introspection Query 4
    • A query that asks for meta data about a GraphQL Schema
    • Will return all of the possible queries and mutations
    • Will return all of the models in a graph
    • Foot note has a deep dive into these queries
    4 https://graphqlmastery.com/blog/graphql-introspection-and-introspection-queries
    @AdamMc331
    #AndroidMakers 22

    View Slide

  36. Run Introspection Query
    @AdamMc331
    #AndroidMakers 23

    View Slide

  37. Copy Result
    @AdamMc331
    #AndroidMakers 24

    View Slide

  38. Paste Into schema.json
    @AdamMc331
    #AndroidMakers 25

    View Slide

  39. Writing Your Queries
    @AdamMc331
    #AndroidMakers 26

    View Slide

  40. Create A [something].graphql File
    // pokemonqueries.graphql
    // same directory as our schema.json file
    query PokemonQuery($first: Int!) {
    pokemonList: pokemons(first: $first) {
    name
    types
    image
    }
    }
    @AdamMc331
    #AndroidMakers 27

    View Slide

  41. Build Our Project
    @AdamMc331
    #AndroidMakers 28

    View Slide

  42. Generated Java Classes For Our Queries
    @AdamMc331
    #AndroidMakers 29

    View Slide

  43. Write A Query From Our Code
    val query = PokemonQuery.builder()
    .first(DEFAULT_LIMIT)
    .build()
    val callback = object : ApolloCall.Callback() {
    override fun onFailure(e: ApolloException) {
    Log.e("ApolloService", e.message, e)
    }
    override fun onResponse(response: Response) {
    val pokemonList = response.data()
    ?.pokemonList()
    ?.map { apolloPokemon ->
    val name = apolloPokemon.name()
    val typeNames = apolloPokemon.types()
    val image = apolloPokemon.image()
    }
    }
    }
    apolloClient.query(query).enqueue(callback)
    @AdamMc331
    #AndroidMakers 30

    View Slide

  44. Write A Query From Our Code
    val query = PokemonQuery.builder()
    .first(DEFAULT_LIMIT)
    .build()
    val callback = object : ApolloCall.Callback() {
    override fun onFailure(e: ApolloException) {
    Log.e("ApolloService", e.message, e)
    }
    override fun onResponse(response: Response) {
    val pokemonList = response.data()
    ?.pokemonList()
    ?.map { apolloPokemon ->
    val name = apolloPokemon.name()
    val typeNames = apolloPokemon.types()
    val image = apolloPokemon.image()
    }
    }
    }
    apolloClient.query(query).enqueue(callback)
    @AdamMc331
    #AndroidMakers 30

    View Slide

  45. Write A Query From Our Code
    val query = PokemonQuery.builder()
    .first(DEFAULT_LIMIT)
    .build()
    val callback = object : ApolloCall.Callback() {
    override fun onFailure(e: ApolloException) {
    Log.e("ApolloService", e.message, e)
    }
    override fun onResponse(response: Response) {
    val pokemonList = response.data()
    ?.pokemonList()
    ?.map { apolloPokemon ->
    val name = apolloPokemon.name()
    val typeNames = apolloPokemon.types()
    val image = apolloPokemon.image()
    }
    }
    }
    apolloClient.query(query).enqueue(callback)
    @AdamMc331
    #AndroidMakers 30

    View Slide

  46. Write A Query From Our Code
    val query = PokemonQuery.builder()
    .first(DEFAULT_LIMIT)
    .build()
    val callback = object : ApolloCall.Callback() {
    override fun onFailure(e: ApolloException) {
    Log.e("ApolloService", e.message, e)
    }
    override fun onResponse(response: Response) {
    val pokemonList = response.data()
    ?.pokemonList()
    ?.map { apolloPokemon ->
    val name = apolloPokemon.name()
    val typeNames = apolloPokemon.types()
    val image = apolloPokemon.image()
    }
    }
    }
    apolloClient.query(query).enqueue(callback)
    @AdamMc331
    #AndroidMakers 30

    View Slide

  47. Write A Query From Our Code
    val query = PokemonQuery.builder()
    .first(DEFAULT_LIMIT)
    .build()
    val callback = object : ApolloCall.Callback() {
    override fun onFailure(e: ApolloException) {
    Log.e("ApolloService", e.message, e)
    }
    override fun onResponse(response: Response) {
    val pokemonList = response.data()
    ?.pokemonList()
    ?.map { apolloPokemon ->
    val name = apolloPokemon.name()
    val typeNames = apolloPokemon.types()
    val image = apolloPokemon.image()
    }
    }
    }
    apolloClient.query(query).enqueue(callback)
    @AdamMc331
    #AndroidMakers 30

    View Slide

  48. Understanding The Generated Code
    @AdamMc331
    #AndroidMakers 31

    View Slide

  49. Understanding The Generated Code
    @AdamMc331
    #AndroidMakers 32

    View Slide

  50. Understanding The Generated Code
    @AdamMc331
    #AndroidMakers 33

    View Slide

  51. Understanding The Generated Code
    @AdamMc331
    #AndroidMakers 34

    View Slide

  52. Creating An Apollo Client
    val okHttpClient = OkHttpClient.Builder()
    .build()
    val apolloClient = ApolloClient.builder()
    .serverUrl("https://graphql-pokemon.now.sh/")
    .okHttpClient(okHttpClient)
    .build()
    @AdamMc331
    #AndroidMakers 35

    View Slide

  53. You Wrote Your First GraphQL Query
    @AdamMc331
    #AndroidMakers 36

    View Slide

  54. Going Further With Apollo
    @AdamMc331
    #AndroidMakers 37

    View Slide

  55. Use Kotlin Models
    // build.gradle or build.gradle.kts
    apollo {
    generateKotlinModels.set(true) // or false for Java models
    }
    @AdamMc331
    #AndroidMakers 38

    View Slide

  56. Query Code Is More Modern
    // repository.kt
    // No builder pattern
    val query = PokemonQuery(first = DEFAULT_LIMIT)
    // Use fields not methods
    val name = apolloPokemon.name
    val typeNames = apolloPokemon.types
    val image = apolloPokemon.image
    @AdamMc331
    #AndroidMakers 39

    View Slide

  57. GraphQL Fragments
    @AdamMc331
    #AndroidMakers 40

    View Slide

  58. Queries With Duplicated Code
    query PokemonQuery($first: Int!) {
    pokemonList: pokemons(first: $first) {
    name
    types
    image
    }
    }
    query PokemonDetailQuery($pokemonName: String) {
    pokemon: pokemon(name: $pokemonName) {
    name
    types
    image
    }
    }
    @AdamMc331
    #AndroidMakers 41

    View Slide

  59. Share Information With A Fragment
    fragment ApolloPokemon on Pokemon {
    name
    types
    image
    }
    query PokemonQuery($first: Int!) {
    pokemonList: pokemons(first: $first) {
    ...ApolloPokemon
    }
    }
    query PokemonDetailQuery($pokemonName: String) {
    pokemon: pokemon(name: $pokemonName) {
    ...ApolloPokemon
    }
    }
    @AdamMc331
    #AndroidMakers 42

    View Slide

  60. Migrating To GraphQL
    @AdamMc331
    #AndroidMakers 43

    View Slide

  61. A Common Problem
    /**
    * This class has a direct dependency on a REST API.
    * It returns a data class we defined.
    * Not the data class generated by Apollo.
    * This makes swapping them difficult.
    */
    class PokemonListViewModel(
    private val restApi: PokemonAPI
    ) : ViewModel() {
    @AdamMc331
    #AndroidMakers 44

    View Slide

  62. Leverage Repository Interfaces
    interface PokemonRepository {
    fun getPokemonList(): PokemonListResponse?
    fun getPokemonDetail(pokemonName: String): Pokemon?
    }
    class PokemonListViewModel(
    private val repository: PokemonRepository
    ) : ViewModel() {
    @AdamMc331
    #AndroidMakers 45

    View Slide

  63. Create Multiple Implementations
    class RetrofitService(): PokemonRepository { }
    class ApolloService(): PokemonRepository { }
    @AdamMc331
    #AndroidMakers 46

    View Slide

  64. Allows You To A/B Test Your New API
    private fun getRepository(): PokemonRepository {
    if (isInGraphQLTestGroup) {
    return ApolloService()
    } else {
    return RetrofitService()
    }
    }
    @AdamMc331
    #AndroidMakers 47

    View Slide

  65. Why A/B Test A New API?
    @AdamMc331
    #AndroidMakers 48

    View Slide

  66. Why A/B Test A New API?
    • You can monitor performance of a page using different APIs
    @AdamMc331
    #AndroidMakers 48

    View Slide

  67. Why A/B Test A New API?
    • You can monitor performance of a page using different APIs
    • This is a big change, you should ship with confidence
    @AdamMc331
    #AndroidMakers 48

    View Slide

  68. Why A/B Test A New API?
    • You can monitor performance of a page using different APIs
    • This is a big change, you should ship with confidence
    • You won't need to do this for every page, but is helpful for your
    first page to use GraphQL
    @AdamMc331
    #AndroidMakers 48

    View Slide

  69. Dealing With Generated Code
    @AdamMc331
    #AndroidMakers 49

    View Slide

  70. Our Repository Doesn't Return Generated
    Code
    interface PokemonRepository {
    fun getPokemonList(): PokemonListResponse?
    fun getPokemonDetail(pokemonName: String): Pokemon?
    }
    @AdamMc331
    #AndroidMakers 50

    View Slide

  71. Leverage Kotlin Extension Functions
    // pokemonqueries.graphql
    fragment ApolloPokemon on Pokemon {
    name
    types
    image
    }
    // ApolloService.kt
    private fun ApolloPokemon.toPokemon(): Pokemon {
    return Pokemon(
    name = this.name.orEmpty(),
    firstType = this.types?.getOrNull(0),
    secondType = this.types?.getOrNull(1),
    frontSpriteUrl = this.image
    )
    }
    @AdamMc331
    #AndroidMakers 51

    View Slide

  72. Makes The Parsing Of Requests Cleaner
    val callback = object : ApolloCall.Callback() {
    override fun onFailure(e: ApolloException) {
    Log.e("ApolloService", e.message, e)
    }
    override fun onResponse(response: Response) {
    val pokemonList = response.data()
    ?.pokemonList
    ?.filterNotNull()
    ?.map { apolloPokemon ->
    val pokemon = apolloPokemon.fragments.apolloPokemon.toPokemon()
    }
    }
    }
    @AdamMc331
    #AndroidMakers 52

    View Slide

  73. Consuming Apollo API Requests
    @AdamMc331
    #AndroidMakers 53

    View Slide

  74. Callbacks
    val callback = object : ApolloCall.Callback() {
    override fun onFailure(e: ApolloException) {
    // ...
    }
    override fun onResponse(response: Response) {
    // ...
    }
    }
    @AdamMc331
    #AndroidMakers 54

    View Slide

  75. RxJava25
    val query = PokemonQuery(first = DEFAULT_LIMIT)
    apolloClient.rxQuery(query)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe {
    // ...
    }
    5 https://www.apollographql.com/docs/android/advanced/rxjava2/
    @AdamMc331
    #AndroidMakers 55

    View Slide

  76. Coroutines6
    override suspend fun getPokemon(): PokemonResponse {
    val query = PokemonQuery(first = DEFAULT_LIMIT)
    val response = apolloClient.query(query).toDeferred().await()
    }
    6 https://www.apollographql.com/docs/android/advanced/coroutines/
    @AdamMc331
    #AndroidMakers 56

    View Slide

  77. One Last Suggestion
    @AdamMc331
    #AndroidMakers 57

    View Slide

  78. JS GraphQL Plugin7
    7 https://jimkyndemeyer.github.io/js-graphql-intellij-plugin/
    @AdamMc331
    #AndroidMakers 58

    View Slide

  79. Thank You!
    @AdamMc331
    #AndroidMakers 59

    View Slide