OData + RxJava + Retrofit 2 for android application

Faced on a project with a hitherto unseen problem. I had to smoke the documentation and in this article I will tell you how using RxJava and Retrofit 2 - you can solve the problem of creating an Odata client for an android application.

Thank you so much Jake Wharton for creating such comfortable tools.

Welcome to the world of magic


We have an application that, according to the Odata protocol , must extract data from the server, display it in lists that should be loaded as it scrolls, and send the data created by the user to the server. A trivial task, but it wasnโ€™t here, what works without problem in Java - does not want to work with android as well.

And the libraries and documentation on Odata are only from Apache - Olingo and Microsoft in C #.

In this article I will not consider the Odata protocol , Microsoft has very good documentation and I will leave links at the end of the article.

Here is a brief definition with the Wiki Open_Data_Protocol

Open Data Protocol (OData)
Open Data Protocol (OData) โ€” - . , HTTP-, XML JSON.

4.0, OData โ€” , OASIS.

And here the most interesting part begins, Odata is a kind of SQL in the REST API, and thatโ€™s what it is for dynamically generated data.

But we have a strongly typed language and without knowledge of the model - data processing and storage create a rather difficult task.

The solution of which cannot be standard and repeatedly described on the network.

I will say even more, except for these links to documentation on the network - everything is bad on the topic.

Now let's do the magic:

Create a service for working with the network.

We will use Retrofit 2 and related Square products.

add dependencies to the build.gradle file
// 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

All these dependencies are the most powerful network library in Java. I donโ€™t see the point of

describing how to work with Retrofit 2 , here is a good manual: We use Retrofit 2 in an Android application .

Create the NetworkService class:
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

Create the API:
public interface ApiService {
//    
@GET("odata/product")
Observable<List<ProductsDto>> getProducts();
}
listing 3

And we create some kind of controller to pull requests:
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

So, the Poduct data model is stored on the server, which has some parameters and attributes. All data goes in JSON format and for work we need a POJO class.

I recommend in HttpLoggingInterceptor to configure the level of interception detail - Level.BODY.

We make a listing 4 request to maximize the data structure and the answer will be approximately in this format:

//         
//   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

And already on the basis of this data it is possible to form a request for further data manipulations.

That is, this is a full-fledged object with some kind of behavior and attributes, in order to get this information when creating a request, it is necessary to add conditions on the basis of which we will receive our DataSource without inventing a new bicycle and without screwing crutches to it.

The climax and puppy delight of the comfort of the instrument


And here he moment is true, the power, strength and simplicity of the Retrofit 2 library . Now you can get properties using the Odata service document :

//   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

Here it is the Silushka heroic library Retrofit 2 . Dynamic Url takes on all this mass of parameters with which you can play in the code for as long as you have enough imagination.

It will look something like this:
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

Summary


It was a useful experience, which I hasten to share, in due time this article would really remove a bunch of problems and questions.

In the article, I did not go into unnecessary details on Retrofit 2 and OData and indicated links to the documentation if there was a need to delve deeper.

I canโ€™t provide the full version of the code, for which I apologize, the product is commercial.

And, as promised, links:

โ†’  Microsoft Documentation: Open Data Protocol
โ†’  Documentation OData 4.0 Java Library
โ†’  Create fully functional Internet applications using the Open Data Protocol

All Articles