2 Minutes: Paginating on REST API using Retrofit2 and RxAndroid

While making myself familiar with RxAndroid today ‘ve found a very interesting problem.

Let’s say we have a paginated REST resource (I’ve used GET users from https://reqres.in) and Retrofit. So my API declaration is

public interface FakeAPIDeclaration {
@GET("users")
Observable<UsersResponse> getUsers(@Query("page") int page);
}

But I want to have Observable<User>as my internal data source

public interface IMainModel {
Observable<User> getUsers();
}

Didn’t have much idea about it, and all the variants I’ve seen on Internet weren’t really satisfying. To be honest, most of them, with all that recursive subscribing, looked ugly to me.

But then I’ve found that you can make an Observable from an Iterable…

@Override
public Observable<User> getUsers() {
return Observable.from(new Iterable<UsersResponse>() {

int page = 1;
boolean hasNext = true;

@Override
public Iterator<UsersResponse> iterator() {
return new Iterator<UsersResponse>() {
@Override
public boolean hasNext() {
return hasNext;
}

@Override
public UsersResponse next() {
UsersResponse usersResponse = api.getUsers(page).toBlocking().first();
if (Integer.parseInt(usersResponse.getPage()) == usersResponse.getTotalPages()) {
hasNext = false;
} else {
page++;
}
return usersResponse;
}
};
}
}).flatMap(new Func1<UsersResponse, Observable<User>>() {
@Override
public Observable<User> call(UsersResponse usersResponse) {
return Observable.from(usersResponse.getData());
}
});
}

Here we treat every HTTP request as a separate item from an Iterator. We’re able to say when the last item is reached (except for the empty first page, of course) and stop giving new “items”. Then, using .flatMap(), we convert each List<User> into separate User instances being fired to our internal code.

If you want to have Observable<List<User>> in your Model (so users are received in bundles) you can simply change it to

@Override
public Observable<List<User>> getUsersInLists() {
return Observable.from(new Iterable<UsersResponse>() {

int page = 1;
boolean hasNext = true;

@Override
public Iterator<UsersResponse> iterator() {
return new Iterator<UsersResponse>() {
@Override
public boolean hasNext() {
return hasNext;
}

@Override
public UsersResponse next() {
UsersResponse usersResponse = api.getUsers(page).toBlocking().first();
if (Integer.parseInt(usersResponse.getPage()) == usersResponse.getTotalPages()) {
hasNext = false;
} else {
page++;
}
return usersResponse;
}
};
}
}).map(new Func1<UsersResponse, List<User>>() {
@Override
public List<User> call(UsersResponse usersResponse) {
return usersResponse.getData();
}
});
}

It’s all about changing .flatMap() to .map() to simply convert type from UsersResponse to List<User>.

From my point of view, it looks good enough, and it also works as required :) So I’ll put it here just in case somebody will face the same issue one day.

Since I’m really new to Rx, I’ll be glad to hear some opinion from an experienced folk on this matter.


2 Minutes are less-then-one-cigarette, while-brewing-coffee articles about different questions in Android. You can read other ones using tag 2 Minutes. I’m always happy to hear a feedback from you on how to make this thing better and what you’d like to read about.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.