Kinvey – Simple Aggregates and Paging

I have been working with Kinvey as the backend for Critters 2.0 for about a month. I was having problems setting a filter on a paged query and getting a total count of results with it.

Getting the Data

The first part of getting the data was easy. The Kinvey documentation makes this a simple process. Note that I am using the blocking versions of the various methods available as I am making these calls from an AsyncTask:


   // Get the list
   AsyncLinkedData<PetEntity> pets = mKinveyClient.linkedData(
     "Pets", PetEntity.class);
   pets.setCache(new InMemoryLRUCache(), CachePolicy.CACHEFIRST);

   try {
    Get data = pets.getBlocking(q1, null, null, null, 0, false);
    mPetEntities = data.execute();
   } catch (IOException e) {
    Log.e(TAG, e.getMessage());
   }

Getting the Aggregate Row Count

The Kinvey documentation did not cover this very well; however, their excellent support was able to provide me with a solution (hint: access the undocumented “_result” field of the query result set). I know if I was staying sharp while debugging, I would have caught this myself:


 try {
     GenericJson[] ret = mKinveyClient
       .appData("Pets", PetEntity[].class)
       .countBlocking(fields, q1).execute();
     retval = Integer.valueOf((ret[0].get("_result"))
       .toString());
    } catch (IOException e) {
     Log.e(TAG, e.getMessage());
    }
   } else {
    retval = 0;
   }

Complete AsyncTask Listing

Here is my complete task that uses the above snippets to page a filtered query for display. One should note that the variable mQuery is pulled from a filtering module and contains the filter keywords (unfinished in the example code):


private int mVisibleThreshold = 10;
private int mCurrentItem = 0;
private int mItemsReturned = 0;

/**
 * Get request, a simple asynchronous task.
 */
private class GetRequest extends AsyncTask<Integer, Void, Integer> {
 // Member variables
 private int mStartingAt = 0;
 private String mQuery = null;
 private Client mKinveyClient;
 private Object[] mPetEntities;
 private boolean mNetworkPing = false;

 /**
  * Creates a new instance of <tt>NetworkRequest<tt>.
  */
 public GetRequest(int startingAt, String query) {
  Log.d(TAG, "GetRequest()");

  this.mStartingAt = startingAt;
  this.mQuery = query;
  mKinveyClient = ((App) App.getContext()).getKinveyService();
 }

 /**
  * Performs the work of the network request in the background
  * 
  * @param params
  *            The parameters of the task.
  * 
  * @return A result, defined by the subclass of this task.
  */
 @SuppressWarnings("rawtypes")
 @Override
 protected Integer doInBackground(Integer... params) {
  Log.d(TAG, "doInBackground()");

  Integer retval = 0;

  // Check network connectivity
  try {
   mNetworkPing = mKinveyClient.pingBlocking();
  } catch (IOException e1) {
   Log.e(TAG, e1.getMessage());
  }

  // Get the requested data
  if (mNetworkPing) {
   // Set query paging options and sort
   Query q1 = mKinveyClient.query();
   q1.setLimit(mVisibleThreshold);
   q1.setSkip(mStartingAt);
   q1.addSort("name", SortOrder.ASC);

   if (mQuery != null) {
    q1.startsWith("name", mQuery);
   }

   // Get the list
   AsyncLinkedData<PetEntity> pets = mKinveyClient.linkedData(
     "Pets", PetEntity.class);
   pets.setCache(new InMemoryLRUCache(), CachePolicy.CACHEFIRST);

   try {
    Get data = pets.getBlocking(q1, null, null, null, 0, false);
    mPetEntities = data.execute();
   } catch (IOException e) {
    Log.e(TAG, e.getMessage());
   }

   // Get total record count in cloud
   if (mPetEntities.length > 0) {
    ArrayList<String> fields = new ArrayList<String>();
    fields.add("_acl");

    try {
     GenericJson[] ret = mKinveyClient
       .appData("Pets", PetEntity[].class)
       .countBlocking(fields, q1).execute();
     retval = Integer.valueOf((ret[0].get("_result"))
       .toString());
    } catch (IOException e) {
     Log.e(TAG, e.getMessage());
    }
   } else {
    retval = 0;
   }
  }

  return retval;
 }

 /**
  * Runs on the UI thread after doInBackground(Params...).
  * 
  * @param Params
  *            The result of the operation computed by
  *            doInBackground(Params...).
  */
 @Override
 protected void onPostExecute(Integer listSize) {
  Log.d(TAG, "onPostExecute()");

  if (mNetworkPing) {
   // No more data
   if (mStartingAt > listSize) {
    loadComplete(getString(R.string.list_no_more_data_msg));

    // No data at all (not an empty search)
   } else if (mStartingAt == 0 && listSize == 0) {
    getActivity().findViewById(R.id.pet_list_empty_view)
      .setVisibility(View.VISIBLE);

    getActivity().findViewById(R.id.initializing_list_view)
      .setVisibility(View.GONE);

    // Get more data
   } else {
    // Make sure empty list message is hidden
    if (getActivity().findViewById(R.id.pet_list_empty_view)
      .getVisibility() == View.VISIBLE) {

     getActivity().findViewById(R.id.pet_list_empty_view)
       .setVisibility(View.GONE);

     getActivity().findViewById(R.id.initializing_list_view)
       .setVisibility(View.GONE);
    }

    // Create a new list otherwise we will get issues with
    // concurrent modifications
    ArrayList<GenericJson> list = new ArrayList<GenericJson>();

    for (PetEntity item : (PetEntity[]) mPetEntities) {
     list.add(item);
    }

    mAdapter.add(list);
    mItemsReturned = list.size();

    loadComplete(getString(R.string.list_finished_loading_msg1)
      + listSize
      + getString(R.string.list_finished_loading_msg2));
   }
  } else {
   showNetworkErrorDialog();
  }
 }

 /**
  * Runs on the UI thread before doInBackground(Params...).
  */
 @Override
 protected void onPreExecute() {
  Log.d(TAG, "onPreExecute()");

  loadStarted();
 }

 /**
  * Runs on the UI thread after publishProgress(Progress...) is invoked.
  * The specified values are the values passed to
  * publishProgress(Progress...).
  */
 @Override
 protected void onProgressUpdate(Void... values) {
  Log.d(TAG, "onProgressUpdate");
 }
}

I hope this helps some you trying to address the same problem.

Please follow and like us:

Leave a Reply

Your email address will not be published. Required fields are marked *