Skip to content

Instantly share code, notes, and snippets.

@tuanchauict
Last active October 23, 2015 01:35
Show Gist options
  • Save tuanchauict/42fa0466b191db5d31e6 to your computer and use it in GitHub Desktop.
Save tuanchauict/42fa0466b191db5d31e6 to your computer and use it in GitHub Desktop.
ParseListLoader is an universal version of ParseQueryAdapter that we can use it on both ListView/GridView and RecyclerView as well as ViewPager

There are two things to do before using ParseListLoader:

####1. Implement LoaderTarget

Just implement the interface LoaderTarget on our Adapter (any kind of adapter), there are three functions to implement with just 1 more LOC for them:

public void appendList(List<T> sublist); // list.addAll(sublist);
public void clearList(); // list.clear();
public void notifyDataChanged(); // notifyDataSetChanged();

####2. Create a loader

Create a ParseListLoader instance with 2 parameters: LoaderTarget and QueryFactory (see TestParseListLoaderActivity.java).

Then call loader.loadObjects() or loader.loadNext()


Next version: Support ResourceCursorAdapter

/*
* Copyright (C) 2015 Tuan Chau (tuanchauict)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.parse;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import bolts.Capture;
/**
* Created by tuanchauict on 10/5/15.
*/
public class ParseListLoader<T extends ParseObject> {
private List<List<T>> mObjectPages;
private int mCurrentPage;
private boolean mHasNextPage;
private LoaderTarget<T> mTarget;
private QueryFactory<T> mQueryFactory;
private List<OnQueryLoadListener<T>> mOnQueryLoadListeners;
private int mObjectsPerPage;
private List<ParseQuery> mRunningQueries;
public ParseListLoader(@NonNull LoaderTarget<T> target, @NonNull QueryFactory<T> factory) {
mTarget = target;
mQueryFactory = factory;
mObjectPages = new ArrayList<>();
mCurrentPage = -1; // init -1 so that we don't care about call loadObjects or loadNext first
mHasNextPage = true;
mOnQueryLoadListeners = new ArrayList<>();
mObjectsPerPage = 25;
mRunningQueries = new ArrayList<>();
}
public void setObjectsPerPage(int objectsPerPage) {
mObjectsPerPage = objectsPerPage;
}
public int getObjectsPerPage() {
return mObjectsPerPage;
}
public void addOnQueryLoadListener(OnQueryLoadListener<T> onQueryLoadListener) {
mOnQueryLoadListeners.add(onQueryLoadListener);
}
public void clearOnQueryLoadListeners() {
mOnQueryLoadListeners.clear();
}
public boolean hasOnQueryLoadListeners() {
return !mOnQueryLoadListeners.isEmpty();
}
/**
* support this function because sometime, we have to force the list reload
*
* @param hasNextPage
*/
public void setHasNextPage(boolean hasNextPage) {
mHasNextPage = hasNextPage;
}
public boolean hasNextPage() {
return mHasNextPage;
}
public void loadObjects() {
this.loadObjects(0, true);
}
public void loadNextPage() {
loadObjects(mCurrentPage + 1, false);
}
private void loadObjects(int page, boolean shouldClear) {
if (!mHasNextPage) {
return;
}
if (mQueryFactory == null) {
return;
}
ParseQuery<T> query = mQueryFactory.create();
if (query == null) {
return;
}
mRunningQueries.add(query);
setPageOnQuery(page, query);
notifyOnLoadingListeners();
if (page >= mObjectPages.size()) {
for (int i = mObjectPages.size(); i <= page; i++) {
mObjectPages.add(new ArrayList<>());
}
}
int objectsPerPage = mObjectsPerPage;
Capture<Boolean> firstCallback = new Capture<>(Boolean.TRUE);
query.findInBackground(new FindCallback<T>() {
@Override
public void done(List<T> foundObjects, ParseException e) {
if (Parse.isLocalDatastoreEnabled()
|| query.getCachePolicy() != ParseQuery.CachePolicy.CACHE_ONLY
|| e == null
|| e.getCode() != ParseException.CACHE_MISS) {
if (e != null && (e.getCode() == ParseException.CONNECTION_FAILED
|| e.getCode() != ParseException.CACHE_MISS)) {
mHasNextPage = true;
} else if (foundObjects != null) {
if (shouldClear && firstCallback.get()) {
mObjectPages.clear();
mObjectPages.add(new ArrayList<>());
mCurrentPage = page;
firstCallback.set(Boolean.FALSE);
}
if (page >= mCurrentPage) {
mCurrentPage = page;
mHasNextPage = foundObjects.size() > objectsPerPage;
}
if (mHasNextPage) {
foundObjects.remove(objectsPerPage);
}
List<T> currentPage = mObjectPages.get(page);
currentPage.clear();
currentPage.addAll(foundObjects);
syncTargetWithPages();
mTarget.notifyDataChanged();
}
}
notifyOnLoadedListeners(foundObjects, mHasNextPage, e);
mRunningQueries.remove(query);
}
});
}
private void notifyOnLoadingListeners() {
for (OnQueryLoadListener listener : mOnQueryLoadListeners) {
listener.onLoading();
}
}
private void notifyOnLoadedListeners(List<T> list, boolean hasNextPage, ParseException e) {
for (OnQueryLoadListener<T> listener : mOnQueryLoadListeners) {
listener.onLoaded(list, hasNextPage, e);
}
}
private void syncTargetWithPages() {
mTarget.clearList();
for (List<T> list : mObjectPages) {
mTarget.appendList(list);
}
}
private void setPageOnQuery(int page, ParseQuery<T> query) {
query.setLimit(mObjectsPerPage + 1);
query.setSkip(page * mObjectsPerPage);
}
public void cancelAllRunningQueries() {
for (ParseQuery query : mRunningQueries) {
query.cancel();
}
mRunningQueries.clear();
}
public boolean isLoading(){
return !mRunningQueries.isEmpty();
}
public interface LoaderTarget<T> {
void appendList(List<T> sublist);
void clearList();
void notifyDataChanged();
}
public interface QueryFactory<T extends ParseObject> {
ParseQuery<T> create();
}
public interface OnQueryLoadListener<T> {
void onLoading();
void onLoaded(List<T> list, boolean hasNextPage, ParseException e);
}
}
/*
* Copyright (C) 2015 Tuan Chau (tuanchauict)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* THIS EXAMPLE USES JAVA 8 LAMBDA EXPRESSION
*/
package com.tuanchauict.test;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.tuanchauict.R;
import com.parse.ParseListLoader;
import com.parse.ParseObject;
import com.parse.ParseQuery;
import java.util.ArrayList;
import java.util.List;
public class TestParseListLoaderActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_parse_list_loader);
ListView listView = (ListView) findViewById(R.id.listview);
Adapter adapter = new Adapter(this);
listView.setAdapter(adapter);
ParseListLoader.QueryFactory<ParseObject> queryFactory = () -> {
ParseQuery<ParseObject> query = ParseQuery.getQuery(ParseObject.class);
query.setCachePolicy(ParseQuery.CachePolicy.CACHE_ELSE_NETWORK);
return query;
};
ParseListLoader<ParseObject> loader = new ParseListLoader<>(adapter, queryFactory);
loader.loadNextPage();
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem + visibleItemCount > totalItemCount - 5) {
loader.loadNextPage();
}
}
});
}
static class Adapter extends ArrayAdapter<ParseObject> implements ParseListLoader.LoaderTarget<ParseObject> {
private List<ParseObject> girls;
public Adapter(Context context) {
super(context, 0);
girls = new ArrayList<>();
}
@Override
public void appendList(List<ParseObject> sublist) {
girls.addAll(sublist);
// Logger.d("append list: %s / %s", sublist.size(), girls.size());
}
@Override
public void clearList() {
girls.clear();
}
@Override
public void notifyDataChanged() {
notifyDataSetChanged();
}
@Override
public int getCount() {
return girls.size();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ParseObject girl = girls.get(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(android.R.layout.simple_list_item_1, parent, false);
}
TextView txt = (TextView) convertView.findViewById(android.R.id.text1);
txt.setText(girl.getString("StringKey"));
return convertView;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment