OData + RxJava + Retrofit 2 для android приложения

от автора

Столкнулся на проекте с проблемой доселе не виданной. Пришлось покурить документацию и в этой статье я расскажу как с помощью RxJava и Retrofit 2 — можно решить задачу по созданию клиента Odata для android приложения.

Спасибо огромное Jake Wharton за создание таких комфортных инструментов.

Добро пожаловать в мир магии

У нас есть приложение, которое по протоколу Odata должно выгребать с сервера данные, отображать их в списках, которые должны подгружаться по мере прокрутки и отправлять данные созданные пользователем на сервер. Тривиальная задача, но не тут то было, то что работает без проблемно на Java — не хочет так же работать с android.

А библиотеки и документация на Odata только от Apache — Olingo и Microsoft на C#.

В данной статье протокол Odata рассматривать я не буду, очень хорошая документация есть у Microsoft и ссылки я оставлю в конце статьи.

Вот вкратце определение с Wiki Open_Data_Protocol

Open Data Protocol (OData)

Open Data Protocol (OData) — это открытый веб-протокол для запроса и обновления данных. Протокол позволяет выполнять операции с ресурсами, используя в качестве запросов HTTP-команды, и получать ответы в форматах XML или JSON.

Начиная с версии 4.0, OData — открытый стандарт, одобренный OASIS.

И вот здесь и начинается самое интересное, Odata — это своеобразный SQL в REST API и для динамически создаваемых данных самое то.

Но у нас строго типизированный язык и без знания модели — обработка и хранение данных создают довольно не простую задачу.

Решение которой не может быть типовым и многократно описанным в сети.

Скажу даже больше, кроме этих ссылок на документацию в сети — по теме все плохо.

А теперь займемся магией:

Создаем службу для работы с сетью

Будем использовать Retrofit 2 и сопутствующие продукты компании Square

добавим зависимости в в файл build.gradle

// Network     implementation 'com.squareup.retrofit2:retrofit:2.7.1' // Собственно сам Retrofit 2     implementation 'com.squareup.retrofit2:converter-gson:2.7.1' // Конвертер для работы с JSON      implementation 'com.squareup.okhttp3:logging-interceptor:4.3.1' // Перехватчик запросов     implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1' // Адаптер для работы с RxJava     implementation 'com.squareup.okhttp3:okhttp:4.3.1' // OkHttp - это HTTP-клиент 

listing 1

Все эти зависимости и есть мощнейшая библиотека для работы с с сетью в Java.

Описывать работу с Retrofit 2 не вижу смысла, вот есть хороший мануал: Используем Retrofit 2 в Android-приложении.

Создаем класс NetworkService:

public class NetworkService {      private static final String TAG = "NetworkService";      private static final NetworkService             mInstance = new NetworkService();      private String mToken;     private Retrofit mRetrofit;      public static NetworkService getInstance() {         return mInstance;     }      private NetworkService() {          RxJava2CallAdapterFactory rxAdapter =                 RxJava2CallAdapterFactory                         .createWithScheduler(Schedulers.io());          HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();         interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);           OkHttpClient.Builder okHttpClient =                 new OkHttpClient.Builder()                         .addInterceptor(interceptor)                         .addInterceptor(chain -> {                             Request newRequest =                                     chain.request().newBuilder()                                            .addHeader("Accept",                                                      "application/json,text/plain,*/*")                                            .addHeader("Content-Type",                                                     "application/json;odata.metadata=minimal")                                            .addHeader("Authorization", mToken)                                     .build();                              return chain.proceed(newRequest);                         });          Gson gson = new GsonBuilder()                 .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")                 .create();          mRetrofit = new Retrofit.Builder()                 .baseUrl(Const.BASE_URL)                 .addConverterFactory(GsonConverterFactory.create(gson))                 .addCallAdapterFactory(rxAdapter)                 .client(okHttpClient.build())                 .build();     }     public ApiService getNetworkClient(){         return mRetrofit.create(ApiService.class);     } } 

listing 2

Создаем интерфейс API:

public interface ApiService { // Делаем запрос без условий @GET("odata/product") Observable<List<ProductsDto>> getProducts(); } 

listing 3

И создаем какой нибудь контроллер что бы дергать запросы:

public class ProductsController {      private ApiService mApiService;      private List<ProductsDto> listProducts;     private Gson gson;      public ProductsController() {          mApiService = App.getNetworkService().getNetworkClient();          listProducts = new ArrayList<>();          gson = new Gson();     }      public void productsBtnClick() {          mApiService.getProducts()                 .subscribeOn(Schedulers.io())                 .observeOn(AndroidSchedulers.mainThread())                 .subscribe(new DisposableObserver<List<ProductsDto>>() {                     @Override                     public void onNext(List<ProductsDto> products) {                          listProducts.addAll(listProducts);                     }                      @Override                     public void onError(Throwable e) {                      }                      @Override                     public void onComplete() {                     }                 });     } 

listing 4

Итак, на сервере храниться модель данных Poduct, которая имеет какие то параметры и атрибуты. Все данные идут в формате JSON и для работы нам необходим POJO класс.

Рекомендую в HttpLoggingInterceptor настроить уровень подробности перехвата — Level.BODY.

Делаем запрос listing 4, чтобы максимально вытянуть структуру данных и ответ будет приблизительно в таком формате:

// Есть некий список в котором перечислены сущности высшего уровня // в формате JSON {   "@odata.context":"https://example.xxx/api/odata/$metadata","value":[     {       "name":"product","kind":"EntitySet","url":"product" // продукт      },{       "name":"blogs","kind":"EntitySet","url":"blogs"  // блоги     },{       "name":"posts","kind":"EntitySet","url":"posts"  // посты     },{       "name":"comments","kind":"EntitySet","url":"comments" // комментарии     },{       "name":"rates","kind":"EntitySet","url":"rates" // рейтинги     }   ] } 

listing 5

И уже на основании этих данных можно формировать запрос для дальнейших манипуляций с данными.

То есть это полноценный объект с каким то поведением и атрибутами, чтобы получить эту информацию при создании запроса необходимо добавить условия, на основании которых мы и получим наш DataSource не выдумывая новый велосипед и не прикручивая к нему костыли.

Кульминация и щенячий восторг от комфорта инструмента

И вот он момент истинны, мощь, сила и простота библиотеки Retrofit 2. Теперь можно получить properties используя сервисный документ Odata:

// Можно получить properties сущности product @GET("odata/product?$filter=Id eq 111&$expand=dateReading($orderby=Date desc") Observable<List<ProductsDto>> getProducts();  // Или properties сущности blogs @GET("odata/blogs?$orderby=Date desc") Observable<List<BlogsDto>> getBlogs();  // И запрос уже можно формулировать исходя из потребностей @GET("odata/product?$filter=((Id eq 19) and (Name eq 'Available')) and ((Status eq 'OPEN') or ((Status eq 'CLOSED') and (Date ge 2020-02-13T06:39:48Z)))&$orderby=Status asc,Date desc&$top=10&$expand=AuthorId,CategoryId($expand=weight)&$count=true") Observable<List<ProductsDto>> getProducts(); // Этот запрос выдаст точную информацию отобранную по условиям, // но он явно выходит за разумные рамки.   // Исправляем: @GET Observable<List<ProductsDto>> getProducts(@Url String url); 

listing 6

Вот она силушка богатырская библиотеки Retrofit 2. Динамический Url забирает на себя всю эту массу параметров, с которыми можно играться в коде на сколько хватит фантазии.

Выглядеть это будет как то так:

private void buttonGetProduct() { // Здесь со строками можно все         String one = "odata/product?$filter=Id eq ";         String id = "777";         String tree = "&$expand=dateReading($orderby=Date desc)";         String url = one + id + tree;          mApiService.getProduct(url)                 .subscribeOn(Schedulers.io())                 .observeOn(AndroidSchedulers.mainThread())                 .subscribe(new DisposableObserver<List<ProductDto>>() {                     @Override                     public void onNext(List<ProductDto> products) {  // А вот здесь мы и получаем искомое, правда данные в формате JSON, // но есть масса конвертеров и это не проблема                          mProductsDto.addAll(countersDtos);                     }                      @Override                     public void onError(Throwable e) {                     }                      @Override                     public void onComplete() {                     }                 });     }

listing 7

Итоги

Это был полезный опыт, которым я и спешу поделится, мне в свое время эта статья реально бы убрала кучу проблем и вопросов.

В статье я не стал углубляться в ненужные подробности по Retrofit 2 и OData и указал ссылки на документацию если возникнет необходимость вникнуть глубже.

Полную версию кода предоставить не могу, за что приношу извинения, продукт коммерческий.

И, как обещал, ссылки:

Документация Microsoft: Open Data Protocol
Documentation OData 4.0 Java Library
Создание полнофункциональных интернет-приложений с применением Open Data Protocol

ссылка на оригинал статьи https://habr.com/ru/post/489688/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *