Создание приложения, использующего REST API в наше время стало обыденностью. Однако библиотеки Android не могут похвастаться удобным инструментарием для выполнения такой простой задачи. Перед разработчиком стоит несколько задач:
‒ Совершать вызовы к API из потока отличного от основного (main thread)
‒ Обрабатывать полученные данные и помещать их в локальное хранилище
‒ Код должен быть ёмким, кратким, понятным и многофункциональным
Стандартные процедуры увеличивают трудоёмкость написания кода, делают его менее читабельным и устойчивым к возникновению ошибок. Для общения с сервером придется написать AsyncTask либо запустить Tread/Runnable на выполнение асинхронной обработки. Необходимо изучить потоки данных в Java для того, чтоб обработать результат запроса с помощью http-клиента. В данном случае никак не обойтись без большого количества try/catch блоков для обработки всевозможных ошибок. Для последующий записи и хранения полученных данных потребуется создать локальную БД SQLite, для взаимодействия с которой необходимы знания языка SQL. Запись и чтение желательно проводить в отдельных от UI потоках, что приводит к созданию еще большего количества AsyncTask. Для каждого запроса нужно писать свой код, подготавливать данные для записи в БД, а затем для их получения.
Однако существует иной подход к решению данной задачи. В этом поможет использование open source библиотек.
RxJava и RxAndroid позволят совершать асинхронную обработку, используя принципы функционального реактивного программирования
Retrofit2 позволяет легко обращаться с HTTP запросами и без проблем обрабатывать ответы
Realm — объектная БД, принцип работы которой основан на работе с моделями данных
Для того, чтоб показать минимальные возможности данных библиотек, будет использоваться Github API. Результатом будет получение, обработка, запись в локальную БД и отображение данных, предоставленных Github API. Этим и обработкой данных займется Retrofit. Асинхронность будет обеспечена через RxJava. Запрос вернет модель, которая будет помещена в локальную БД При помощи Realm. Слушатель изменений в БД в реальном времени отображает результат на экран.
Установка
Ниже указаны основные зависимости:
dependencies {
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
kapt «io.realm:realm-annotations-processor:3.1.3"}
В build.gradle на уровне приложения необходимо добавить:
apply plugin: 'realm-android'
На уровне проекта:
dependencies {
classpath "io.realm:realm-gradle-plugin:3.1.3"}
А также добавить App Permissions в AndroidManifest.xml:
RetrofitService
Retrofit использует Java интерфейс в качестве прокси для вызовов REST API. Аннотация @GET указывает на то, что будет выполнен get-запрос. В качестве параметров необходимо указать url/путь. Данный код совершает get-запрос и возвращает объект типа User, обернутый в Flowable
interface GithubService {
@GET("/users/{user}") fun getUsers(@Path("user") user: String): Flowable
Github REST API возвращает JSON данного вида:
{"login": "linkedin",
"blog": "http://engineering.linkedin.com",
"public_repos": 73,
//...truncated JSON}
Далее необходимо определить модель. Поля переменных в модели автоматически парсятся из JSON ответа. Необходимость ручного парсинга отпадает. Чтоб все сработало автоматически необходимо использовать аннотацию @ SerializedName, где параметром будет имя ключа из входного JSON.
open class User : RealmObject() {
var login: String? = null
var id: Int = 0
@SerializedName("avatar_url") var avatarUrl: String? = null}
В классе, унаследованном от android.app.Application проинициализируем Retrofit service как указано в коде ниже.
val restAdapter = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
service = restAdapter.create(GithubService::class.java)
Метод baseUrl() принимает на вход базовую часть адреса. addCallAdapterFactory() и addConverterFactory() принимают на вход адаптеры для работы с оберткой и самими данными соответственно, преобразовывая входящий JSON в объект.
Чтоб получить сервис из Activity используем
companion object {
var service: GithubService? = null}
Дальше в ход вступает RxJava, который запустит цепочку получения и записи данных.
RxJava. Создание асинхронного потока
Объект Flowable, полученный из get-запроса дает возможность работать с потоком данных когда тот станет доступным. Необходимо указать поток который будет следить за изменениями потока данных. Для этого нужно подписаться на новый поток при помощи метода subscribeOn() и передать в него параметр Schedulers.newThread(). Далее указать поток, который будет следить за результатом (обозревать). Это делает метод observeOn() с параметром AndroidSchedulers.mainThread(). Код ниже демонстрирует работу с объектом Flowable
service.getUsers(it)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ r -> // действие с данными} },
{ e -> // обработка ошибок})
}.forEach { compositeDisposable.add(it) }
getService().getUsers() возвращает объект типа Flowable. Метод subscribe перегружен двумя функциями обратного вызова такими как onNext() и onError(). onNext() вызывается при завершении работы с потоком данных и предоставляет данные для работы с ними. onError() вызывается при возникновении ошибки в процессе получения данных для последующей обработки исключения. Каждый Flowable должен быть помещен в CompositeDisposable для возможности отписаться ото всех событий сразу. Это может быть сделано, например, в момент разрушения Activity
Код ниже демонстрирует работу CompositeDisposable
override fun onDestroy() {
super.onDestroy()
compositeDisposable.clear()}
Данные получены. Все Flowable гарантированно будут отписаны. Остается лишь записать полученные данные в БД.
Realm. Асинхронная запись данных
Для начала необходимо проинициализировать Realm в классе, наследуемом от android.app.Application.
Ниже представлена инициализация Realm
Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.build()
Realm.setDefaultConfiguration(realmConfiguration)
Realm.init(this) принимает на вход контекст приложения. Установка параметра deleteRealmIfMigrationNeeded() изменит поведение того, как будет обработана ошибка миграции.
Далее в основном Activity необходимо получить экземпляр Realm. Для упрощения работы можно повесить слушатель на изменение модели в БД. Теперь отображение данных напрямую зависит от состояния модели. Слушатель будет оповещать адаптер
val realm: Realm = Realm.getDefaultInstance()
realm.addChangeListener { mCardAdapter.notifyDataSetChanged() }
Заполним метод subscribe. Запишем данные в БД и обработаем ошибки
.subscribe({ r -> realm.executeTransactionAsync { data -> data.insert(r) } },
{ e -> e.printStackTrace()})
Сделаем обработчик нажатия на кнопку очистки
findViewById(R.id.button_clear).setOnClickListener {
realm.executeTransactionAsync { data -> data.delete(User::class.java) }}
executeTransactionAsync может принимать на вход три анонимных класса таких как Realm.Transaction() с функцией обратного вызова execute(), Realm.Transaction.OnSuccess() c функцией onSuccess() и Realm.Transaction.OnError() c onError() для работы с управляемой моделью, для выполнения действий по окончании транзакции и обработке ошибок в ходе транзакции соответственно.
По уничтожении Activity в меоде onDestroy() также как и с CompositeDisposable нужно вызвать realm.close()
Заключение
Данные получены и записаны в локальное хранилище. Возможные ошибки обработаны. Отображение реагирует на любое изменение данных. Графический интерфейс не будет заблокирован, так как все манипуляции с данными происходят в отдельных потоках. Сложная на вид задача реализована минимальным количеством кода. Полный код программы можно найти в репозитории по адресу https://github.com/slowfarm/RXKotlin
Литература:
- https://realm.io
- https://habrahabr.ru/post/269417/. — «Введение в RxJava: Почему Rx?»
- http://reactivex.io
- http://square.github.io/retrofit/
- https://habrahabr.ru/post/314028/. — «Изучаем Retrofit 2»
- https://kotlinlang.org/docs/tutorials/kotlin-android.html