We are using ReactiveX and Retrofit in our network stack to handle all API requests in an asynchronous way.
Our goal is to create one method that will return a completely populated collection of User
models. Each User
model has a list of Pet
objects. We can fetch all of the User
models with one request. However, Pet
models need to be requested per User
.
Getting users is simple:
// Service.java
@GET("users/?locationId={id}")
Observable<List<User>> getUsersForLocation(@Path("id") int locationId);
@GET("pets/?userId={id}")
Observable<List<Pet>> getPetsForUser(@Path("id") int userId);
// DataManager.java
public Observable<List<User>> getUsersForLocation(int locationId) {
return api.getUsersForLocation(locationId);
}
public Observable<List<Pet>> getPetsForUser(int userId) {
return api.getPetsForUser(userId);
}
We would like to find some convenient (RX style) way of looping through the User
list, fetching the Pet
s for that each user, assigning them to the User
and ultimately returning the Observable<List<User>>
.
I am fairly new to RX. I've looked over the documentation and have tried using various methods such as flatMap()
and zip
, however, I have yet to find the exact combination of transforms or combiners to make it happen.
I wrote a small sample app that makes what you are trying to achieve. Here the components:
public class Datasource {
public Observable<List<User>> users() {
return Observable.just(Arrays.asList(
new User("1", "Foo"), new User("2", "Bar")
));
}
public Observable<List<Pet>> pets(User user) {
return Observable.just(Arrays.asList(
new Pet(user.getName() + "'s cat"),
new Pet(user.getName() + "'s dog")
));
}
}
Pet class:
public class Pet {
private String mName;
public Pet(String name) {
mName = name;
}
public String getName() {
return mName;
}
@Override
public String toString() {
return "Pet{" +
"mName='" + mName + '\'' +
'}';
}
}
User class:
public class User {
private String mName;
private String mId;
private List<Pet> mPetList;
public User(String id, String name) {
this(id, name, Collections.<Pet>emptyList());
}
public User(String id, String name, List<Pet> pets) {
mName = name;
mId = id;
mPetList = pets;
}
public String getName() {
return mName;
}
public String getId() {
return mId;
}
public User copyWithPets(List<Pet> pets) {
return new User(mId, mName, pets);
}
@Override
public String toString() {
return "User{" +
"mName='" + mName + '\'' +
", mId='" + mId + '\'' +
", mPetList=" + mPetList +
'}';
}
}
Put all together:
datasource.users()
.flatMap(new Func1<List<User>, Observable<User>>() {
@Override
public Observable<User> call(List<User> users) {
return Observable.from(users);
}
})
.flatMap(new Func1<User, Observable<User>>() {
@Override
public Observable<User> call(final User user) {
return datasource.pets(user)
.map(new Func1<List<Pet>, User>() {
@Override
public User call(List<Pet> pets) {
return user.copyWithPets(pets);
}
});
}
})
.toList()
.subscribe(new Action1<List<User>>() {
@Override
public void call(List<User> users) {
for (User user : users) {
Log.d(TAG, "user: " + user.toString());
}
}
});
It produces:
D/MainActivity: user: User{mName='Foo', mId='1', mPetList=[Pet{mName='Foo's cat'}, Pet{mName='Foo's dog'}]}
D/MainActivity: user: User{mName='Bar', mId='2', mPetList=[Pet{mName='Bar's cat'}, Pet{mName='Bar's dog'}]}
If it doesn't answer you question please post you actual datamodel for User and Pet.