2016-01-12 27 views
7

Ich möchte 2 asynchrone Aufgaben ausführen, eine nacheinander gefolgt von der anderen (sequenziell). Ich habe etwas über ZIP oder Flat gelesen, aber ich habe es nicht gut verstanden ...Wie zwei Abfragen nacheinander in einem Android RxJava Observable ausgeführt werden?

Mein Zweck ist es, die Daten von einem lokalen SQLite zu laden, und wenn es fertig ist, ruft es die Abfrage an den Server (Fernbedienung).

Kann mir jemand einen Weg vorschlagen, das zu erreichen?

Dies ist das RxJava beobachtbare Skelett, das ich (Einzelaufgabe) bin mit:

// 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); 
+1

Mögliches Duplikat von [RxJava: Verkettung von Observablen] (http://stackoverflow.com/questions/26935821/rxjava-chaining-observables) –

Antwort

6

Der Betreiber, das zu tun merge wäre, sehen http://reactivex.io/documentation/operators/merge.html.

Mein Ansatz wäre zwei Observablen zu schaffen, sagen wir mal observableLocal und observableRemote und fusionieren die Ausgabe:

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

Wenn Sie sicherstellen möchten, dass remote nach lokalen ausgeführt wird, Sie concat verwenden können.

+1

Dies funktioniert nur, wenn ObservableLocal und ObservableRemote dasselbe Objekt oder etwas zurückgeben, das dieses Objekt erweitert – Sniper

6

Lukas Batteaus Antwort ist am besten, wenn die Abfragen nicht voneinander abhängig sind. Wenn Sie jedoch die Daten von der lokalen SQLite-Abfrage vor benötigen, führen Sie die Remoteabfrage aus (Sie benötigen beispielsweise die Daten für die ferne Abfrageparameter oder Header). Dann können Sie mit der lokalen Observablen und dann Flatmap beginnen sie kombinieren die beiden Observablen nach Sie die Daten aus der lokalen Abfrage erhalten:

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

die flatmap Funktion können Sie die lokale beobachtbaren in eine Kombination der lokalen & Fern Observablen über die Zip-Funktion zu transformieren können. Und um es noch einmal zu wiederholen: Der Vorteil besteht darin, dass die beiden Observablen sequenziell sind und die Zip-Funktion nur dann läuft, wenn beide abhängigen Observablen laufen.

Darüber hinaus können Sie mit der Zip-Funktion Observable kombinieren, auch wenn die zugrunde liegenden Objekte unterschiedliche Typen haben. In diesem Fall geben Sie eine Kombinationsfunktion als dritten Parameter an. Wenn die zugrunde liegenden Daten vom selben Typ sind, ersetzen Sie die ZIP-Funktion durch eine Zusammenführung.

1

Sie können meine Lösungen versuchen, es gibt mehrere Möglichkeiten, um Ihr Problem zu lösen.
Um sicherzustellen, dass es funktioniert, habe ich eine Stand-alone-Beispiel arbeiten und diese API-Test verwenden: 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); 
} 

Solution1: Verwendung beim Aufruf mehrerer Aufgaben in Sequenzen, ist das Ergebnis der früheren Aufgaben immer die Eingabe der nächsten Aufgabe

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 : Wenn mehrere Tasks in Sequenzen aufgerufen werden, ist das Ergebnis aller vorherigen Tasks das In Put der endgültigen Aufgabe (zum Beispiel: nach Avatar-Bild und Cover-Bild hochladen, api ruft neue Benutzer mit diesem Bild-URLs erstellen):

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

root build.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' 
} 

Modell

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; 
    } 
} 

By the way, verwenden Rx + Retrofit + Dolch + MVP-Muster ist ein großer kombinieren.