3232import android .widget .LinearLayout ;
3333import android .widget .TextView ;
3434
35- import com .parse .ParseQuery . CachePolicy ;
35+ import com .parse .widget . util . ParseQueryPager ;
3636
3737import java .util .ArrayList ;
38- import java .util .Collections ;
3938import java .util .Iterator ;
4039import java .util .List ;
41- import java .util .Set ;
4240import 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