Skip to content

Commit cf93cc7

Browse files
committed
Merge pull request #129 from ParsePlatform/grantland.pager
Implement ParseQueryPager
2 parents 629ac20 + e9af63f commit cf93cc7

File tree

3 files changed

+441
-131
lines changed

3 files changed

+441
-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: 69 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,38 @@ 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+
if (paginationEnabled) {
289+
setPageOnQuery(page, query);
290+
}
291+
return query;
292+
}
293+
};
294+
cts = new CancellationTokenSource();
295+
}
296+
297+
return pager;
298+
}
299+
}
300+
301+
private List<T> getObjects() {
302+
return getPager().getObjects();
303+
}
304+
293305
/** {@inheritDoc} **/
294306
@Override
295307
public T getItem(int index) {
296308
if (index == getPaginationCellRow()) {
297309
return null;
298310
}
299-
return objects.get(index);
311+
return getObjects().get(index);
300312
}
301313

302314
/** {@inheritDoc} **/
@@ -337,18 +349,15 @@ public void unregisterDataSetObserver(DataSetObserver observer) {
337349
* Remove all elements from the list.
338350
*/
339351
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();
352+
synchronized (lock) {
353+
if (cts != null) {
354+
cts.cancel();
355+
}
356+
pager = null;
357+
cts = null;
350358
}
351-
runningQueries.clear();
359+
360+
notifyDataSetChanged();
352361
}
353362

354363
/**
@@ -359,118 +368,47 @@ private void cancelAllQueries() {
359368
* {@code false}.
360369
*/
361370
public void loadObjects() {
362-
loadObjects(0, true);
371+
loadNextPage(true);
363372
}
364373

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);
374+
private void loadNextPage(final boolean shouldClear) {
375+
synchronized (lock) {
376+
if (shouldClear && pager != null) {
377+
cts.cancel();
378+
pager = null;
379+
}
370380
}
371381

372382
notifyOnLoadingListeners();
373383

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>() {
384+
getPager().loadNextPage(new FindCallback<T>() {
388385
@Override
389-
public void done(List<T> foundObjects, ParseException e) {
390-
if (!runningQueries.contains(query)) {
386+
public void done(List<T> results, ParseException e) {
387+
if (results == null && e == null) { // cancelled
391388
return;
392389
}
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-
}
400390

391+
// Backwards compatibility
401392
if ((!Parse.isLocalDatastoreEnabled() &&
402-
query.getCachePolicy() == CachePolicy.CACHE_ONLY) &&
393+
query.getCachePolicy() == ParseQuery.CachePolicy.CACHE_ONLY) &&
403394
(e != null) && e.getCode() == ParseException.CACHE_MISS) {
404395
// no-op on cache miss
405396
return;
406397
}
407398

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;
399+
notifyDataSetChanged();
427400

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);
440-
441-
syncObjectsWithPages();
442-
443-
// executes on the UI thread
444-
notifyDataSetChanged();
445-
}
446-
447-
notifyOnLoadedListeners(foundObjects, e);
401+
notifyOnLoadedListeners(results, e);
448402
}
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-
}
403+
}, cts.getToken());
461404
}
462405

463406
/**
464407
* Loads the next page of objects, appends to table, and notifies the UI that the model has
465408
* changed.
466409
*/
467410
public void loadNextPage() {
468-
if (objects.size() == 0 && runningQueries.size() == 0) {
469-
loadObjects(0, false);
470-
}
471-
else {
472-
loadObjects(currentPage + 1, false);
473-
}
411+
loadNextPage(false);
474412
}
475413

476414
/**
@@ -482,7 +420,7 @@ public void loadNextPage() {
482420
*/
483421
@Override
484422
public int getCount() {
485-
int count = objects.size();
423+
int count = getObjects().size();
486424

487425
if (shouldShowPaginationCell()) {
488426
count++;
@@ -689,7 +627,7 @@ public void setAutoload(boolean autoload) {
689627
return;
690628
}
691629
this.autoload = autoload;
692-
if (this.autoload && !dataSetObservers.isEmpty() && objects.isEmpty()) {
630+
if (this.autoload && !dataSetObservers.isEmpty() && getObjects().isEmpty()) {
693631
loadObjects();
694632
}
695633
}
@@ -725,11 +663,12 @@ private View getDefaultView(Context context) {
725663
}
726664

727665
private int getPaginationCellRow() {
728-
return objects.size();
666+
return getObjects().size();
729667
}
730668

731669
private boolean shouldShowPaginationCell() {
732-
return paginationEnabled && objects.size() > 0 && hasNextPage;
670+
ParseQueryPager<T> pager = getPager();
671+
return paginationEnabled && pager.getObjects().size() > 0 && pager.hasNextPage();
733672
}
734673

735674
private void notifyOnLoadingListeners() {

0 commit comments

Comments
 (0)