How can multiple requests used to populate models

2019-03-27 10:10发布

问题:

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 Pets 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.

回答1:

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.