Давайте рассмотрим, каким образом настроить и использовать последнюю на данный момент версию клиента 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

