Apollo 3.0 for working with GraphQL in a multi-module Android application

Let’s take a look at how to set up and use the latest version of the apollo client in a multi-module android application.

What is Apollo and when is it used?

Apollo Android is a GraphQL client that generates Kotlin models based on GraphQL queries. These models provide you with a type-safe API for working with GraphQL servers. Apollo helps you combine, organize, and easily access your GraphQL query statements.
Link to official Github: https://github.com/apollographql/apollo-android

We create a project, connect the plugin and dependencies.

Create a new project based on Empty Activity, I will name it ApolloConfig_Example

Creating a new project

Let’s create several modules, I will name them ‘common’, and ‘modules’, the latter in turn will contain ‘module_1’ and ‘module_2’

Project modules

We connect the JS GraphQL plugin – it adds language support, GraphQL syntax highlighting

JS GraphQL Plugin

We include the Apollo 3.0 plugin in all gradle files of our modules

id 'com.apollographql.apollo3' version("3.0.0-alpha07")
Apollo 3.0 plugin

After adding the apollo plugin, a message will appear about the need to synchronize the project, but before that you need to add settings for the plugin

For gradle module file ‘app’

apollo {
        //Устанавливает имя пакета куда будут помещены автоматически генерируемые классы.
        packageName.set("com.alab.app")
        //Устанавливает путь к вашей схеме (В данном случае указывает на файл в корне проекта).
        schemaFile.set(file("../schema.graphqls"))
    }

For gradle module file ‘common’

apollo {
        //Устанавливает имя пакета куда будут помещены автоматически генерируемые классы.
        packageName.set("com.alab.common")
        //Устанавливает путь к вашей схеме (В данном случае указывает на файл в корне проекта).
        schemaFile.set(file("../schema.graphqls"))
        //Устанавливает сопоставление скаляра из graphql схемы к вашему классу.
        customScalarsMapping = [
                "DateTime" : "java.util.Date"
        ]
    }

For gradle module file ‘module_1’ and ‘module_2’

apollo {
        //Устанавливает имя пакета куда будут помещены автоматически генерируемые классы.
        packageName.set("com.alab.module_1")
        //Устанавливает путь к вашей схеме (В данном случае указывает на файл в корне проекта).
        schemaFile.set(file("../../schema.graphqls"))
        //Устанавливает путь к пакету с файлами .qraphql, которые описывают ваши запросы.
        srcDir(file("src/main/java/com/alab/module_1/services/"))
        //Устанавливает сопоставление скаляра из graphql схемы к вашему классу.
        customScalarsMapping = [
                "DateTime" : "java.util.Date"
        ]
    }
apollo {
        //Устанавливает имя пакета куда будут помещены автоматически генерируемые классы.
        packageName.set("com.alab.module_2")
        //Устанавливает путь к вашей схеме (В данном случае указывает на файл в корне проекта).
        schemaFile.set(file("../../schema.graphqls"))
        //Устанавливает путь к пакету с файлами .qraphql, которые описывают ваши запросы.
        srcDir(file("src/main/java/com/alab/module_2/services/"))
        //Устанавливает сопоставление скаляра из graphql схемы к вашему классу.
        customScalarsMapping = [
                "DateTime" : "java.util.Date"
        ]
    }

Click the ‘Sync Now’ button

‘Sync Now’ button

Add dependencies to modules ‘common’, ‘module_1’ and ‘module_2’

implementation "com.apollographql.apollo3:apollo-runtime:3.0.0-alpha07"
implementation "com.apollographql.apollo3:apollo-api:3.0.0-alpha07"
Dependencies

In the Gradle files ‘app’, ‘module_1’ and ‘module_2’ add the module ‘common’ to the dependencies. It will become visible to these modules.

implementation(project(":common"))

Creating the required classes

Let’s start with the scheme.graphqls file, this file describes what data can be requested, it must be placed in the root of the project, at the same level with the folders ‘app’, ‘common’, ‘modules’.

schema {
  query: Query
}

type Query {
  "Возвращает сотрудников.nnn**Returns:**nСотрудник, если он найден."
  employee("Идентификатор сотрудника." id: String): Employee
}

"Представляет сотрудника."
type Employee @source(name: "Employee", schema: "Employees") {
  "Возвращает id пользователя"
  id: String
  "Возвращает полное имя."
  fullName: String!
  "Возвращает табельный номер."
  personnelNumber: String!
  "Возвращает статус, показывающий присутствие сотрудника на работе."
  workStatus: Employees_WorkingPeriod!
}

"Определяет рабочий период."
type Employees_WorkingPeriod @source(name: "NonWorkingPeriod", schema: "Employees") {
  "Возвращает дату начала."
  beginDate: DateTime!
  "Возвращает дату окончания."
  endDate: DateTime!
}

"Annotates the original name of a type."
directive @source("The original name of the annotated type." name: Name! "The name of the schema to which this type belongs to." schema: Name!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE

"The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types."
scalar Name

"The `DateTime` scalar represents an ISO-8601 compliant date time type."
scalar DateTime @specifiedBy(url: "https://www.graphql-scalars.com/date-time")

In the module ‘common’ we will create the class ‘ApolloClient’, in it we will write the client, since it is in the common module, it will be visible to all other modules.

/**
 * Представляет клиент Apollo.
 */
val apolloClient = ApolloClient(
    networkTransport = HttpNetworkTransport(
        serverUrl = "https://your_url.com/api/graphQl",   //Адресс Вашего Api
        okHttpClient = OkHttpClient.Builder()
            .addInterceptor(Interceptor { chain ->
                val newRequestBuilder = chain.request().newBuilder()
                newRequestBuilder.apply {
                    addHeader(
                        "Authorization",
                        "Bearer <Your Token>"   //Ваш токен
                    )
                    addHeader("Content-Type", "application/json")
                        .build()
                }
                val newRequest = newRequestBuilder.build()
                return@Interceptor chain.proceed(newRequest)
            }).build()
    ) )
    .withCustomScalarAdapter(DateTime.type, dateTimeAdapter)

In the same module, we will create the ‘GraphqlAdapters’ class, in it we will write a scalar adapter for the DateTime type, which is present in the schema, the data that will come with this type will be automatically converted into the usual Date class.

/**
 * Адаптер для преобразования Any в Date.
 */
val dateTimeAdapter = object : Adapter<Date> {
    override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): Date {
        val source = reader.nextString()
        val date: Date = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S'Z'", Locale.getDefault())
            .parse(source) ?: throw RuntimeException("Не удалось распарсить строку '${source}' в дату")
        return date
    }

    override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: Date) {
        AnyAdapter.toJson(writer, value)
    }
}

Moving on to module_1 and module_2, the following files and classes will be almost identical for both modules, and done as an example.
Let’s create a file ‘GetEmployee.graphql’, it will describe our request based on scheme.graphqls, and apollo will generate all the necessary classes based on ‘GetEmployee.graphql’.

query GetEmployee($id: String) {
    employee(id: $id) {
        id,
        fullName,
        personnelNumber,
        workStatus {
            beginDate,
            endDate
        }
    }
}

Next, we will create the ApiService class and the corresponding IApiService interface, there we will describe our requests.

The ‘IApiService’ interface

/**
 * Описывает методы запросов к api.
 */
interface IApiService {

    /**
     * Возвращает сотрудника по указанному id.
     * @param id Идентификатор сотрудника.
     */
    suspend fun getEmployee(id: String): GetEmployeeQuery.Employee?

}

ApiService class

/**
 * Представляет сервис запросов к api.
 * @param apolloClient Apollo client.
 */
class ApiService(
    private val apolloClient: ApolloClient,
) : IApiService {

    override suspend fun getEmployee(id: String): GetEmployeeQuery.Employee? {
        return apolloClient.query(GetEmployeeQuery(id)).data?.employee
    }

}

Everything is ready for writing the request itself, we will create a fragment class, and in it the request

/**
 * Представляет фрагмент экрана.
 */
class ModuleFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return View(requireContext())
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        GlobalScope.launch {
            val employeeResponse = apolloClient.query(GetEmployeeQuery("12345")).data?.employee
        }

    }
}
Class structure in module_1 and module_2

You can download the example on my GitHub
https://github.com/AndroidLab/ApolloConfig_Example

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *