RxJava development skills 8-seamless integration with REST-RxJava and Retrofit
In this chapter, we will create a final versionReal world
In this example, retroing data to remote APIs with Retrofit asynchronously queries data, thus creating a rich UI with no effort.
Project objectives
We will create a newActivity
. ThisActivity
The StackExchange API will be used to retrieve the most active 10 users from stackoverflow. With this information, the App will display a user's profile picture, name, fame count, and address list. For each user, the app uses its residential city and OpenWeatherMap API to retrieve local weather forecasts and display a small weather icon. Based on the information retrieved from StackOverflow, the app providesonClick
Event, which will open the personal website they specified in personal information or the personal homepage of Stack Overflow.
Retrofit
Elastic fit is a type-safe REST client designed by Square for Android and Java. It helps you easily interact with any rest api. It perfectly integrates R small Java: All JSON response objects are mapped to original Java objects, and all network calls are based on Rxjava Observable objects.
Using the API documentation, we can define the JSON response data we receive from the server. To easily map JSON response data to our Java code, we will use jsonschema2pojo. This flexible service will generate all the Java classes we need to map JSON response data.
After we have prepared all the Java models, we can start to build the adaptive fit. Retrofi uses standard Java interfaces to map API routes. In the example, we will use a route from the API. below is our ingress fit interface:
public interface StackExchangeService { @GET("2.2users?order=desc&sort=reputation&site=stackoverflow") Observable
getMostPopularSOusers(@Query("pagesize") int howmany);}
interface
The Interface contains only one method, that isgetMostPopularSOusers
. This method uses integerhowmany
As a parameter and returnUserResponse
.
When we haveinterface
, We can createRestAdapter
Class, to better organize our code, we createSeApiManager
Functions provide a more appropriate way to interact with StackExchange APIs.
public class SeApiManager { private final StackExchangeService mStackExchangeService; public SeApiManager() { RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("https://api.stackexchange.com") .setLogLevel(RestAdapter.LogLevel.BASIC) .build(); mStackExchangeService = restAdapter.create(StackExchangeService.class);}public Observable
> getMostPopularSOusers(int howmany) { return mStackExchangeService .getMostPopularSOusers(howmany) .map(UsersResponse::getUsers) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread());}
To simplify the example, we no longer design this class as a singleton that should have been designed. Using dependency injection solutions, such as Dagger2, will lead to higher code quality.
CreateRestAdapter
Class. We have created several important points for the API client. In this example, we setendpoint
Andlog level
. Because the URL in this example is hard-coded, it is important to use external resources to store data like this. It is a good practice to avoid hard encoding strings in the code.
Modify fitRestAdapter
Class and our API interface are bound together and then created. It returns an object to query the API. We can choose to expose this object directly, or encapsulate it in a certain way to restrict access to it. In this example, we encapsulate it and only exposegetMostPopularSOusers
Method. This method is used to execute the query and let parse fit parse the JSON response data. Obtain the user list and return it to the subscriber. As you can see, with Retrofit, RxJava, and lambda, we have almost no template code: It is very compact and highly readable.
Now we have an API manager to expose a responsive method, which obtains data from the Remote API and sends it to the I/O scheduler, resolution ing finally provides a concise user list for our consumers.
App Architecture
We do not use any MVC, MVP, or MVVM mode. Because it is not the purpose of this book, so ourActivity
Class will contain all the logic that we need to create and display the user list.
Create Activity class
We willonCreate()
CreateSwipeRefreshLayout
AndRecyclerView
; We haverefreshList()
To obtain and display the user list,showRefreshing()
To manage the progress bar andRecyclerView
.
OurrefreshList()
The function looks as follows:
private void refreshList() { showRefresh(true); mSeApiManager.getMostPopularSOusers(10) .subscribe(users -> { showRefresh(false); mAdapter.updateUsers(users); }, error -> { App.L.error(error.toString()); showRefresh(false); });}
The progress bar is displayed to view the user list from the StackExchange API manager. Once the list data is obtained, we start to display it and update it.Adapter
AndRecyclerView
Displayed as visible.
Create a RecyclerView Adapter
After we get data from the rest api, We need to bind it to the View and fill the list with an adapter. Our RecyclerView adapter is standard. It inherits fromRecyclerView.Adapter
And specify its ownViewHolder
:
public static class ViewHolder extends RecyclerView.ViewHolder { @InjectView(R.id.name) TextView name; @InjectView(R.id.city) TextView city; @InjectView(R.id.reputation) TextView reputation; @InjectView(R.id.user_image) ImageView user_image; public ViewHolder(View view) { super(view); ButterKnife.inject(this, view); }}
Once we receive data from the API manager, we can set all the labels on the interface:name
,city
Andreputation
.
To show the user's profile picture, we will useUniversal Image Loader
. UIL is a well-known and tested image management library. We can also use Square's Picasso, Glide, or Facebook's Fresco. This is only based on your hobbies. What's important is that you don't need to repeat the wheel: Libraries help developers live and help them achieve their goals more quickly.
In our adapter, we can do this:
@Overridepublic void onBindViewHolder(SoAdapter.ViewHolder holder, int position) { User user = mUsers.get(position); holder.setUser(user); }
InViewHolder
, We can do this:
public void setUser(User user) { name.setText(user.getDisplayName()); city.setText(user.getLocation()); reputation.setText(String.valueOf(user.getReputation())); ImageLoader.getInstance().displayImage(user.getProfileImage(), user_image);}
At this point, we can allow the code to get a list of users, as shown in:
Search weather forecasts
We increased the difficulty and added the weather of the local city to the list.OpenWeatherMapIs a flexible web service public API. We can query and retrieve many useful forecast information.
As usual, we will map using Retrofit to the API and access it through RxJava. For the StackExchange API, we will createinterface
,RestAdapter
And a flexible Manager:
public interface OpenWeatherMapService { @GET("data2.5/weather") Observable
getForecastByCity(@Query("q") String city);}
This method uses the city name as a parameter to provide local forecast information. We willRestAdapter
Classes are bound together:
RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("http://api.openweathermap.org") .setLogLevel(RestAdapter.LogLevel.BASIC) .build();mOpenWeatherMapService = restAdapter.create(OpenWeatherMapService.class);
As before, we only need to set the API port and log level: we only need to do two things immediately.
OpenWeatherMapApiManager
Class provides the following methods:
public Observable
getForecastByCity(String city) { return mOpenWeatherMapService.getForecastByCity(city) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread());}
Now that we have a user list, we can query OpenWeatherMap Based on the city name to receive weather forecast information. The next step is to modify ourViewHolder
Class to retrieve and use weather forecast information for each user to display the weather icon according to the status.
We use these tools to verify the user homepage information and obtain a legal city name:
private boolean isCityValid(String location) { int separatorPosition = getSeparatorPosition(location); return !"".equals(location) && separatorPosition > -1; }private int getSeparatorPosition(String location) { int separatorPosition = -1; if (location != null) { separatorPosition = location.indexOf(","); } return separatorPosition; }private String getCity(String location, int position) { if (location != null) { return location.substring(0, position); } else { return ""; }}
With a valid city name, we can use the following command to obtain all the data of the weather we need:
OpenWeatherMapApiManager.getInstance().getForecastByCity(city)
With the weather response results, we can get the URL of the weather map:
getWeatherIconUrl(weatherResponse);
With the icon URL, we can retrieve the icon itself:
private Observable
loadBitmap(String url) { return Observable.create(subscriber -> { ImageLoader.getInstance().displayImage(url,city_image, new ImageLoadingListener() { @Override public void onLoadingStarted(String imageUri, View view) { } @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { subscriber.onError(failReason.getCause()); } @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { subscriber.onNext(loadedImage); subscriber.onCompleted(); } @Override public void onLoadingCancelled(String imageUri, View view) { subscriber.onError(new Throwable("Image loading cancelled")); } }); });}
ThisloadBitmap()
The returned Observable can be linked to the previous one, and finally we can return a separate Observable for this task:
if (isCityValid(location)) { String city = getCity(location, separatorPosition); OpenWeatherMapApiManager.getInstance().getForecastByCity(city) .filter(response -> response != null) .filter(response -> response.getWeather().size() > 0) .flatMap(response -> { String url = getWeatherIconUrl(response); return loadBitmap(url); }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer
() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { App.L.error(e.toString()); } @Override public void onNext(Bitmap icon) { city_image.setImageBitmap(icon); } });}
Run the code to obtain the new weather icon for each user in the following list:
Open website
Using the information contained on the user homepage, we will createonClick
The listener to navigate to the user's web page, if any, or the Stack Overflow personal homepage.
In order to implement it, we simply implementActivity
Class interface, used to trigger AndroidonClick
Event.
OurAdapter ViewHolder
Specify this interface:
public interface OpenProfileListener { public void open(String url); }
Activity
Implement it:
[https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.] implements SoAdapter.ViewHolder.OpenProfileListener { [https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.] mAdapter.setOpenProfileListener(this); [https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.]@Overridepublic void open(String url) { Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse(url)); startActivity(i);}
Activity
Open the URL with an external Android browser. OurViewHolder
Create on each card in the user listOnClickListener
And check whether we open the Stack Overflow user homepage or the external personal site:
mView.setOnClickListener(view -> { if (mProfileListener != null) { String url = user.getWebsiteUrl(); if (url != null && !url.equals("") && !url.contains("search")) { mProfileListener.open(url); } else { mProfileListener.open(user.getLink()); } })};
Once we click, We will redirect directly to the expected website. On Android, we can use a special form of RxAndroid (ViewObservable) to achieve the same result in a more responsive way.
ViewObservable.clicks(mView) .subscribe(onClickEvent -> { if (mProfileListener != null) { String url = user.getWebsiteUrl(); if (url != null && !url.equals("") && !url.contains("search")) { mProfileListener.open(url); } else { mProfileListener.open(user.getLink()); } }});
The above two code snippets are equivalent. You can choose your favorite method.
Summary
Our journey is over. You are ready to bring your Java application to a new level of code quality. You can enjoy a new coding mode and get in touch with your daily coding life in a smoother way of thinking. RxJava provides the opportunity to consider data in a time-oriented manner: Everything is continuously changeable, data is being updated, and events are being triggered. Then you can create responses based on these events, A flexible and smooth App.
Switching to RxJava at the beginning seems difficult and time-consuming, but we have experienced how to effectively handle daily problems in a responsive manner. Now you can migrate your old code to RxJava:getters
A new responsive life.
RxJava is a developing and expanding world. There are still many methods we haven't explored yet. Some methods are not even available. Because of RxJava, you can create your own operators and push them further.
Android is a fun place, but it also has limitations. As an Android developer, you can use RxJava and RxAndroid to overcome many of them. We used AndroidScheduler to briefly mention RxAndroid. Apart from the last chapter, you understandViewObservable
. RxAndroid gives you a lot: for example,WidgetObservable
,LifecycleObservable
. Now it depends on you to push more.
Remember that the observed sequence is like a river: they flow. You can "filter" a river, you can "Switch" a river, you can combine the two rivers into one, and then it still flows. Finally, it will become the river you want.