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