Retrofit CallAdapter改造需求:分页接口多页与全量数据获取
Alright, let's extend your existing PaginatedCall CallAdapter to support fetching subsequent pages and grabbing all results in one go. Here's a step-by-step approach tailored to your setup:
1. Update the PaginatedCall Interface
First, we need to add methods to handle next-page checks, fetching the next page, and bulk-fetching all pages. Modify your interface like this:
public interface PaginatedCall<T> { // Existing methods for first page List<T> execute() throws IOException; void enqueue(Callback<List<T>> callback); // New: Check if there's a next page available boolean hasNextPage(); // New: Get a call instance for the next page PaginatedCall<T> nextPage(); // New: Sync fetch all pages at once List<T> executeAll() throws IOException; // New: Async fetch all pages at once void enqueueAll(Callback<List<T>> callback); }
2. Modify the CallAdapter Implementation
Assuming your API returns a paginated response with metadata (like currentPage, totalPages, or a hasNext flag), we'll update the adapter to track this state and build subsequent page requests.
First, let's define a sample paginated response model (adjust this to match your actual API response structure):
public class PaginatedResponse<T> { public List<T> data; // Your list of items public int currentPage; public int totalPages; // Or use a boolean `hasNext` if your API provides that instead }
Now update the CallAdapter implementation:
public class PaginatedCallAdapter<T> implements CallAdapter<T, PaginatedCall<T>> { private final Type responseType; private final Retrofit retrofit; public PaginatedCallAdapter(Type responseType, Retrofit retrofit) { this.responseType = responseType; this.retrofit = retrofit; } @Override public Type responseType() { return responseType; } @Override public PaginatedCall<T> adapt(Call<T> call) { return new PaginatedCallImpl<>(call, retrofit); } private static class PaginatedCallImpl<T> implements PaginatedCall<T> { private final Call<T> originalCall; private final Retrofit retrofit; private PaginatedResponse<T> lastResponse; private int currentPage = 1; public PaginatedCallImpl(Call<T> originalCall, Retrofit retrofit) { this.originalCall = originalCall; this.retrofit = retrofit; } @Override public List<T> execute() throws IOException { Response<T> response = originalCall.execute(); if (!response.isSuccessful()) { throw new IOException("HTTP error code: " + response.code()); } lastResponse = (PaginatedResponse<T>) response.body(); currentPage = lastResponse.currentPage; return lastResponse.data; } @Override public void enqueue(Callback<List<T>> callback) { originalCall.enqueue(new Callback<T>() { @Override public void onResponse(Call<T> call, Response<T> response) { if (response.isSuccessful()) { lastResponse = (PaginatedResponse<T>) response.body(); currentPage = lastResponse.currentPage; callback.onResponse(lastResponse.data); } else { callback.onFailure(new IOException("HTTP error code: " + response.code())); } } @Override public void onFailure(Call<T> call, Throwable t) { callback.onFailure(t); } }); } @Override public boolean hasNextPage() { return lastResponse != null && currentPage < lastResponse.totalPages; // If your API uses a `hasNext` flag instead, use: // return lastResponse != null && lastResponse.hasNext; } @Override public PaginatedCall<T> nextPage() { if (!hasNextPage()) { throw new IllegalStateException("No more pages available"); } // Build the next page request by updating the `page` query parameter HttpUrl originalUrl = originalCall.request().url(); HttpUrl newUrl = originalUrl.newBuilder() .setQueryParameter("page", String.valueOf(currentPage + 1)) .build(); Request newRequest = originalCall.request().newBuilder() .url(newUrl) .build(); Call<T> nextCall = retrofit.newCall(newRequest); return new PaginatedCallImpl<>(nextCall, retrofit); } @Override public List<T> executeAll() throws IOException { List<T> allResults = new ArrayList<>(); PaginatedCall<T> currentCall = this; do { List<T> pageResults = currentCall.execute(); allResults.addAll(pageResults); currentCall = currentCall.hasNextPage() ? currentCall.nextPage() : null; } while (currentCall != null); return allResults; } @Override public void enqueueAll(Callback<List<T>> callback) { List<T> accumulatedResults = new ArrayList<>(); fetchNextPage(accumulatedResults, callback); } private void fetchNextPage(List<T> accumulatedResults, Callback<List<T>> finalCallback) { this.enqueue(new Callback<List<T>>() { @Override public void onResponse(List<T> pageData) { accumulatedResults.addAll(pageData); if (hasNextPage()) { nextPage().fetchNextPage(accumulatedResults, finalCallback); } else { finalCallback.onResponse(accumulatedResults); } } @Override public void onFailure(Throwable t) { finalCallback.onFailure(t); } }); } } }
3. Usage Examples
Fetch the Second Page
// Sync approach PaginatedCall<Book> bookCall = getBooks(); List<Book> firstPage = bookCall.execute(); if (bookCall.hasNextPage()) { PaginatedCall<Book> secondPageCall = bookCall.nextPage(); List<Book> secondPage = secondPageCall.execute(); // Process second page data } // Async approach bookCall.enqueue(new Callback<List<Book>>() { @Override public void onResponse(List<Book> firstPage) { if (bookCall.hasNextPage()) { bookCall.nextPage().enqueue(new Callback<List<Book>>() { @Override public void onResponse(List<Book> secondPage) { // Process second page data } @Override public void onFailure(Throwable t) { // Handle error } }); } } @Override public void onFailure(Throwable t) { // Handle error } });
Fetch All Pages at Once
// Sync approach List<Book> allBooks = getBooks().executeAll(); // Async approach getBooks().enqueueAll(new Callback<List<Book>>() { @Override public void onResponse(List<Book> allBooks) { // Process all books } @Override public void onFailure(Throwable t) { // Handle error } });
Key Notes
- API Compatibility: If your API uses Link Headers (like GitHub's API) instead of response-body metadata, adjust the
hasNextPageandnextPagemethods to parse the Link Header for the next page URL instead of manually updating thepageparameter. - Cancellation: Add a
void cancel()method toPaginatedCalland delegate it to the underlyingCallinstance to handle request cancellation (critical for avoiding memory leaks in Android). - Error Handling: Extend the callback interfaces if you need to track partial results when fetching all pages asynchronously.
内容的提问来源于stack exchange,提问作者Tom Insam

