Skip to content
Retrofit converter which uses annotations to inject .graphql query or mutation files into a request body, along with any variables.
Branch: master
Clone or download
Pull request Compare This branch is 11 commits behind AniTrend:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.github
.idea
app
gradle/wrapper
images
library
.gitignore
.travis.yml
CODE_OF_CONDUCT.md
CONTRIBUTING.md
LICENSE
README.md
build.gradle
gradle.properties
gradlew
gradlew.bat
settings.gradle

README.md

Retrofit Converter - With GraphQL Support  

This is a retrofit converter which uses annotations to inject .graphql query or mutation files into a request body along with any GraphQL variables. The included example makes use of GitHunt GraphQL API that sometimes responds with null fields, feel free to try it with any other GraphQL API like GitHub API v4 also this project does not teach you how to use Retrofit, Glide or the ViewModel.

Why This Project Exists?

Many might wonder why this exists when an android GraphQL library like Apollo exists. Unfortunately Apollo for Android still lacks some basic but important features/functionality which led to the following questions about General Design Questions Regarding Apollo, Polymorphic Type Handling and Non Shared Types. Don't get me wrong Apollo is not inferior any way, it has amazing features such as:

  • Code Generation (Classes and Data Types)
  • Custom Scalar Types
  • Cached Responses

But since model classes are automatically generated for you, the developer loses some flexibility, such as use of generics, abstraction and inheritance. Also Android Peformance best practice suggests that developers should use StringDef and IntDef over enums and here's why.

Strangly there are tons of simple examples all over Medium using apollo graphql for Android, but none of them address these issues because most of them just construct a simple single resource request demo application. These look just fine at first glance until you start working with multiple data types and apollo starts generating classes for every fragment and query even if the data models are the same, or share similar properties. Thus this project came to be


How Everything Works

Seeing how we already have a really powerful type-safe HTTP client for Android and Java Retrofit why not use it and extend it's functionality?

For a detailed example please clone the project and look at the included sample application. The entire project & example app makes use of the following libraries:

The Basics

Firstly you'll need some to save your GraphQL queries and mutations into .graphql files, You can use this tool to generate your Insomnia workspaces into directories and files insomnia-graphql-generator and place these files into your assets folder as shown below:

  • Add the JitPack repository to your build file
allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}
  • Add the dependency
dependencies {
    implementation 'com.github.AniTrend:retrofit-graphql:{latest_version}'
}

Next we make our retrofit interfaces and annotate them with the @GraphQuery annotation using the name of the .graphql file without the extention, this will allow the runtime resolution of the target file inside your assets to be loaded before the request is sent. e.g.

    @POST("graphql")
    @GraphQuery("Trending")
    @Headers("Content-Type: application/json")
    fun getTrending(@Body request: QueryContainerBuilder): Call<GraphContainer<TrendingFeed>>

    @POST("graphql")
    @GraphQuery("RepoEntries")
    @Headers("Content-Type: application/json")
    fun getRepoEntries(@Body request: QueryContainerBuilder): Call<GraphContainer<EntryFeed>>

Models

The model creation is upto the developer, this is where retrofit-graphql differs from apollo, this way you can design your models in anyway you desire. By default the library supplies you with a QueryContainerBuilder which is a holder for your GraphQL variables and request. Also two basic top level models, which you don't have to use if you want to design your own:

QueryContainerBuilder

Suggest using this as is, but if you want to make your own that's not a problem either. The QueryContainerBuilder is used as follows:

Given a .graphql files such as the following:

query Trending($type: FeedType!, $offset: Int, $limit: Int) {
  feed(type: $type, offset: $offset, limit: $limit) {
    id
    hotScore
    repository {
      ...repository
    }
    postedBy {
      ...user
    }
  }
}

Adding parameters to the request would be done as follows:

val queryBuilder = QueryContainerBuilder()
            .putVariable("type", "TRENDING")
            .putVariable("offset", 1)
            .putVariable("limit", 15);

The QueryContainerBuilder is then passed into your retrofit interface method as parameter and that's it! Just like an ordinary retrofit application.

GraphError

Common GraphQL error that makes use of extension functions

/**
 * Converts the response error response into an object.
 *
 * @return The error object, or null if an exception was encountered
 * @see Error
 */
fun Response<*>?.getError(): List<GraphError>? {
    try {
        if (this != null) {
            val responseBody = errorBody()
            val message = responseBody?.string()
            if (responseBody != null && !message.isNullOrBlank()) {
                val graphErrors= message.getGraphQLError()
                if (graphErrors != null)
                    return graphErrors
            }
        }
    } catch (ex: Exception) {
        ex.printStackTrace()
    }
    return null
}

private fun String.getGraphQLError(): List<GraphError>? {
    Log.e("GraphErrorUtil", this)
    val tokenType = object : TypeToken<GraphContainer<*>>() {}.type
    val graphContainer = Gson().fromJson<GraphContainer<*>>(this, tokenType)
    return graphContainer.errors
}
GraphContainer

Similar to the top level GraphQL response, but the data type is generic to allow easy reuse.

data class GraphContainer<T>(
        val data: T?,
        val errors: List<GraphError>?
) { fun isEmpty(): Boolean = data == null }

Working Example

Check the example project named app for a more extensive overview of how everything works

The Result

   

Proof Of Concept?

This project is derived from AniTrend which is already published on the PlayStore

You can’t perform that action at this time.