Как стать автором
Обновить

Apollo 3.0 для работы с GraphQL в многомодульном Android приложении

Время на прочтение5 мин
Количество просмотров3.8K

Давайте рассмотрим, каким образом настроить и использовать последнюю на данный момент версию клиента apollo в многомодульном приложении под android.

Что такое Apollo и когда он используется?

Apollo Android - это клиент GraphQL, который генерирует Kotlin модели на основе запросов GraphQL. Эти модели предоставляют вам типобезопасный API для работы с серверами GraphQL. Apollo помогает вам объединять, организовывать и легко получать доступ к вашим операторам запросов GraphQL.

Ссылка на официальный Github

Создаем проект подключаем плагин и зависимости

Создаем новый проект на основе Empty Activity, я назову его ApolloConfig_Example

Создание нового проекта

Создадим несколько модулей, я назову их 'common', и 'modules',последний в свою очередь будет содержать 'module_1' и 'module_2'

Модули проекта

Подключаем JS GraphQL плагин - он добавляет поддержку языка, подсветку синтаксиса GraphQL

Плагин JS GraphQL

Подключаем плагин Apollo 3.0 во всех gradle файлах наших модулях

id 'com.apollographql.apollo3' version("3.0.0-alpha07")
Плагин Apollo 3.0

После добавления apollo плагина появится сообщение о необходимости синхронизации проекта, но перед этим необходимо добавить настройки для плагина

Для gradle файла модуля 'app'

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

Для gradle файла модуля 'common'

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

Для gradle файла модуля 'module_1' и '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"
        ]
    }

Нажимаем кнопку 'Sync Now'

Кнопка 'Sync Now'

Добавляем зависимости в модули 'common', 'module_1' и 'module_2'

implementation "com.apollographql.apollo3:apollo-runtime:3.0.0-alpha07"
implementation "com.apollographql.apollo3:apollo-api:3.0.0-alpha07"
Зависимости

В Gradle файлах 'app', 'module_1' и 'module_2' добавляем в зависимости модуль 'common'. Он станет видим для этих модулей.

implementation(project(":common"))

Создаем необходимые классы

Начнем с файла scheme.graphqls, это файл описывает, какие данные могут быть запрошены, разместить его нужно в корне проекта, на одном уровне с папками 'app', 'common', 'modules'.

schema {
  query: Query
}

type Query {
  "Возвращает сотрудников.\n\n\n**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")

В модуле 'common' создадим класс 'ApolloClient', в нем напишем клиент, поскольку он находится в общем модуле, он будет виден всем другим модулям.

/**
 * Представляет клиент 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)

В этом же модуле создадим класс 'GraphqlAdapters', в нем напишем скалярный адаптер для типа DateTime, который присутствует в схеме, данные которые будут приходить с этим типом, будут автоматически преобразованы в привычный класс Date.

/**
 * Адаптер для преобразования 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)
    }
}

Перейдем к module_1 и module_2, следующие файлы и классы будут почти идентичны для обоих модулей, и сделано для примера.
Создадим файл 'GetEmployee.graphql', он будет на основе scheme.graphqls описывать наш запрос, а apollo на основе 'GetEmployee.graphql' сгенерирует все необходимые классы.

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

Далее создадим класс ApiService и cоответствующий ему интерфейс IApiService, там будем описывать наши запросы.

Интерфейс 'IApiService'

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

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

}

Класс 'ApiService'

/**
 * Представляет сервис запросов к 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
    }

}

Все готово для написания самого запроса, создадим класс фрагмента, а в нем запрос

/**
 * Представляет фрагмент экрана.
 */
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
        }

    }
}
Структура классов в module_1 и module_2

Скачать пример можно на моем GitHub.

Теги:
Хабы:
Всего голосов 1: ↑1 и ↓0+1
Комментарии1

Публикации

Истории

Работа

Ближайшие события

22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань