Зачем статья
Недавно у меня появилась необходимость реализовать работу с бэком на GraphQL. Туториалов по настройке на Android, в отличие от REST не так много и большинство из них уже не совсем актуальны.
Что такое GraphQL
GraphQL — модная альтернатива REST API, которая позволяет запрашивать данные более оптимизированным способом, отдавая только нужные вам данные.
Настройка окружения
Делать запросы к серверу мы будем через Apollo — самая популярная библиотека для работы с GraphQL на данный момент.
Приступим к работе. Первым делом давайте добавим в манифест нужные разрешения для работы с сетью:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Далее необходимо подключить apollo. Идём в основной build.gradle и в разделе dependencies добавляем следующую строчку:
classpath 'com.apollographql.apollo:apollo-gradle-plugin:2.0.0'
Теперь необходимо в файле build.gradle модуля app подключить дополнительные зависимости:
implementation("com.apollographql.apollo:apollo-runtime:2.0.0") implementation "com.apollographql.apollo:apollo-android-support:2.0.0"
Кроме того, в самый верх файла добавим подключение плагина:
apply plugin: 'com.apollographql.apollo'
После того, как прое��т синхронизировался, нам нужно настроить кодогенерацию моделей, с помощью которых мы будем делать запросы к GraphQL.
Свернем среду разработки и откроем терминал. Переходим в папку с вашим проектом:
cd /Users/user/Desktop/ProjectName
Если у вас ещё нет npm, то сперва скачайте с официального сайта
Устанавливаем apollo-codegen — инструмент, который позволит скачать schema.json — файл, который послужит apollo источником для генерации моделей:
npm install apollo-codegen
Скачиваем schema.json(необходимо находиться в директории вашего проекта, где появилась папка node_modules):
node_modules/.bin/apollo-codegen download-schema https://ссылка на ваше api/ --output schema.json
Теперь в папке проекта мы видим файл schema.json. Осталось показать apollo файлы для генерирования моделей. Для этого делаем следующие шаги.
Переходим в папку app вашего проекта, далее src -> main. Здесь нам необходимо создать папку graphQL. Сюда мы будем складывать наши .graphql файлы.
Копируем в созданную папку файл, скачанный в предыдущем шаге — schema.json
Настройка окружения закончена, переходим к коду
Генерация кода моделей
Начнём с файлов .graphql. Тут будут храниться запросы.
В GraphQL есть два вида запросов:
query — аналог GET
mutation — аналог POST/PUT/DELETE
Предположим, что вы делаете сервис для заселения постояльцев в комнату в отеле для администраторов этого отеля. Приложение умеет делать 3 функции. Логинить пользователя(администратора), получать информацию о комнате по id и заселять постояльцев
Создадим файл loginUser.graphql в созданной в предыдущем разделе директории app/src/main/graphQL. С помощью этого файла apollo сгенерирует нам модель для логина пользователя.
Содержание файла:
mutation loginUser($email:String!, $password:String!) { login( user: { email: $email, password: $password } ){ email, token, refreshToken } }
Кроме того, нам потребуется файл getRoom.graphql, с его помощью сгенерируется модель для получения комнаты отеля:
query getRoom($room_id: String) { room(room_id: $room_id) { title, room_number, cheked_in_family_name, has_minibar } }
И финальный файл — заселение постояльцев checkUserIn.graphql. Тоже использует mutation:
mutation checkInFamily($room_id: String!, $family_name: String!) { room( room: { title: $family_name, room_id: $room_id } ){ room_id, family_name, minibar_products{ title, weight } } }
Билдим проект и видим 3 сгенерированных модельки в папке app/build/generated/source/apollo/debug/service: GetRoomQuery, СheckUserInMutation, LoginUserMutation
Выполнение запросов
Создадим singleton класс NetworkService, который будет провайдить нам ApolloClient. В нём делаем 2 метода. getApolloClient() для выполнения запросов, которые не требуют токена или каких-либо дополнительных параметров, и getApolloClientWithTokenInterceptor(), в который будем прокидывать токен, для запросов:
import com.apollographql.apollo.ApolloClient import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request class NetworkService { fun getApolloClient(): ApolloClient { val okHttp = OkHttpClient .Builder() .build() return ApolloClient.builder() .serverUrl(BASE_URL) .okHttpClient(okHttp) .build() } fun getApolloClientWithTokenInterceptor(token: String): ApolloClient { val httpClient = OkHttpClient.Builder() .addInterceptor(Interceptor { chain: Interceptor.Chain -> val original: Request = chain.request() val builder: Request.Builder = original .newBuilder() .method(original.method, original.body) builder.header("Authorization", "Bearer $token") return@Interceptor chain.proceed(builder.build()) }) .build() return ApolloClient.builder() .serverUrl(BASE_URL) .okHttpClient(httpClient) .build() } companion object { private var mInstance: NetworkService? = null fun getInstance(): NetworkService? { if (mInstance == null) { mInstance = NetworkService() } return mInstance } } }
Теперь идём в наше activity или fragment, тут реализуем выполнение запросов. Для начала залогинемся:
private fun loginUser() { val client = NetworkService.getInstance()?.getApolloClient() val loginMutation = LoginUserMutation .builder() .email(emailEdit.text.toString()) .password(passwordEdit.text.toString()) .build() client ?.mutate(loginMutation) ?.enqueue(object : ApolloCall.Callback<LoginUserMutation.Data>() { override fun onResponse(response: Response<LoginUserMutation.Data>) { if (!response.hasErrors()) { val token = response.data?.login()?.token() val email = response.data?.login()?.email() //Делаем операции, не трогающие ui, например сохраняем токен в БД runOnUiThread { //Выводим на экран то, что хотим } } } override fun onFailure(e: ApolloException) {} }) }
Это пример работы с mutation. Данный запрос мы выполняли без токена в header. Теперь давайте рассмотрим пример работы с query, попробуем получить информации о комнате. Предположим, что комната нам отдается только с токеном. Выполняем запрос следующим образом:
private fun getRoom() { val token = "123456" val client = NetworkService.getInstance() ?.getApolloClientWithTokenIntercetor(token) val roomId = "123" val allRoomsQuery = GetRoomQuery(Input.fromNullable(roomId)) client ?.query(allRoomsQuery) ?.enqueue(object : ApolloCall.Callback<GetRoomQuery.Data>() { override fun onResponse(response: Response<GetRoomQuery.Data>) { if (!response.hasErrors()) { val familyName = response.data?.room()?.family_name() } } override fun onFailure(e: ApolloException) {} }) }
В качестве домашнего задания попробуйте сами написать реализацию заселения постояльцев.
Полезные ссылки:
Документация apollo-client для Android
Github apollo-client android
