The following scenarios are available:

Nested callbacks often occur when the front segment calls the back-end API. Suppose we have two apis, queryA and queryB. And queryB runs depending on the results of queryA. So our program might look something like this in the general case.

Imagine code like this:

Do you feel very uncomfortable? A few more layers of nested apis would be a disaster. It may not seem like a problem when you’re developing alone, but you can imagine the faces of your colleagues doing code review or new to the project when they see your complicated nesting.

It’s time for flatMap! Let’s tweak the application a bit and change the makeRequest method to an RxJava Observable in the Server class (I’ll explain why flatmap() later) :

Doesn’t look like it’s all neat, does it? You wait for me to show you if the nesting of several more layers:

See? Under RxJava’s chained calls, all the places that previously needed to be nested are separated by flatMap(). Code readability greatly increased! If your IDE supports Java 8, you can experience something even better: lambda!

The code is much cleaner after ditching the nasty anonymous classes!

It seems that many students do not quite understand why flatMap is used to solve nested callbacks. So let’s go a little deeper.

What exactly is flatMap() for? Simply put, flatMap transforms an Observable into multiple Observables, and then emits multiple Obervable elements one by one.

Suppose you have an API called queryA that uses this query to get the userids of all the stars that a given user is interested in from a userID(but the raw data is just strings). You need to print out the userids one by one.

So when we call queryA, we’ve created Obervable, which we’ll call O1. Each time O1 emits, we need to call the returned String into another Observable, O2, which has all the star userids and emits them one by one. Code examples:

As you can see, the new Observable2 is of type JsonObject. Since we return a new Observable (possibly multiple) with all userids after the comment in line 3, RxJava will tiled this (or more)Observable.

Or you can refer to the example of the student and the course in the parabola article which is sort of in the middle of the article

In short, the most core function of flatmap can be understood as transformation, which can transform one Obervable into multiple Observable, and then talk about the result of tiling emission.

In our example, one Observable (O1) becomes another observable(O2), a one-to-one relationship. Since queryA only returns a String s result, we only tiled and emitted one Observable (O2).

So what does this have to do with nested callback?

It matters!

Think again, what’s the difference between what we just did and what nested callback does? The only exception is that when we get all the star userids, we are doing a synchronous operation, meaning that the userids are all included in the results returned by queryA. We encapsulate it synchronously as a JSONArray.

What if all queryA returns is the URL where the user follows the star userID? What if we need to do another API call in Part 2?

Yes, even if we need another API call, the structure of the application is the same:

The core function of flatMap can be understood as transformation, which can transform one Obervable into multiple Observables and then emit the results.

We don’t need to think too much about synchronous or asynchronous Observable operations in the transformed Observable. Rxjava has solved the problem perfectly.

Reference: www.jianshu.com/p/0f926fda6…

Extension: Sample project (experience flatMap usage)

Add dependencies

The compile 'com. Squareup. Retrofit2: retrofit: 2.0.0 - beta4' compile 'IO. Reactivex: rxjava: 1.1.1' compile 'com. Squareup. Retrofit2: converter - gson: 2.0.0 - beta4' / / gson parser compile 'IO. Reactivex: rxandroid: 1.1.0' / / rxjava used in AndroidSchedulers. MainThread () the compile 'com. Squareup. Retrofit2: adapter - rxjava: 2.0.0 - beta4' Retrofit2.0 is used here and the default is okhttp3.0Copy the code

Define request interface, convert HTTPAPI to Java interface

public interface IgankApi { @GET("a.json") Call<List<GirlEntity>> getGirl(); @GET("data/%E7%A6%8F%E5%88%A9/{count}/{page}") Call<GirlJsonData> getGirl(@Path("count") int count, @Path("page") int page); @get ("data/%E7%A6%8F%E5%88%A9/{count}/{page}") Observable<GirlJsonData> getG(@path ("count") int count, @Path("page") int page); }Copy the code

The implementation of the interface is then generated using the class Retrofit, using dynamic proxies.

   public static IgankApi getIgankApi() {

        if (igankApi == null) {
            synchronized (IgankApi.class) {
                if (igankApi == null) {
                    Retrofit retrofit = new Retrofit.Builder().baseUrl("http://gank.io/api/")
                            .addConverterFactory(gsonConverterFactory)
                            .client(okHttpClient)
                            .addCallAdapterFactory(rxJavaCallAdapterFactory)
                            .build();
                    igankApi = retrofit.create(IgankApi.class);
                }
            }
        }
        return igankApi;
    }
Copy the code

4. Call interface

private void getImg() {
        NetUtils.getIgankApi().getG(10, 2).flatMap(new Func1<GirlJsonData, Observable<List<GirlEntity>>>() {
            @Override
            public Observable<List<GirlEntity>> call(GirlJsonData girlJsonData) {
                return Observable.just(girlJsonData.getResults());
            }
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<List<GirlEntity>>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(List<GirlEntity> girlEntities) {
                        girlEntityList.addAll(girlEntities);
                        loge(girlEntityList.get(1).getUrl() + "");
                        myRecycleViewAdapter.notifyDataSetChanged();
                    }
                });

    }
Copy the code

Project code: github.com/jdsjlzx/Gir…

Or consider the following scenario:

When accessing an interface, you need to fill in a token obtained online instead of directly accessing it.

Callback method, which can be nested with Callback:

@GET("/token") public void getToken(Callback<String> callback); @GET("/user") public void getUser(@Query("token") String token, @Query("userId") String userId, Callback<User> callback); . getToken(new Callback<String>() { @Override public void success(String token) { getUser(userId, new Callback<User>() { @Override public void success(User user) { userView.setUser(user); } @Override public void failure(RetrofitError error) { // Error handling ... }}; } @Override public void failure(RetrofitError error) { // Error handling ... }});Copy the code

Using RxJava, the code looks like this:

@GET("/token") public Observable<String> getToken(); @GET("/user") public Observable<User> getUser(@Query("token") String token, @Query("userId") String userId); . getToken() .flatMap(new Func1<String, Observable<User>>() { @Override public Observable<User> call(String token) { return getUser(token, userId); }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<User>() { @Override public void onNext(User user) { userView.setUser(user); } @Override public void onCompleted() { } @Override public void onError(Throwable error) { // Error handling ... }});Copy the code

Using the flatMap() operator solves the logic, which is still a chain.

The function of the flatMap() operator is to transform the collection of Observables emitted by an Observable into a collection of Observables, and then flatten the data emitted by these Observables into a single Observable, which is still too abstract.

Simply convert each entry in a List or array into an Observable.

Scene:

So let’s say we write a web request, and we pull out all the data for the request, and now we don’t need that much data, we just need the city field and the WD field, and instead of just giving you one URL, I’m giving you multiple urls.

Let’s start writing code (code is the best teacher) :

public static final String HOST = "http://www.weather.com.cn"; List<String> values = new ArrayList<>(); private String TAG = "SecondActivity2"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); values.add("/adat/sk/101010100.html"); values.add("/adat/sk/101010100.html"); values.add("/adat/sk/101010100.html"); values.add("/adat/sk/101010100.html"); values.add("/adat/sk/101010100.html"); Observable.just(values).flatMap(new Func1<List<String>, Observable<? >>() { @Override public Observable<? > call(List<String> strings) { return Observable.from(strings); } }).cast(String.class).map(new Func1<String, String>() { @Override public String call(String s) { return doNetTaskForString(HOST + s); // Retrieve the desired field, I am not going out here}}) subscribeOn (Schedulers. NewThread ()). ObserveOn (AndroidSchedulers. MainThread ()). The subscribe (new Action1<String>() { @Override public void call(String s) { Log.i(TAG, "value: " + s); }}); } @NonNull @Override public int getContentView() { return R.layout.activity_second2; } private synchronized String doNetTaskForString(String s) { HttpClient client = new DefaultHttpClient(); Log.i(TAG, "url:" + s); HttpGet get = new HttpGet(s); String result; try { HttpResponse response = client.execute(get); Log.i(TAG, "state code :" + response.getStatusLine().getStatusCode()); if (200 == response.getStatusLine().getStatusCode()) { result = EntityUtils.toString(response.getEntity(), HTTP.UTF_8); } else {result = "status line not 200"; }} catch (Exception e1) {result = "throw Exception" + e1.getMessage(); e1.printStackTrace(); } return result; }Copy the code

Print the log:

The new operator cast is used again in this code.

Cast forces all data emitted by an Observable to be converted to the specified type before launching.

Here’s another example.

Here retrofit is used to return an Observable through STR of a city in flatMap. The parameters of this Observable are some obtained weather information structure WeatherData. This is then processed in the subscrib that follows.

FlatMap multithreaded tasks

A single thread has the following code:

private Observable<String> processUrlIpByOneFlatMap() { return Observable.just( "http://www.baidu.com/", "http://www.google.com/", "https://www.bing.com/") .flatMap(new Func1<String, Observable<String>>() { @Override public Observable<String> call(String s) { return createIpObservable(s); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<String>() { @Override public void call(String s) { printLog(tvLogs, "Consume Data <- ", s); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { printErrorLog(tvLogs, "throwable call()", throwable.getMessage()); }}); Private Observable<String> createIpObservable(final String URL) {return Observable. Create (new) Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { try { String ip = getIPByUrl(url); subscriber.onNext(ip); printLog(tvLogs, "Emit Data -> ",url+" : " +ip); } catch (MalformedURLException e) { e.printStackTrace(); //subscriber.onError(e); subscriber.onNext(null); } catch (UnknownHostException e) { e.printStackTrace(); //subscriber.onError(e); subscriber.onNext(null); } subscriber.onCompleted(); }}); }Copy the code

Execution Result:

Emit Data -> 'http://www.baidu.com/ : 115.239.211.112 'Main Thread: false, Thread Name: Consume RxCachedThreadScheduler - 1 Data < - '115.239.211.112' Main Thread: true, Thread Name:main Emit Data -> 'http://www.google.com/ : 216.58.199.100 'Main Thread: false, Thread Name: Consume RxCachedThreadScheduler - 1 Data < - '216.58.199.100' Main Thread: true, Thread Name:main Emit Data -> 'https://www.bing.com/ : 202.89.233.104 'Main Thread: false, The Thread Name: Consume RxCachedThreadScheduler - 1 Data < - '202.89.233.104 Main Thread: true, Thread Name: MainCopy the code

As we can see from the output above, the effect is the same as using the map operator.

We also find that Thread names are rxCachedThreadScheduler-1, indicating that they complete all tasks through a single Thread.

multithreading

If you have a lot of tasks, isn’t it inefficient to just do them through one thread? What if I want to use multiple threads to complete these tasks?

Very simple, just add subscribeOn(schedulers.io ()) when creating an Observable. The complete code is as follows:

Private Observable<String> createIpObservable(final String URL) {return Observable. Create (new) Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { try { String ip = getIPByUrl(url); subscriber.onNext(ip); printLog(tvLogs, "Emit Data -> ",url+" : " +ip); } catch (MalformedURLException e) { e.printStackTrace(); //subscriber.onError(e); subscriber.onNext(null); } catch (UnknownHostException e) { e.printStackTrace(); //subscriber.onError(e); subscriber.onNext(null); } subscriber.onCompleted(); } }) .subscribeOn(Schedulers.io()); }Copy the code

Execution Result:

Consume Data <- '202.89.233.103' Main Thread:true, Thread Name: Main Emit Data -> 'https://www.bing.com/ : 202.89.233.103 'Main Thread: false, Thread Name: RxCachedThreadScheduler - 8 Emit Data - > "http://www.google.com/ : 216.58.203.36 'Main Thread: false, The Thread Name: RxCachedThreadScheduler - 7 Consume Data < - '216.58.203.36' Main Thread: true, Thread Name:main Emit Data -> 'http://www.baidu.com/ : 115.239.211.112 'Main Thread: false, The Thread Name: RxCachedThreadScheduler - 6 Consume Data < - '115.239.211.112' Main Thread: true, Thread Name: MainCopy the code

As can be seen from the execution, the completed task is not performed by one thread, but by three different threads rxCachedThreadScheduler-8, rxCachedThreadScheduler-7, and rxCachedThreadScheduler-6.

But found a problem, the order of the output the result of wrong, is not we enter baidu.com/google.com/bing.com order.

So what to do?

Then concatMap operator dependent, details: blog.csdn.net/jdsjlzx/art…

Refer to the article: blog.csdn.net/johnny90111…