Как запустить 2 запроса последовательно в наблюдаемом Android RxJava?

Я хочу запустить 2 асинхронные задачи, одну за другой (последовательно). Я читал что-то о ZIP или Flat, но я не очень хорошо понял это...

моя цель-загрузить данные из локального SQLite, и когда он завершается, он вызывает запрос на сервер (удаленный).

может кто-то предлагает мне, способ достичь этого?

это наблюдаемый скелет RxJava, который я использую (одна задача):

    // RxJava Observable
    Observable.OnSubscribe<Object> onSubscribe = subscriber -> {
        try {

            // Do the query or long task...

            subscriber.onNext(object);
            subscriber.onCompleted();
        } catch (Exception e) {
            subscriber.onError(e);
        }
    };

    // RxJava Observer
    Subscriber<Object> subscriber = new Subscriber<Object>() {
        @Override
        public void onCompleted() {
            // Handle the completion
        }

        @Override
        public void onError(Throwable e) {
            // Handle the error
        }

        @Override
        public void onNext(Object result) {

          // Handle the result

        }
    };

    Observable.create(onSubscribe)
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(subscriber);

3 ответов


оператор для этого будет merge см. http://reactivex.io/documentation/operators/merge.html.

мой подход состоял бы в создании двух наблюдаемых, скажем observableLocal и observableRemote, и объединить вывод:

Observable<Object> observableLocal = Observable.create(...)
Observable<Object> observableRemote = Observable.create(...)
Observable.merge(observableLocal, observableRemote)
          .subscribe(subscriber)

если вы хотите убедиться, что удаленный запускается после локального, вы можете использовать concat.


ответ Лукаса Батто лучше всего, если запросы не зависят друг от друга. Однако, если вам необходимо получить данные из локального запроса SQLite до вы запускаете удаленный запрос (например, вам нужны данные для параметров или заголовков удаленного запроса), затем вы можете начать с локального наблюдаемого, а затем flatmap, чтобы объединить два наблюдаемых после вы получаете данные из локального запроса:

   Observable<Object> localObservable = Observable.create(...)
   localObservable.flatMap(object -> 
   {
       return Observable.zip(Observable.just(object), *create remote observable here*, 
           (localObservable, remoteObservable) -> 
           {
               *combining function*
           });
   }).subscribe(subscriber);

функция flatmap позволяет преобразовать локальное наблюдаемое в комбинацию локальных и удаленных наблюдаемых с помощью функции zip. И чтобы повторить, преимущество здесь в том, что два наблюдаемых последовательны, и функция zip будет работать только после запуска обоих зависимых наблюдаемых.

кроме того, функция zip позволит вам комбинировать наблюдаемые объекты, даже если базовые объекты имеют разные типы. В этом случае вы предоставляете комбинирующую функцию в качестве 3-го параметра. Если базовые данные того же типа, замените функцию zip слиянием.


вы можете попробовать мои решения, есть несколько способов решить вашу проблему.
Чтобы убедиться, что он работает, я создал автономный рабочий пример и использую этот API для тестирования:https://jsonplaceholder.typicode.com/posts/1

private final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://jsonplaceholder.typicode.com/posts/")
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();

    private final RestPostsService restPostsService = retrofit.create(RestPostsService.class);

    private Observable<Posts> getPostById(int id) {
        return restPostsService.getPostsById(id);
    }

RestPostService.java

package app.com.rxretrofit;

import retrofit2.http.GET;
import retrofit2.http.Path;
import rx.Observable;

/**
 * -> Created by Think-Twice-Code-Once on 11/26/2017.
 */

public interface RestPostsService {

    @GET("{id}")
    Observable<Posts> getPostsById(@Path("id") int id);
}

Решения1: используйте при вызове нескольких задач в последовательностях, результатом предыдущих задач всегда является ввод следующего задания

getPostById(1)
                .concatMap(posts1 -> {
                    //get post 1 success
                    return getPostById(posts1.getId() + 1);
                })
                .concatMap(posts2 -> {
                    //get post 2 success
                    return getPostById(posts2.getId() + 1);
                })
                .concatMap(posts3 -> {
                    //get post 3success
                    return getPostById(posts3.getId() + 1);
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(finalPosts -> {
                    //get post 4 success
                    Toast.makeText(this, "Final result: " + finalPosts.getId() + " - " + finalPosts.getTitle(),
                            Toast.LENGTH_LONG).show();
                });

Solution2: используйте при вызове нескольких задач в последовательности, все результаты предыдущих задач являются входными данными конечной задачи (например: после загрузки изображения Аватара и изображения обложки вызовите api для создания нового пользователя с этими URL-адресами изображений):

Observable
                .zip(getPostById(1), getPostById(2), getPostById(3), (posts1, posts2, posts3) -> {
                    //this method defines how to zip all separate results into one
                    return posts1.getId() + posts2.getId() + posts3.getId();
                })
                .flatMap(finalPostId -> {
                    //after get all first three posts, get the final posts,
                    // the final posts-id is sum of these posts-id
                    return getPostById(finalPostId);
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(finalPosts -> {
                    Toast.makeText(this, "Final posts: " + finalPosts.getId() + " - " + finalPosts.getTitle(),
                            Toast.LENGTH_SHORT).show();
                });

AndroidManifest

 <uses-permission android:name="android.permission.INTERNET"/>

корень строить.Gradle в

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'me.tatarka:gradle-retrolambda:3.2.0'
        classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }

    // Exclude the version that the android plugin depends on.
    configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app / build.Gradle в

apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.1"
    defaultConfig {
        applicationId "app.com.rxretrofit"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'

    provided 'org.projectlombok:lombok:1.16.6'
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    compile 'com.squareup.retrofit2:converter-gson:2.3.0'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
    compile 'io.reactivex:rxandroid:1.2.1'
}

модель

package app.com.rxretrofit;
import com.google.gson.annotations.SerializedName;
/**
 * -> Created by Think-Twice-Code-Once on 11/26/2017.
 */
public class Posts {
    @SerializedName("userId")
    private int userId;
    @SerializedName("id")
    private int id;
    @SerializedName("title")
    private String title;
    @SerializedName("body")
    private String body;
    public int getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
}

кстати, использовать Rx + дооснащение + Кинжал + шаблон MVP это прекрасно совмещать.