Столкнулся на проекте с проблемой доселе не виданной. Пришлось покурить документацию и в этой статье я расскажу как с помощью RxJava и Retrofit 2 — можно решить задачу по созданию клиента Odata для android приложения.
Спасибо огромное Jake Wharton за создание таких комфортных инструментов.
У нас есть приложение, которое по протоколу Odata должно выгребать с сервера данные, отображать их в списках, которые должны подгружаться по мере прокрутки и отправлять данные созданные пользователем на сервер. Тривиальная задача, но не тут то было, то что работает без проблемно на Java — не хочет так же работать с android.
А библиотеки и документация на Odata только от Apache — Olingo и Microsoft на C#.
В данной статье протокол Odata рассматривать я не буду, очень хорошая документация есть у Microsoft и ссылки я оставлю в конце статьи.
Вот вкратце определение с Wiki Open_Data_Protocol
И вот здесь и начинается самое интересное, Odata — это своеобразный SQL в REST API и для динамически создаваемых данных самое то.
Но у нас строго типизированный язык и без знания модели — обработка и хранение данных создают довольно не простую задачу.
Решение которой не может быть типовым и многократно описанным в сети.
Скажу даже больше, кроме этих ссылок на документацию в сети — по теме все плохо.
А теперь займемся магией:
Создаем службу для работы с сетью
Будем использовать Retrofit 2 и сопутствующие продукты компании Square
Все эти зависимости и есть мощнейшая библиотека для работы с с сетью в Java.
Описывать работу с Retrofit 2 не вижу смысла, вот есть хороший мануал: Используем Retrofit 2 в Android-приложении.
Итак, на сервере храниться модель данных Poduct, которая имеет какие то параметры и атрибуты. Все данные идут в формате JSON и для работы нам необходим POJO класс.
Рекомендую в HttpLoggingInterceptor настроить уровень подробности перехвата — Level.BODY.
Делаем запрос listing 4, чтобы максимально вытянуть структуру данных и ответ будет приблизительно в таком формате:
listing 5
И уже на основании этих данных можно формировать запрос для дальнейших манипуляций с данными.
То есть это полноценный объект с каким то поведением и атрибутами, чтобы получить эту информацию при создании запроса необходимо добавить условия, на основании которых мы и получим наш DataSource не выдумывая новый велосипед и не прикручивая к нему костыли.
И вот он момент истинны, мощь, сила и простота библиотеки Retrofit 2. Теперь можно получить properties используя сервисный документ Odata:
listing 6
Вот она силушка богатырская библиотеки Retrofit 2. Динамический Url забирает на себя всю эту массу параметров, с которыми можно играться в коде на сколько хватит фантазии.
Это был полезный опыт, которым я и спешу поделится, мне в свое время эта статья реально бы убрала кучу проблем и вопросов.
В статье я не стал углубляться в ненужные подробности по Retrofit 2 и OData и указал ссылки на документацию если возникнет необходимость вникнуть глубже.
Полную версию кода предоставить не могу, за что приношу извинения, продукт коммерческий.
И, как обещал, ссылки:
→ Документация Microsoft: Open Data Protocol
→ Documentation OData 4.0 Java Library
→ Создание полнофункциональных интернет-приложений с применением Open Data Protocol
Спасибо огромное 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
listing 1// 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-клиент
Все эти зависимости и есть мощнейшая библиотека для работы с с сетью в Java.
Описывать работу с Retrofit 2 не вижу смысла, вот есть хороший мануал: Используем Retrofit 2 в Android-приложении.
Создаем класс NetworkService:
listing 2
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:
listing 3public interface ApiService { // Делаем запрос без условий @GET("odata/product") Observable<List<ProductsDto>> getProducts(); }
И создаем какой нибудь контроллер что бы дергать запросы:
listing 4public 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() { } }); }
Итак, на сервере храниться модель данных 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 забирает на себя всю эту массу параметров, с которыми можно играться в коде на сколько хватит фантазии.
Выглядеть это будет как то так:
listing 7private 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() { } }); }
Итоги
Это был полезный опыт, которым я и спешу поделится, мне в свое время эта статья реально бы убрала кучу проблем и вопросов.
В статье я не стал углубляться в ненужные подробности по Retrofit 2 и OData и указал ссылки на документацию если возникнет необходимость вникнуть глубже.
Полную версию кода предоставить не могу, за что приношу извинения, продукт коммерческий.
И, как обещал, ссылки:
→ Документация Microsoft: Open Data Protocol
→ Documentation OData 4.0 Java Library
→ Создание полнофункциональных интернет-приложений с применением Open Data Protocol
