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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

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

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

Apollo Android - это клиент GraphQL, который генерирует Kotlin модели на основе запросов GraphQL. Эти модели предоставляют вам типобезопасный API для работы с серверами GraphQL. Apollo помогает вам объединять, организовывать и легко получать доступ к вашим операторам запросов GraphQL.
Ссылка на официальный Github: https://github.com/apollographql/apollo-android

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

Создаем новый проект на основе 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
https://github.com/AndroidLab/ApolloConfig_Example

Источник: https://habr.com/ru/post/582552/


Интересные статьи

Интересные статьи

Android Fake PermissionsРечь в статье пойдет о такой вещи как Runtime permissions, а именно – о возможности введения в заблуждение конечного пользователя путем демонстрац...
Всем привет. Когда я искал информацию о журналировании (аудите событий) в Bitrix, на Хабре не было ни чего, в остальном рунете кое что было, но кто же там найдёт? Для пополнения базы знаний...
Пришло время продемонстрировать как криптографический АРМ на базе стандартов с открытым ключом cryptoarmpkcs работает на одной из мобильных платформ, а именно Android. Концепция, которая закла...
Пост о том, как привязанный к аккаунту сервиса Яндекс.Почта телефон, помог угнать домен созданного мной сетевого издания "Банки Сегодня". Отмечу, что в это издание я вложил все свои накопленные д...
Здравствуйте. Я уже давно не пишу на php, но то и дело натыкаюсь на интернет-магазины на системе управления сайтами Битрикс. И я вспоминаю о своих исследованиях. Битрикс не любят примерно так,...