Столкнулся на проекте с проблемой доселе не виданной. Пришлось покурить документацию и в этой статье я расскажу как с помощью 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
// 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:
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:
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