Давно хотел рассказать про RX но никак не выходило, то времени не хватало, то желания небыло, и так куча причин была… А тут выдалось свободное время, и желание тоже появилось, в общем как-то планеты сошлись все в одну линию и пришло осознание бытия, и что пора написать что-то новое.
В интернете куча статей о том как писать с помощью Rx, я сам по ним пытался учиться использовать его, много статей было прочитанно, но лишь одна статья меня реально поразила своей логичностью и порядком изложения информации, после которой у меня открылся третий глаз и я начал слышать голоса объясняющие основы Rx. Вот эта статья.Всем кто не знаком с Rx начинать с нее, даже не знание английского не помеха, Google Translate в помощь, статья очень понятно и логично расписывает работу с Rx. Прям ну ооочень хорошо. Остальные статьи как по мне шлак.
Попался мне проект один, который был написан на Rx, я как нубас впал в ступор и начал истерить что это вообще какая-то не понятная хрень и в растроенных чувствах начал разбираться в ней. В итоге понял что эта непонятная хрень довольно таки удобная хрень, дак еще и полезная, так как она обсорбирует в себя кучу ошибок, и разных плюшек типа работы в UI треде или объеденение нескольких списков в один после запроса.
В этой статье я хочу рассказать про несколько вариантов использования Rx в проекте, в одном случае я приведу прям вот живой пример использования этой штуки, а в других случаях пройдусь объяснив как это можно реализовать у себя в проекте. Я лично использую Rx для спокойной и удобной работы в UI треде, как минимум не надо создавать там разные AsyncTask'и и писать кучу колбеков, достаточно написать один класс в котором будут нужные методы, а дальше просто вызывать этот класс в нужных классах, и получать нужные данные.
Начнем мы с того что разберем что такое Observable и Subscriber. Это два основных класса которые обеспечивают нас как программистов основными функциями Rx. Observable — у нас класс который хранит в себе данные, а Subscriber — класс передающий данные из Observable. Ну то есть Observable у нас хранит данные, а Subscriber — возвращает все что имеет в себе Observable. Как то так. В общем дальше на примерах будет понятней.
Стандартный вид Observable который все обычно используют это Observable, где Т — это любой объект который может быть возвращаен, пусть то будет Object, String или ArrayList. То есть Observable может вернуть буквально любой вид данных.
Давайте перейдем к практике. Вот у нас есть какая-то функция которая возвращает нам какой-то ArrayList, нам нужно его получить через Observable и передать в MainActivity. Что нам для этого нужно. Создать какой-то метод котоый будет возвращать Observable<ArrayList> и дальше вернуть этот список в наш метод.
Как видно из кода, мы создали Observable.create, который передает какой-то observableEmitter, это объект самого ObservableEmitter<ArrayList> который возвращает текущее состояние потока, и работает в то же время колбеком для нас. С помощью его мы можем вызывать такие методы как:
Из их названий я так думаю понятно что они занчат. onNext() вызывается когда выполнилась ваша функция, и он возвращает в Subscriber, то что вы передаете в него. onError() соответственно возвращает ошибку которую вы в него передадите, если ничего не передадите, то соответственно он ничего и не выведет. onComplete() вызывается когда все действия завершены и поток закрывается.
Дальше для того что бы в нашей MainActivity получить эти данные с помощью Subscriber, нам нужно создать подписчика, то есть Subscriber вызвав наш метод и дальше в методах будет возвращаться состояние вашего потока. В нашем случае просто выведется то, что я передал в предыдущем методе, без ошибок и остального.
Это в принципе основной функционал который считается самым популярным в RX. Есть еще методы just, first, last, rand, from, они все нужны, но в редких случаях когда у вас идет работа с локальным списком, когда вы передаете список в Observable и вам нужно сделать какие-то манипуляции с ним внутри Observable. Тогда да, но я тут все это размазывал не из-за этих методов, так что их я оставлю на потом. Возможно в будущем расскажу о них…
А сейчас я расскажу как я парсил сайт с помощью JSOUP и RxAndroid. Это оказалось довольно просто, даже как-то подозрительно.
Для примера я взял сайт instructables.com. Там довольно простая верстка, и просто можно достать нужные названия классов для парсинга. В примере я всего лишь достану список статей из главной страницы об технологиях.
Начнем мы с того что создадим проект, и в нем у нас будут MainActivity.java и разметка activity_main.xml. Нам нужно будет подключить пару библиотек, и java 8 для работы ретролямбды, что бы не подключать библиотеку. Данный проект писался в Android Studio 3, они уже умеет в ретролямбду без ретролябмды, то есть фишки java 8 доступны без лишних библиотек, это кстати очень удобно, так как раньше это был страшный геморой, пока подключишь ее, свихнешься.
Библиотеки которые нам понадобятся:
app/build.gradle
Вроде бы для такого маленького проекта не нужно особо много библиотек, но у меня их довольно много, я буду создавать списки с помощью RecyclerView, буду использовать Rx, буду паристь страницы с помощью JSOUP и буду искать вюхи с помощью ButterKnife, а еще как же без Picasso, мне же надо с помощью чего-то отображать картинки…
А еще нам нужно добавить в тот же app/build.gradle использование java 8.
Таким образом мы говорим студии что у нас можно сокращать код и делать что-то на подобии -> в место длинного и не удобного new View.OnClickListener() { private void onClick }. Но это опять же по желанию, мне нравятся эти сокращения, может кому-то больше нравится длинные но понятные колбеки, без этих непонятных сокращений.
В общем с настройкой gradle мы справились, это уже великолепно. Далее нужно создать интерфейс который будет у нас хранить все методы которые нам нужны для работу. Так и красивей получается и удобней. У нас там будет один метод, но это ведь не важно, за то красиво!
IRepository.java
В общем создали мы интерфейс, Observable должен будет возвращать нам ArrayList, и это офигенно. Но что же такое ArticlesModel, а это просто модель в которой у нас хранится название и картинка статьи.
ArticlesModel.java
Вот такая вот структура. Пока что просто? Просто, вот и замечательно, дальше нам нужно создать имплементацию нашего интерфейса Repository. Это очень просто, создаем класс любой, и имплементируем в него наш интерфейс, и там сразу создасться методы которые есть в нашем интерфейсе для дальнейшей реализации.
RepositoryImpl.java
Что же мы тут имеем? А то что у нас создался наш метод getArticles() который у нас возвращает Observable<ArrayList>, и дальше у нас идет реализация Observable.create(), как я писал ранее. Тут у нас идет реализация парсинга сайта по ссылке, которую мы передаем в метод. Дальше Observable.create() у нас Rx заканчивается и начинается JSOUP, с его помощью мы находим нужные нам параметры на странице и парсим их доставая название статей и ссылки на картинки. Распихиваем это все по своим местам в ArticlesModel и сетим эти данные в список. А по окончанию, сетим этот список в observableEmitter.onNext().
Если у нас появятся какие то ошибки, то мы добавили в catch метод onError() который принимает ексепшн и отдает его в сабскрайбера. Ну и в конце в finnlay когда уже все действия у нас заканчиваются мы вызываем onComplete(), что бы убирать прогрессбар на пример, или что-то прятать по окончанию загрузки.
Давайте создадим еще адаптер, что бы в конце статьи уже собрать все в кучу и получить какой-то красивый результат. Адаптер у нас будет стандартный, по этому я на нем не буду задерживать внимания. Обычный RecyclerView.Adapter который отображает текст и картинку.
ArticlesRecyclerAdapter.java
Как я и говорил все тривиально просто. Создали адаптер который принимает в конструкторе ArrayList и дальше сетит данные из списка в вьюхи.
И не забываем конечно же за разметочку для адаптера.
item_article.xml
А что же делать дальше скажете вы? Да все просто, дальше мы открываем наш MainActivity, и прямо в onCreate() пишем вызов этого метода с определенными параметрами, которые я описывал выше.
MainActivity.java
Находим RecyclerView на нашей активити, задаем ему параметры, что бы он использовал LinearLayoutManager для отображения, и DividerItemDecoration для разделения айтемов друг от друга. Дальше вызываем наш RepositoryImpl() который вызывает наш метод, передаем в него ссылку откуда парсить наши статьи.
Метод subscribeOn(Schedulers.io()) — говорит о том что поток в котором будет выполняться функция будет задан Schedulers'ом.
Метод observeOn(AndroidSchedulers.mainThread()) — очевидно говорит что потом мы создаем поверх нашего UI треда. Но как бы мы не хотели повлиять на главный поток, он все равно не будет заблокирован пока действие не закончится, то что нам как раз и нужно, так как JSOUP требует выполнение действий в отдельном потоке.
Ну и конечно же subscribe() который возвращает нам наш Observer<ArrayList>, который мы и хотим получить в итоге.
Дальше в методе onNext() мы получаем наш ArrayList, и выводим то что у нас в нем есть в адаптер. Если же у нас появтяся какие-то ошибки по ходу действия Observable — все ошибки вернутся в onError().
Осталось только создать RecyclerView в разметке. Кто не знает как вот пример.
activity_main.xml
И не забываем что у нас приложение все таки работает с интернетом, по этому ему нужно разрешить работу с интернетом в манифесте.
AndroidManifest.xml
И вот после этого всего, у вас по идее должно все скомпилироваться и работать как часы, без падений и ексепшинов. Как часы.
PS:
Rx еще часто используют для комбайна данных из запросов Retrofit на пример. Это довольно интересная тема, так как это очень спасает от костылей которые будут вручную эти данные добавлять и будут в ключат ьв себя кучу ошибок типа смешивания данных и путаницу в айдишниках, а так у нас допустим есть два запроса, как это было сказанно в статье с медиума что я выше дал, для получения фотографии и данных пользователя, и нам нужно эти данные объеденить в одну кучу что бы потом передать куда-нибудь в адаптер.
Если в кратце. Мы создаем интерфейс для работы с ретрофитом, как я описывал это в статье про работу с Retrofit.
И у нас как бы есть два запроса, которые мы дальше объеденяем в простую конструкцию
Где Observable.zip() метод который объеденяет наши два объекта service.getUserPhoto(id) и service.getPhotoMetadata(id). Дальше у нас идет колбек который возвращае (photo, metadata) и мы их объъеденяем в методе createPhotoWithData() который принимает их. Указываем работу в отдельном потоке, но при этом в UI, и дальше выводим то что у нас получилось в итоге в subscribe(), в метод showPhoto() который принимает photoWithData.
В общем если вам так не понятно то объясню на другом примере. Возможно так будет более понятно. Вот у нас есть два метода которые возвращают разные данные в списках.
Первый у нас возвращает слово one. Второй возвращает слово two. И преобразовываем мы это все в Observable что бы можно было его скормить для парсинга. Все очень просто, а дальше у нас уже знакомая конструкция сбора данных в одну кучу:
Передаем в Observable.zip() наши два списка, дальше объеденяем их, выполняем это мы все в отдельном UI потоке, и потом в subscribe() выводим в лог, то что у нас получилось в итоге.
Спасибо за статью. Очень помогли.
ОтветитьУдалить