Skip to content

Commit 6086672

Browse files
committed
Implement ParseQueryPager
A utility class to page through `ParseQuery` results
1 parent 629ac20 commit 6086672

File tree

3 files changed

+425
-131
lines changed

3 files changed

+425
-131
lines changed

ParseUI-Widget-Sample/src/main/java/com/parse/ui/widget/sample/ListActivity.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import android.os.Bundle;
44
import android.support.annotation.Nullable;
55
import android.support.v7.app.AppCompatActivity;
6+
import android.util.Log;
67
import android.widget.ListView;
78
import android.widget.Toast;
89

@@ -16,6 +17,8 @@
1617

1718
public class ListActivity extends AppCompatActivity {
1819

20+
private static final String TAG = "ListActivity";
21+
1922
@Override
2023
protected void onCreate(@Nullable Bundle savedInstanceState) {
2124
super.onCreate(savedInstanceState);
@@ -36,11 +39,12 @@ public ParseQuery<ParseObject> create() {
3639
adapter.addOnQueryLoadListener(new ParseQueryAdapter.OnQueryLoadListener<ParseObject>() {
3740
@Override
3841
public void onLoading() {
39-
42+
Log.d(TAG, "loading");
4043
}
4144

4245
@Override
4346
public void onLoaded(List<ParseObject> objects, Exception e) {
47+
Log.d(TAG, "loaded");
4448
if (e != null
4549
&& e instanceof ParseException
4650
&& ((ParseException) e).getCode() != ParseException.CACHE_MISS) {

ParseUI-Widget/src/main/java/com/parse/ParseQueryAdapter.java

Lines changed: 61 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,14 @@
3232
import android.widget.LinearLayout;
3333
import android.widget.TextView;
3434

35-
import com.parse.ParseQuery.CachePolicy;
35+
import com.parse.widget.util.ParseQueryPager;
3636

3737
import java.util.ArrayList;
38-
import java.util.Collections;
3938
import java.util.Iterator;
4039
import java.util.List;
41-
import java.util.Set;
4240
import java.util.WeakHashMap;
43-
import java.util.concurrent.ConcurrentHashMap;
4441

45-
import bolts.Capture;
42+
import bolts.CancellationTokenSource;
4643

4744
/**
4845
* A {@code ParseQueryAdapter} handles the fetching of objects by page, and displaying objects as
@@ -112,15 +109,23 @@ public interface OnQueryLoadListener<T extends ParseObject> {
112109
void onLoaded(List<T> objects, Exception e);
113110
}
114111

112+
private final Object lock = new Object();
113+
private ParseQueryPager<T> pager;
114+
private CancellationTokenSource cts;
115+
116+
//region Backwards compatibility
117+
private ParseQuery<T> query;
118+
private int objectsPerPage = 25;
119+
//endregion
120+
121+
private Integer itemResourceId;
122+
115123
// The key to use to display on the cell text label.
116124
private String textKey;
117125

118126
// The key to use to fetch an image for display in the cell's image view.
119127
private String imageKey;
120128

121-
// The number of objects to show per page (default: 25)
122-
private int objectsPerPage = 25;
123-
124129
// Whether the table should use the built-in pagination feature (default:
125130
// true)
126131
private boolean paginationEnabled = true;
@@ -142,24 +147,6 @@ public interface OnQueryLoadListener<T extends ParseObject> {
142147

143148
private Context context;
144149

145-
private List<T> objects = new ArrayList<>();
146-
147-
private Set<ParseQuery> runningQueries =
148-
Collections.newSetFromMap(new ConcurrentHashMap<ParseQuery, Boolean>());
149-
150-
151-
// Used to keep track of the pages of objects when using CACHE_THEN_NETWORK. When using this,
152-
// the data will be flattened and put into the objects list.
153-
private List<List<T>> objectPages = new ArrayList<>();
154-
155-
private int currentPage = 0;
156-
157-
private Integer itemResourceId;
158-
159-
private boolean hasNextPage = true;
160-
161-
private QueryFactory<T> queryFactory;
162-
163150
private List<OnQueryLoadListener<T>> onQueryLoadListeners =
164151
new ArrayList<>();
165152

@@ -277,7 +264,7 @@ public ParseQueryAdapter(Context context, QueryFactory<T> queryFactory, int item
277264
private ParseQueryAdapter(Context context, QueryFactory<T> queryFactory, Integer itemViewResource) {
278265
super();
279266
this.context = context;
280-
this.queryFactory = queryFactory;
267+
query = queryFactory.create();
281268
itemResourceId = itemViewResource;
282269
}
283270

@@ -290,13 +277,32 @@ public Context getContext() {
290277
return context;
291278
}
292279

280+
private ParseQueryPager<T> getPager() {
281+
synchronized (lock) {
282+
if (pager == null) {
283+
pager = new ParseQueryPager<T>(query, objectsPerPage) {
284+
@Override
285+
protected ParseQuery<T> createQuery(int page) {
286+
// Workaround for backwards compatibility
287+
ParseQuery<T> query = new ParseQuery<>(getQuery());
288+
setPageOnQuery(page, query);
289+
return query;
290+
}
291+
};
292+
cts = new CancellationTokenSource();
293+
}
294+
295+
return pager;
296+
}
297+
}
298+
293299
/** {@inheritDoc} **/
294300
@Override
295301
public T getItem(int index) {
296302
if (index == getPaginationCellRow()) {
297303
return null;
298304
}
299-
return objects.get(index);
305+
return getPager().getObjects().get(index);
300306
}
301307

302308
/** {@inheritDoc} **/
@@ -337,18 +343,15 @@ public void unregisterDataSetObserver(DataSetObserver observer) {
337343
* Remove all elements from the list.
338344
*/
339345
public void clear() {
340-
objectPages.clear();
341-
cancelAllQueries();
342-
syncObjectsWithPages();
343-
notifyDataSetChanged();
344-
currentPage = 0;
345-
}
346-
347-
private void cancelAllQueries() {
348-
for (ParseQuery q : runningQueries) {
349-
q.cancel();
346+
synchronized (lock) {
347+
if (cts != null) {
348+
cts.cancel();
349+
}
350+
pager = null;
351+
cts = null;
350352
}
351-
runningQueries.clear();
353+
354+
notifyDataSetChanged();
352355
}
353356

354357
/**
@@ -359,118 +362,45 @@ private void cancelAllQueries() {
359362
* {@code false}.
360363
*/
361364
public void loadObjects() {
362-
loadObjects(0, true);
365+
loadNextPage(true);
363366
}
364367

365-
private void loadObjects(final int page, final boolean shouldClear) {
366-
final ParseQuery<T> query = queryFactory.create();
367-
368-
if (objectsPerPage > 0 && paginationEnabled) {
369-
setPageOnQuery(page, query);
368+
private void loadNextPage(final boolean shouldClear) {
369+
if (shouldClear && pager != null) {
370+
cts.cancel();
371+
pager = null;
370372
}
371373

372374
notifyOnLoadingListeners();
373375

374-
// Create a new page
375-
if (page >= objectPages.size()) {
376-
objectPages.add(page, new ArrayList<T>());
377-
}
378-
379-
// In the case of CACHE_THEN_NETWORK, two callbacks will be called. Using this flag to keep
380-
// track of the callbacks.
381-
final Capture<Boolean> firstCallBack = new Capture<>(true);
382-
383-
runningQueries.add(query);
384-
385-
// TODO convert to Tasks and CancellationTokens
386-
// (depends on https://github.com/ParsePlatform/Parse-SDK-Android/issues/6)
387-
query.findInBackground(new FindCallback<T>() {
376+
getPager().loadNextPage(new FindCallback<T>() {
388377
@Override
389-
public void done(List<T> foundObjects, ParseException e) {
390-
if (!runningQueries.contains(query)) {
378+
public void done(List<T> results, ParseException e) {
379+
if (results == null && e == null) { // cancelled
391380
return;
392381
}
393-
// In the case of CACHE_THEN_NETWORK, two callbacks will be called. We can only remove the
394-
// query after the second callback.
395-
if (Parse.isLocalDatastoreEnabled() ||
396-
(query.getCachePolicy() != CachePolicy.CACHE_THEN_NETWORK) ||
397-
(query.getCachePolicy() == CachePolicy.CACHE_THEN_NETWORK && !firstCallBack.get())) {
398-
runningQueries.remove(query);
399-
}
400382

383+
// Backwards compatibility
401384
if ((!Parse.isLocalDatastoreEnabled() &&
402-
query.getCachePolicy() == CachePolicy.CACHE_ONLY) &&
385+
query.getCachePolicy() == ParseQuery.CachePolicy.CACHE_ONLY) &&
403386
(e != null) && e.getCode() == ParseException.CACHE_MISS) {
404387
// no-op on cache miss
405388
return;
406389
}
407390

408-
if ((e != null) &&
409-
((e.getCode() == ParseException.CONNECTION_FAILED) ||
410-
(e.getCode() != ParseException.CACHE_MISS))) {
411-
hasNextPage = true;
412-
} else if (foundObjects != null) {
413-
if (shouldClear && firstCallBack.get()) {
414-
runningQueries.remove(query);
415-
cancelAllQueries();
416-
runningQueries.add(query); // allow 2nd callback
417-
objectPages.clear();
418-
objectPages.add(new ArrayList<T>());
419-
currentPage = page;
420-
firstCallBack.set(false);
421-
}
422-
423-
// Only advance the page, this prevents second call back from CACHE_THEN_NETWORK to
424-
// reset the page.
425-
if (page >= currentPage) {
426-
currentPage = page;
427-
428-
// since we set limit == objectsPerPage + 1
429-
hasNextPage = (foundObjects.size() > objectsPerPage);
430-
}
431-
432-
if (paginationEnabled && foundObjects.size() > objectsPerPage) {
433-
// Remove the last object, fetched in order to tell us whether there was a "next page"
434-
foundObjects.remove(objectsPerPage);
435-
}
436-
437-
List<T> currentPage = objectPages.get(page);
438-
currentPage.clear();
439-
currentPage.addAll(foundObjects);
391+
notifyDataSetChanged();
440392

441-
syncObjectsWithPages();
442-
443-
// executes on the UI thread
444-
notifyDataSetChanged();
445-
}
446-
447-
notifyOnLoadedListeners(foundObjects, e);
393+
notifyOnLoadedListeners(results, e);
448394
}
449-
});
450-
}
451-
452-
/**
453-
* This is a helper function to sync the objects with objectPages. This is only used with the
454-
* CACHE_THEN_NETWORK option.
455-
*/
456-
private void syncObjectsWithPages() {
457-
objects.clear();
458-
for (List<T> pageOfObjects : objectPages) {
459-
objects.addAll(pageOfObjects);
460-
}
395+
}, cts.getToken());
461396
}
462397

463398
/**
464399
* Loads the next page of objects, appends to table, and notifies the UI that the model has
465400
* changed.
466401
*/
467402
public void loadNextPage() {
468-
if (objects.size() == 0 && runningQueries.size() == 0) {
469-
loadObjects(0, false);
470-
}
471-
else {
472-
loadObjects(currentPage + 1, false);
473-
}
403+
loadNextPage(false);
474404
}
475405

476406
/**
@@ -482,7 +412,7 @@ public void loadNextPage() {
482412
*/
483413
@Override
484414
public int getCount() {
485-
int count = objects.size();
415+
int count = getPager().getObjects().size();
486416

487417
if (shouldShowPaginationCell()) {
488418
count++;
@@ -689,7 +619,7 @@ public void setAutoload(boolean autoload) {
689619
return;
690620
}
691621
this.autoload = autoload;
692-
if (this.autoload && !dataSetObservers.isEmpty() && objects.isEmpty()) {
622+
if (this.autoload && !dataSetObservers.isEmpty() && getPager().getObjects().isEmpty()) {
693623
loadObjects();
694624
}
695625
}
@@ -725,11 +655,12 @@ private View getDefaultView(Context context) {
725655
}
726656

727657
private int getPaginationCellRow() {
728-
return objects.size();
658+
return getPager().getObjects().size();
729659
}
730660

731661
private boolean shouldShowPaginationCell() {
732-
return paginationEnabled && objects.size() > 0 && hasNextPage;
662+
ParseQueryPager<T> pager = getPager();
663+
return paginationEnabled && pager.getObjects().size() > 0 && pager.hasNextPage();
733664
}
734665

735666
private void notifyOnLoadingListeners() {

0 commit comments

Comments
 (0)