├── libs └── CWAC-AdapterWrapper.jar ├── res └── raw │ └── only_to_commit_the_res_folder ├── demo ├── res │ ├── drawable │ │ └── ic_popup_sync_1.png │ ├── layout │ │ ├── main.xml │ │ ├── pending.xml │ │ └── row.xml │ └── values │ │ └── strings.xml ├── project.properties ├── .classpath ├── ant.properties ├── src │ └── com │ │ └── commonsware │ │ └── cwac │ │ └── endless │ │ └── demo │ │ ├── EndlessAdapterFragment.java │ │ ├── EndlessAdapterDemo.java │ │ ├── DemoAdapter.java │ │ ├── EndlessAdapterFragmentDemo.java │ │ ├── EndlessAdapterExceptionDemo.java │ │ └── EndlessAdapterCustomTaskFragment.java ├── .project ├── proguard.cfg ├── AndroidManifest.xml └── build.xml ├── AndroidManifest.xml ├── .gitignore ├── project.properties ├── .classpath ├── ant.properties ├── .project ├── proguard.cfg ├── lint.xml ├── LICENSE ├── README.markdown └── src └── com └── commonsware └── cwac └── endless └── EndlessAdapter.java /libs/CWAC-AdapterWrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podio/cwac-endless/master/libs/CWAC-AdapterWrapper.jar -------------------------------------------------------------------------------- /res/raw/only_to_commit_the_res_folder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/res/drawable/ic_popup_sync_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podio/cwac-endless/master/demo/res/drawable/ic_popup_sync_1.png -------------------------------------------------------------------------------- /demo/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | EndlessAdapter Demo 4 | Endless Exception Demo 5 | EndlessAdapter Fragment Demo 6 | Simple 7 | Custom Task 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | gen 3 | bin/classes/com/commonsware/cwac/endless/BuildConfig.class 4 | 5 | bin/AndroidManifest.xml 6 | 7 | bin/classes/com/commonsware/cwac/endless/EndlessAdapter$AppendTask.class 8 | 9 | bin/classes/com/commonsware/cwac/endless/EndlessAdapter.class 10 | 11 | bin/endlessadapter.jar 12 | 13 | bin/jarlist.cache 14 | 15 | gen/com/commonsware/cwac/endless/BuildConfig.java 16 | 17 | .settings 18 | 19 | # GRADLE 20 | build/ 21 | *.iml 22 | -------------------------------------------------------------------------------- /demo/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-11 12 | android.library.reference.1=.. 13 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | android.library=true 11 | # Project target. 12 | target=Google Inc.:Google APIs:19 13 | -------------------------------------------------------------------------------- /demo/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /demo/res/layout/pending.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked in Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | -------------------------------------------------------------------------------- /demo/ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked in Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | -------------------------------------------------------------------------------- /demo/res/layout/row.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 20 | 21 | -------------------------------------------------------------------------------- /demo/src/com/commonsware/cwac/endless/demo/EndlessAdapterFragment.java: -------------------------------------------------------------------------------- 1 | package com.commonsware.cwac.endless.demo; 2 | 3 | import android.app.ListFragment; 4 | import android.os.Bundle; 5 | import java.util.ArrayList; 6 | 7 | public class EndlessAdapterFragment extends ListFragment { 8 | DemoAdapter adapter=null; 9 | 10 | @Override 11 | public void onActivityCreated(Bundle savedInstanceState) { 12 | super.onActivityCreated(savedInstanceState); 13 | 14 | setRetainInstance(true); 15 | 16 | if (adapter==null) { 17 | ArrayList items=new ArrayList(); 18 | 19 | for (int i=0;i<25;i++) { items.add(i); } 20 | 21 | adapter=new DemoAdapter(getActivity(), items); 22 | } 23 | else { 24 | adapter.startProgressAnimation(); 25 | } 26 | 27 | setListAdapter(adapter); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | EndlessAdapter 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /demo/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | EndlessAdapterDemo 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /demo/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /demo/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /demo/src/com/commonsware/cwac/endless/demo/EndlessAdapterDemo.java: -------------------------------------------------------------------------------- 1 | /*** 2 | Copyright (c) 2008-2009 CommonsWare, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package com.commonsware.cwac.endless.demo; 16 | 17 | import android.app.ListActivity; 18 | import android.os.Bundle; 19 | import java.util.ArrayList; 20 | 21 | public class EndlessAdapterDemo extends ListActivity { 22 | @Override 23 | public void onCreate(Bundle icicle) { 24 | super.onCreate(icicle); 25 | setContentView(R.layout.main); 26 | 27 | DemoAdapter adapter=(DemoAdapter)getLastNonConfigurationInstance(); 28 | 29 | if (adapter==null) { 30 | ArrayList items=new ArrayList(); 31 | 32 | for (int i=0;i<25;i++) { items.add(i); } 33 | 34 | adapter=new DemoAdapter(this, items); 35 | } 36 | else { 37 | adapter.startProgressAnimation(); 38 | } 39 | 40 | setListAdapter(adapter); 41 | } 42 | 43 | @Override 44 | public Object getLastNonConfigurationInstance() { 45 | return(getListAdapter()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /demo/src/com/commonsware/cwac/endless/demo/DemoAdapter.java: -------------------------------------------------------------------------------- 1 | package com.commonsware.cwac.endless.demo; 2 | 3 | import android.content.Context; 4 | import android.os.SystemClock; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.animation.Animation; 9 | import android.view.animation.RotateAnimation; 10 | import android.widget.ArrayAdapter; 11 | import java.util.ArrayList; 12 | import com.commonsware.cwac.endless.EndlessAdapter; 13 | 14 | class DemoAdapter extends EndlessAdapter { 15 | private RotateAnimation rotate=null; 16 | private View pendingView=null; 17 | 18 | DemoAdapter(Context ctxt, ArrayList list) { 19 | super(new ArrayAdapter(ctxt, 20 | R.layout.row, 21 | android.R.id.text1, 22 | list)); 23 | rotate=new RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 24 | 0.5f, Animation.RELATIVE_TO_SELF, 25 | 0.5f); 26 | rotate.setDuration(600); 27 | rotate.setRepeatMode(Animation.RESTART); 28 | rotate.setRepeatCount(Animation.INFINITE); 29 | } 30 | 31 | @Override 32 | protected View getPendingView(ViewGroup parent) { 33 | View row=LayoutInflater.from(parent.getContext()).inflate(R.layout.row, null); 34 | 35 | pendingView=row.findViewById(android.R.id.text1); 36 | pendingView.setVisibility(View.GONE); 37 | pendingView=row.findViewById(R.id.throbber); 38 | pendingView.setVisibility(View.VISIBLE); 39 | startProgressAnimation(); 40 | 41 | return(row); 42 | } 43 | 44 | @Override 45 | protected boolean cacheInBackground() { 46 | SystemClock.sleep(10000); // pretend to do work 47 | 48 | return(getWrappedAdapter().getCount()<75); 49 | } 50 | 51 | @Override 52 | protected void appendCachedData() { 53 | if (getWrappedAdapter().getCount()<75) { 54 | @SuppressWarnings("unchecked") 55 | ArrayAdapter a=(ArrayAdapter)getWrappedAdapter(); 56 | 57 | for (int i=0;i<25;i++) { a.add(a.getCount()); } 58 | } 59 | } 60 | 61 | void startProgressAnimation() { 62 | if (pendingView!=null) { 63 | pendingView.startAnimation(rotate); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /demo/src/com/commonsware/cwac/endless/demo/EndlessAdapterFragmentDemo.java: -------------------------------------------------------------------------------- 1 | /*** 2 | Copyright (c) 2012 CommonsWare, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package com.commonsware.cwac.endless.demo; 16 | 17 | import android.app.ActionBar; 18 | import android.app.ActionBar.Tab; 19 | import android.app.Activity; 20 | import android.app.FragmentTransaction; 21 | import android.os.Bundle; 22 | 23 | public class EndlessAdapterFragmentDemo extends Activity implements 24 | ActionBar.TabListener { 25 | private EndlessAdapterFragment simple=null; 26 | private EndlessAdapterCustomTaskFragment customTask=null; 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | 32 | ActionBar bar=getActionBar(); 33 | 34 | bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 35 | 36 | bar.addTab(bar.newTab().setText(R.string.simple) 37 | .setTabListener(this).setTag(Tabs.TAB_SIMPLE)); 38 | bar.addTab(bar.newTab().setText(R.string.custom) 39 | .setTabListener(this).setTag(Tabs.TAB_CUSTOM_TASK)); 40 | } 41 | 42 | @Override 43 | public void onTabReselected(Tab tab, FragmentTransaction ft) { 44 | onTabSelected(tab, ft); 45 | } 46 | 47 | @Override 48 | public void onTabSelected(Tab tab, FragmentTransaction ft) { 49 | switch ((Tabs)tab.getTag()) { 50 | case TAB_SIMPLE: 51 | showSimple(ft); 52 | break; 53 | case TAB_CUSTOM_TASK: 54 | showCustom(ft); 55 | break; 56 | } 57 | } 58 | 59 | @Override 60 | public void onTabUnselected(Tab tab, FragmentTransaction ft) { 61 | // no-op 62 | } 63 | 64 | private void showSimple(FragmentTransaction ft) { 65 | if (simple == null) { 66 | simple=new EndlessAdapterFragment(); 67 | } 68 | 69 | ft.replace(android.R.id.content, simple); 70 | } 71 | 72 | private void showCustom(FragmentTransaction ft) { 73 | if (customTask == null) { 74 | customTask=new EndlessAdapterCustomTaskFragment(); 75 | } 76 | 77 | ft.replace(android.R.id.content, customTask); 78 | } 79 | 80 | private static enum Tabs { 81 | TAB_SIMPLE, TAB_CUSTOM_TASK; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /demo/src/com/commonsware/cwac/endless/demo/EndlessAdapterExceptionDemo.java: -------------------------------------------------------------------------------- 1 | /*** 2 | Copyright (c) 2008-2009 CommonsWare, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package com.commonsware.cwac.endless.demo; 16 | 17 | import android.app.ListActivity; 18 | import android.os.Bundle; 19 | import android.os.SystemClock; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.widget.ArrayAdapter; 23 | import com.commonsware.cwac.endless.EndlessAdapter; 24 | import java.util.ArrayList; 25 | 26 | public class EndlessAdapterExceptionDemo extends ListActivity { 27 | @Override 28 | public void onCreate(Bundle icicle) { 29 | super.onCreate(icicle); 30 | setContentView(R.layout.main); 31 | 32 | ArrayList items=new ArrayList(); 33 | 34 | for (int i=0;i<25;i++) { items.add(i); } 35 | 36 | setListAdapter(new DemoAdapter(items)); 37 | } 38 | 39 | class DemoAdapter extends EndlessAdapter { 40 | DemoAdapter(ArrayList list) { 41 | super(EndlessAdapterExceptionDemo.this, 42 | new SpecialAdapter(list), R.layout.pending); 43 | } 44 | 45 | @Override 46 | protected boolean cacheInBackground() throws Exception { 47 | SystemClock.sleep(10000); // pretend to do work 48 | 49 | if (getWrappedAdapter().getCount()<75) { 50 | return(true); 51 | } 52 | 53 | throw new Exception("Gadzooks!"); 54 | } 55 | 56 | @Override 57 | protected void appendCachedData() { 58 | if (getWrappedAdapter().getCount()<75) { 59 | @SuppressWarnings("unchecked") 60 | ArrayAdapter a=(ArrayAdapter)getWrappedAdapter(); 61 | 62 | for (int i=0;i<25;i++) { a.add(a.getCount()); } 63 | } 64 | } 65 | } 66 | 67 | class SpecialAdapter extends ArrayAdapter { 68 | SpecialAdapter(ArrayList items) { 69 | super(EndlessAdapterExceptionDemo.this, R.layout.row, 70 | android.R.id.text1, items); 71 | } 72 | 73 | @Override 74 | public View getView(int position, View convertView, 75 | ViewGroup parent) { 76 | View row=super.getView(position, convertView, parent); 77 | 78 | // further customize your rows here 79 | 80 | return(row); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /demo/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 51 | 63 | 64 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /demo/src/com/commonsware/cwac/endless/demo/EndlessAdapterCustomTaskFragment.java: -------------------------------------------------------------------------------- 1 | package com.commonsware.cwac.endless.demo; 2 | 3 | import android.app.ListFragment; 4 | 5 | import android.os.AsyncTask; 6 | import android.os.Bundle; 7 | import android.os.SystemClock; 8 | 9 | import android.view.animation.Animation; 10 | import android.view.animation.RotateAnimation; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import android.widget.ArrayAdapter; 15 | 16 | import com.commonsware.cwac.endless.EndlessAdapter; 17 | 18 | import java.util.ArrayList; 19 | 20 | /** 21 | * This example makes use of EndlessAdapter's setRunInBackground feature. 22 | * 23 | * Calling setRunInBackground(false) allows you to launch your own AsyncTask 24 | * with a listener callback, rather than using the built in cacheInBackground 25 | * functionality. 26 | * 27 | * This is useful if you have existing AsyncTask(s) written to fetch data in a 28 | * background thread, and don't want EndlessAdapter launching that in yet 29 | * another background thread. 30 | */ 31 | public class EndlessAdapterCustomTaskFragment extends ListFragment { 32 | 33 | DemoAdapter adapter=null; 34 | ArrayList items=null; 35 | 36 | @Override 37 | public void onActivityCreated(Bundle savedInstanceState) { 38 | super.onActivityCreated(savedInstanceState); 39 | 40 | setRetainInstance(true); 41 | 42 | if (adapter==null) { 43 | items=new ArrayList(); 44 | 45 | for (int i=0;i<25;i++) { items.add(i); } 46 | 47 | adapter=new DemoAdapter(items); 48 | adapter.setRunInBackground(false); // Tell the adapter we will handle 49 | // starting the background task 50 | } 51 | 52 | setListAdapter(adapter); 53 | } 54 | 55 | class DemoAdapter extends EndlessAdapter implements IItemsReadyListener { 56 | private RotateAnimation rotate=null; 57 | private boolean hasMoreData=true; 58 | 59 | DemoAdapter(ArrayList list) { 60 | super(new ArrayAdapter(getActivity(), R.layout.row, 61 | android.R.id.text1, list)); 62 | 63 | rotate= 64 | new RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 65 | 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 66 | rotate.setDuration(600); 67 | rotate.setRepeatMode(Animation.RESTART); 68 | rotate.setRepeatCount(Animation.INFINITE); 69 | } 70 | 71 | @Override 72 | protected View getPendingView(ViewGroup parent) { 73 | View row= 74 | getActivity().getLayoutInflater().inflate(R.layout.row, null); 75 | 76 | View child=row.findViewById(android.R.id.text1); 77 | 78 | child.setVisibility(View.GONE); 79 | 80 | child=row.findViewById(R.id.throbber); 81 | child.setVisibility(View.VISIBLE); 82 | child.startAnimation(rotate); 83 | 84 | return(row); 85 | } 86 | 87 | @Override 88 | protected boolean cacheInBackground() throws Exception { 89 | new FetchDataTask(this, items.size()).execute(); 90 | return hasMoreData; 91 | } 92 | 93 | @Override 94 | public void onItemsReady(ArrayList data) { 95 | items.addAll(data); 96 | adapter.onDataReady(); // Tell the EndlessAdapter to remove it's pending 97 | // view and call notifyDataSetChanged() 98 | hasMoreData = items.isEmpty(); 99 | } 100 | 101 | @Override 102 | protected void appendCachedData() { 103 | } 104 | } 105 | 106 | interface IItemsReadyListener { 107 | public void onItemsReady(ArrayList data); 108 | } 109 | 110 | class FetchDataTask extends AsyncTask> { 111 | IItemsReadyListener listener; 112 | 113 | /* The point from where to start counting. In a real life scenario this 114 | * could be a pagination number */ 115 | int startPoint; 116 | 117 | protected FetchDataTask(IItemsReadyListener listener, int startPoint) { 118 | this.listener = listener; 119 | this.startPoint = startPoint; 120 | } 121 | 122 | @Override 123 | protected ArrayList doInBackground(Void... params) { 124 | ArrayList result = new ArrayList(); 125 | 126 | SystemClock.sleep(3000); // pretend to do work 127 | for (int i=startPoint; i < startPoint+25; i++) { 128 | result.add(i); 129 | } 130 | 131 | return(result); 132 | } 133 | 134 | @Override 135 | protected void onPostExecute(ArrayList result) { 136 | listener.onItemsReady(result); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | CWAC EndlessAdapter: It Just Keeps Going and Going And... 2 | ========================================================= 3 | This forked version contains support for reversed scrolling. 4 | 5 | AJAX Web sites have sometimes taken up the "endless page" 6 | model, where scrolling automatically loads in new content, 7 | so you never have to click a "Next" link or anything like that. 8 | 9 | Wouldn't it be cool to have that in an Android application? 10 | Kinda like how the Android Market does it? 11 | 12 | `EndlessAdapter` is one approach to solving this problem. 13 | 14 | It is designed to wrap around another adapter, where you have 15 | your "real" data. Hence, it follows the Decorator pattern, 16 | augmenting your current adapter with new Endless Technology(TM). 17 | 18 | To use it, you extend `EndlessAdapter` to provide details about 19 | how to handle the endlessness. Specifically, you need to be 20 | able to provide a row `View`, independent from any of the rows 21 | in your actual adapter, that will serve as a placeholder 22 | while you, in another method, load in the actual data to 23 | your main adapter. Then, with a little help from you, it 24 | seamlessly transitions in the new data. 25 | 26 | So, this is not truly "endless" insofar as the user does see 27 | when we load in new data. However, it should work well for 28 | Android applications backed by Web services or the like 29 | that work on "page-at-a-time" metaphors -- users get the 30 | additional data quickly and do not incur the bandwidth to 31 | download that data until and unless they scroll all the 32 | way to the bottom. 33 | 34 | Note that this has been tested with `ArrayAdapter` extensively 35 | but may not work with other adapter types, particularly 36 | `SimpleAdapter`. It also will only work with a `ListView` or 37 | possibly other one-`View`-at-a-time `AdapterView` implementations. 38 | 39 | This is [available as a JAR file](https://github.com/commonsguy/downloads). 40 | The project itself is set up as an Android library project, 41 | in case you wish to use the source code in that fashion. 42 | 43 | Usage 44 | ----- 45 | To use `EndlessAdapter`, you need to create a subclass that 46 | will control the endlessness, specifying what `View` to use 47 | for the "loading" placeholder, and then updating that placeholder 48 | with an actual row once data has been loaded. 49 | 50 | `EndlessAdapter` assumes there is at least one more "batch" of 51 | data to be fetched. If everything was retrieved for your 52 | `ListAdapter` the first time out (e.g., the Web search returned 53 | only one "page" of results), do not wrap it in `EndlessAdapter`, 54 | and your users will not perceive a difference. 55 | 56 | ### Constructors 57 | 58 | `EndlessAdapter` has four constructors. The original one takes a `ListAdapter` as 59 | a parameter, representing the existing adapter to be made 60 | endless. Your `EndlessAdapter` subclass will need to override 61 | this constructor and chain upwards. For example, the DemoAdapter 62 | inside the demo project takes an `ArrayList` as a 63 | constructor parameter and wraps it in a `ListAdapter` to supply 64 | to `EndlessAdapter`. 65 | 66 | The second constructor takes a `Context` and resource ID along with 67 | the `ListAdapter`. These will be used to create the placeholder 68 | (see below). 69 | 70 | The remaining pair are the same as the first, but take a boolean 71 | value to indicate whether or not there should be more data to add. 72 | Normally, you would not use these, but instead would skip the 73 | `EndlessAdapter` altogether if you have no data to append. However, 74 | there may be cases where that is inconvenient, and simply keeping 75 | the `EndlessAdapter` is easier, but there is no point in bothering 76 | actually having "endless" behavior. 77 | 78 | ### The Placeholder 79 | 80 | Your `EndlessAdapter` subclass can implement `getPendingView()`. 81 | This method works a bit like the traditional `getView()`, in that 82 | it receives a `ViewGroup` parameter and is supposed to return a 83 | row `View`. The major difference is that this method needs to 84 | return a row `View` that can serve as a placeholder, indicating 85 | to the user that you are fetching more data in the background 86 | (see below). This `View` is not cached by `EndlessAdapter`, so 87 | if you wish to reuse it, cache it yourself. 88 | 89 | If you use the constructor that takes a `Context` and resource ID along with 90 | the `ListAdapter`, you can skip `getPendingView()`, and `EndlessAdapter` 91 | will inflate the supplied layout resource as needed to create 92 | this placeholder. 93 | 94 | This placeholder row, when visible, will be disabled for list 95 | item clicks, as of v1.2 of this library. **This is a regression**, but 96 | presently an unavoidable one, as clicking on that row while the background 97 | data fetch was going on could raise an `IllegalStateException`. Hence, few 98 | (if any) developers were likely relying upon clicks on the placeholder row, 99 | so this regression hopefully will harm few people. 100 | 101 | ### The Loading 102 | 103 | Your `EndlessAdapter` subclass also needs to implement `cacheInBackground()`. 104 | This method will be called from a background thread, and it needs 105 | to download more data that will eventually be added to the `ListAdapter` 106 | you used in the constructor. While the demo application simply sleeps for 10 seconds, a real 107 | application might make a Web service call or otherwise load in 108 | more data. 109 | 110 | This method returns a `boolean`, which needs to be `true` if there 111 | is more data yet to be fetched, `false` otherwise. 112 | 113 | Since this method is called on a background thread, you do not 114 | need to fork your own thread. However, at the same time, do not 115 | try to update the UI directly. 116 | 117 | If you expected to be able to retrieve data, but failed (e.g., network 118 | error), that is fine. However, you should then return `false`, indicating 119 | that you have no more data. 120 | 121 | ### The Attaching 122 | 123 | Your `EndlessAdapter` subclass also needs to implement `appendCachedData()`, 124 | which should take the data cached by `cacheInBackground()` and append 125 | it to the `ListAdapter` you used in the constructor. While 126 | `cacheInBackground()` is called on a background thread, 127 | `appendCachedData()` is called on the main application thread. 128 | 129 | If you had a network error in `cacheInBackground()`, simply do nothing 130 | in `appendCachedData()`. So long as you returned `false` from 131 | `cacheInBackground()`, `EndlessAdapter` will remove the placeholder 132 | `View` and will operate as a normal fixed-length list. Or, 133 | override `onException()` to get control on the main application 134 | thread and be passed the `Exception` raised by `cacheInBackground()`, 135 | so you can do something to let the user know what went wrong. 136 | Have `onException()` return `true` if you want to retry loading data in the background, 137 | `false` otherwise. 138 | 139 | *If* you returned `false` from `onException()` *and* whatever circumstances 140 | caused the exception should now be resolved (e.g., you now have Internet 141 | access where before you did not), call `restartAppending()`, and the 142 | normal "endless" behavior will resume on the next scroll-to-the-bottom. 143 | 144 | ### The Threading 145 | 146 | By default, `EndlessAdapter` will use `AsyncTask` with the classic 147 | thread pool. If you would prefer your `EndlessAdapter` use the 148 | serialized pool on API Level 13+ projects, call `setSerialized(true)`. 149 | 150 | And if that paragraph was clear as mud, 151 | [here is a blog post covering the changes to `AsyncTask`](http://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html) 152 | that pertain to the serialized pool. 153 | 154 | If you wish to extend what is done in this `AsyncTask`, create your 155 | own subclass of the static `EndlessAdapter.AppendTask`, implement what you need 156 | (chaining to the superclass to inherit existing behavior), and 157 | override `buildTask()` in your `EndlessAdapter` subclass to create 158 | an instance of your own custom task class. 159 | 160 | If you would prefer that `EndlessAdapter` *not* run its own `AsyncTask`, 161 | then call `setRunInBackground(false)`. In this mode, your `cacheInBackground()` 162 | method will be called **on the main application thread**. It is up to you 163 | to arrange to do the work on your own background thread, then call `onDataReady()` 164 | when you want the adapter to update to reflect the newly added data. Note 165 | that `appendCachedData()` will not be used in this scenario. 166 | 167 | ### The Overriding 168 | 169 | In addition to the methods mentioned above that you could override in a 170 | custom subclass of `EndlessAdapter`, note that if you elect to override 171 | `isEnabled()`, you should either return `false` *or* the value of 172 | `EndlessAdapter`'s own `isEnabled()` via a chain to `super`. `EndlessAdapter` 173 | needs to mark the pending row placeholder as disabled. `EndlessAdapter` 174 | already then forwards `isEnabled()` for all other rows to your wrapped 175 | adapter, so it is simpler for you to just override `isEnabled()` there 176 | rather than by overriding `EndlessAdapter`'s implementation. 177 | 178 | Dependencies 179 | ------------ 180 | This project relies upon the [CWAC AdapterWrapper][adapter] project. 181 | A copy of compatible JARs can be found in the `libs/` directory 182 | of the project, though you are welcome to try newer ones, or 183 | ones that you have patched yourself. 184 | 185 | This project should work on API Level 4 and higher, except for any portions that 186 | may be noted otherwise in this document. Please report bugs if you find features 187 | that do not work on API Level 4 and are not noted as requiring a higher version. 188 | 189 | Version 190 | ------- 191 | This is version v1.2 of this module. It should be backwards-compatible with v1.0, 192 | with only added methods. However, there is the one behavior regression (placeholder 193 | row no longer clickable) as noted above. 194 | 195 | Demo 196 | ---- 197 | In the `demo/` sub-project you will find 198 | three sample activities that demonstrate the use of `EndlessAdapter`. 199 | Included in this is `EndlessAdapterFragmentDemo`, which shows how 200 | to use `EndlessAdapter` in a retained fragment. Note that while 201 | the `demo/` sample requires API Level 11 (as `EndlessAdapterFragmentDemo` 202 | uses native fragments and the native action bar), `EndlessAdapter` 203 | should work back to API Level 3. 204 | 205 | Note that when you build the JAR via `ant jar`, the sample 206 | activity is not included, nor any resources -- only the 207 | compiled classes for the actual library are put into the JAR. 208 | 209 | License 210 | ------- 211 | The code in this project is licensed under the Apache 212 | Software License 2.0, per the terms of the included LICENSE 213 | file. 214 | 215 | Getting Help 216 | ------------ 217 | Do not ask for help via Twitter. 218 | 219 | And, for those of you who skipped over that sentence: do not ask for help on Twitter. Anyone who thinks that 220 | developer support can be handled in 140-character chunks should not be attempting to use a CWAC component. 221 | 222 | Now, that being said, the rest of your help will be for bugs or questions. 223 | 224 | ### Bugs 225 | 226 | If you are experiencing some sort of problem using this component, where you are fairly certain the component 227 | itself is at fault, please create a project that can reproduce the problem. Then, post the source code to 228 | that project somewhere (e.g., a public GitHub repo). Next, file an 229 | [issue](https://github.com/commonsguy/cwac-endless/issues), pointing to your project and providing 230 | instructions on how to reproduce the problem. Note that if you can reproduce the error with the project's 231 | own `demo/` sub-project, just provide the steps to reproduce the problem. 232 | 233 | Do not file an issue if you cannot reproduce the problem, or with only partial source code that may or may 234 | not be related to the problem. 235 | 236 | ### Other Questions 237 | 238 | If you have questions regarding the use of this code, please post a question 239 | on [StackOverflow](http://stackoverflow.com/questions/ask) tagged with `commonsware` and `android`. Be sure to indicate 240 | what CWAC module you are having issues with, and be sure to include *relevant* source code 241 | and stack traces if you are encountering crashes. 242 | 243 | Release Notes 244 | ------------- 245 | * v1.2: made pending view disabled for list item clicks, made fewer mods in BG thread 246 | * v1.1: merged two pull requests, adding new constructors and `stopAppending()` 247 | * v1.0.0: made this the official 1.0 release 248 | * v0.10.0: added support for `setRunInBackground()` (patch courtesy of [brk3](https://github.com/brk3)), cleaned up demos a bit 249 | * v0.9.1: made `AppendTask` constructor `protected` 250 | * v0.9.0: added `restartAppending()` and `buildTask()`, refactored `AppendTask`, added new sample activity 251 | * v0.8.0: added `setSerialized()` and `isSerialized()` 252 | * v0.7.0: `cacheInBackground()` can now throw checked exceptions, new `getContext()` method available for subclasses 253 | * v0.6.1: merged bug fix from rgladwell/cwac-endless; added @Override annotations 254 | * v0.6.0: added pending `View` support via constructor 255 | * v0.5.0: added `onException()` 256 | * v0.4.0: eliminated need for `rebindPendingView()`, documented the no-data scenario 257 | * v0.3.1: fixed bug in manifest 258 | * v0.3.0: converted to Android library project, added call to `notifyDataSetChanged()` 259 | 260 | Who Made This? 261 | -------------- 262 | ![CommonsWare](http://commonsware.com/images/logo.png) 263 | 264 | [adapter]: http://github.com/commonsguy/cwac-adapter/tree/master 265 | -------------------------------------------------------------------------------- /src/com/commonsware/cwac/endless/EndlessAdapter.java: -------------------------------------------------------------------------------- 1 | /*** 2 | Copyright (c) 2008-2009 CommonsWare, LLC 3 | Portions (c) 2009 Google, Inc. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | not use this file except in compliance with the License. You may obtain 7 | a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package com.commonsware.cwac.endless; 17 | 18 | import java.util.concurrent.atomic.AtomicBoolean; 19 | 20 | import android.annotation.TargetApi; 21 | import android.content.Context; 22 | import android.os.AsyncTask; 23 | import android.os.Build; 24 | import android.util.Log; 25 | import android.view.LayoutInflater; 26 | import android.view.View; 27 | import android.view.ViewGroup; 28 | import android.widget.ListAdapter; 29 | import android.widget.ListView; 30 | 31 | import com.commonsware.cwac.adapter.AdapterWrapper; 32 | 33 | /** 34 | * Adapter that assists another adapter in appearing endless. For example, this 35 | * could be used for an adapter being filled by a set of Web service calls, 36 | * where each call returns a "page" of data. 37 | * 38 | * Subclasses need to be able to return, via getPendingView() a row that can 39 | * serve as both a placeholder while more data is being appended. 40 | * 41 | * The actual logic for loading new data should be done in appendInBackground(). 42 | * This method, as the name suggests, is run in a background thread. It should 43 | * return true if there might be more data, false otherwise. 44 | * 45 | * If your situation is such that you will not know if there is more data until 46 | * you do some work (e.g., make another Web service call), it is up to you to do 47 | * something useful with that row returned by getPendingView() to let the user 48 | * know you are out of data, plus return false from that final call to 49 | * appendInBackground(). 50 | */ 51 | abstract public class EndlessAdapter extends AdapterWrapper { 52 | abstract protected boolean cacheInBackground() throws Exception; 53 | 54 | abstract protected void appendCachedData(); 55 | 56 | private View pendingView = null; 57 | private AtomicBoolean keepOnAppending = new AtomicBoolean(true); 58 | private Context context; 59 | private int pendingResource = -1; 60 | private boolean isSerialized = false; 61 | private boolean runInBackground = true; 62 | 63 | private boolean reversedEndless; 64 | 65 | private ListView listView; 66 | private boolean initialize; 67 | private PageState pageState; 68 | 69 | public static class PageState { 70 | private int pageSize; 71 | private int sqliteTotalLimit; 72 | 73 | public PageState(int pageSize){ 74 | this.pageSize = pageSize; 75 | sqliteTotalLimit = this.pageSize; 76 | } 77 | 78 | public int getSqliteTotalLimit() { 79 | return sqliteTotalLimit; 80 | } 81 | 82 | public void setSqliteTotalLimit(int sqliteTotalLimit) { 83 | this.sqliteTotalLimit = sqliteTotalLimit; 84 | } 85 | 86 | public void resetSqliteTotalLimit() { 87 | sqliteTotalLimit = pageSize; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "PageState [sqliteTotalLimit=" + sqliteTotalLimit + "]"; 93 | } 94 | 95 | } 96 | 97 | /** 98 | * Constructor wrapping a supplied ListAdapter. This also binds the ListView 99 | * with the adapter. 100 | */ 101 | public EndlessAdapter(ListAdapter wrapped, boolean reversedEndless, ListView listView) { 102 | super(wrapped); 103 | this.context = listView.getContext(); 104 | this.listView = listView; 105 | this.reversedEndless = reversedEndless; 106 | this.listView.setAdapter(this); 107 | if (reversedEndless) { 108 | initialize = true; 109 | EndlessAdapter.this.listView.setSelection(EndlessAdapter.this.getCount() - 1); 110 | listView.post(new Runnable() { 111 | 112 | @Override 113 | public void run() { 114 | EndlessAdapter.this.listView.setSelection(EndlessAdapter.this.getCount() - 1); 115 | initialize = false; 116 | pendingView = null; 117 | } 118 | }); 119 | 120 | } 121 | } 122 | 123 | public EndlessAdapter(ListAdapter wrapped) { 124 | super(wrapped); 125 | } 126 | 127 | /** 128 | * Constructor wrapping a supplied ListAdapter and explicitly set if there 129 | * is more data that needs to be fetched or not. 130 | * 131 | * @param wrapped 132 | * @param keepOnAppending 133 | */ 134 | public EndlessAdapter(ListAdapter wrapped, boolean keepOnAppending) { 135 | super(wrapped); 136 | this.keepOnAppending.set(keepOnAppending); 137 | } 138 | 139 | /** 140 | * Constructor wrapping a supplied ListAdapter and providing a id for a 141 | * pending view. 142 | * 143 | * @param context 144 | * @param wrapped 145 | * @param pendingResource 146 | */ 147 | public EndlessAdapter(Context context, ListAdapter wrapped, int pendingResource) { 148 | super(wrapped); 149 | this.context = context; 150 | this.pendingResource = pendingResource; 151 | } 152 | 153 | /** 154 | * Constructor wrapping a supplied ListAdapter, providing a id for a pending 155 | * view and explicitly set if there is more data that needs to be fetched or 156 | * not. 157 | * 158 | * @param context 159 | * @param wrapped 160 | * @param pendingResource 161 | * @param keepOnAppending 162 | */ 163 | public EndlessAdapter(Context context, ListAdapter wrapped, int pendingResource, 164 | boolean keepOnAppending) { 165 | super(wrapped); 166 | this.context = context; 167 | this.pendingResource = pendingResource; 168 | this.keepOnAppending.set(keepOnAppending); 169 | } 170 | 171 | public ListView getListView() { 172 | return listView; 173 | } 174 | 175 | public boolean isReversedEndless() { 176 | return reversedEndless; 177 | } 178 | 179 | public boolean isSerialized() { 180 | return (isSerialized); 181 | } 182 | 183 | public void setSerialized(boolean isSerialized) { 184 | this.isSerialized = isSerialized; 185 | } 186 | 187 | public void stopAppending() { 188 | keepOnAppending.set(false); 189 | } 190 | 191 | public void restartAppending() { 192 | keepOnAppending.set(true); 193 | } 194 | 195 | /** 196 | * When set to false, cacheInBackground is called directly, rather than from 197 | * an AsyncTask. 198 | * 199 | * This is useful if for example you have code to populate the adapter that 200 | * already runs in a background thread, and simply don't need the built in 201 | * background functionality. 202 | * 203 | * When using this you must remember to call onDataReady() once you've 204 | * appended your data. 205 | * 206 | * Default value is true. 207 | * 208 | * @param runInBackground 209 | * 210 | * :see #cacheInBackground() :see #onDataReady() 211 | */ 212 | public void setRunInBackground(boolean runInBackground) { 213 | this.runInBackground = runInBackground; 214 | } 215 | 216 | /** 217 | * Use to manually notify the adapter that it's dataset has changed. Will 218 | * remove the pendingView and update the display. 219 | */ 220 | public void onDataReady() { 221 | pendingView = null; 222 | notifyDataSetChanged(); 223 | } 224 | 225 | /** 226 | * How many items are in the data set represented by this Adapter. 227 | */ 228 | @Override 229 | public int getCount() { 230 | if (keepOnAppending.get()) { 231 | return (super.getCount() + 1); // one more for 232 | // "pending" 233 | } 234 | 235 | return (super.getCount()); 236 | } 237 | 238 | /** 239 | * Masks ViewType so the AdapterView replaces the "Pending" row when new 240 | * data is loaded. 241 | */ 242 | @Override 243 | public int getItemViewType(int position) { 244 | if (reversedEndless && position == 0 && keepOnAppending.get() || !reversedEndless 245 | && position == getWrappedAdapter().getCount()) { 246 | return (IGNORE_ITEM_VIEW_TYPE); 247 | } 248 | if (reversedEndless && keepOnAppending.get()) { 249 | position--; 250 | } 251 | return (super.getItemViewType(position)); 252 | } 253 | 254 | /** 255 | * Masks ViewType so the AdapterView replaces the "Pending" row when new 256 | * data is loaded. 257 | * 258 | * @see #getItemViewType(int) 259 | */ 260 | @Override 261 | public int getViewTypeCount() { 262 | return (super.getViewTypeCount() + 1); 263 | } 264 | 265 | @Override 266 | public Object getItem(int position) { 267 | if (reversedEndless && position == 0 && keepOnAppending.get() || !reversedEndless 268 | && position >= super.getCount()) { 269 | return (null); 270 | } 271 | 272 | if (reversedEndless && keepOnAppending.get()) { 273 | position--; 274 | } 275 | return (super.getItem(position)); 276 | } 277 | 278 | @Override 279 | public boolean areAllItemsEnabled() { 280 | return (false); 281 | } 282 | 283 | @Override 284 | public boolean isEnabled(int position) { 285 | if (reversedEndless && position == 0 && keepOnAppending.get() || !reversedEndless 286 | && position >= super.getCount()) { 287 | return (false); 288 | } 289 | 290 | if (reversedEndless && keepOnAppending.get()) { 291 | position--; 292 | } 293 | return (super.isEnabled(position)); 294 | } 295 | 296 | /** 297 | * Get a View that displays the data at the specified position in the data 298 | * set. In this case, if we are at the end of the list and we are still in 299 | * append mode, we ask for a pending view and return it, plus kick off the 300 | * background task to append more data to the wrapped adapter. 301 | * 302 | * @param position 303 | * Position of the item whose data we want 304 | * @param convertView 305 | * View to recycle, if not null 306 | * @param parent 307 | * ViewGroup containing the returned View 308 | */ 309 | @Override 310 | public View getView(int position, View convertView, ViewGroup parent) { 311 | if ((reversedEndless && position == 0 || !reversedEndless && position == super.getCount()) 312 | && keepOnAppending.get()) { 313 | if (pendingView == null) { 314 | pendingView = getPendingView(parent); 315 | 316 | if (runInBackground) { 317 | executeAsyncTask(buildTask()); 318 | } else { 319 | try { 320 | keepOnAppending.set(cacheInBackground()); 321 | } catch (Exception e) { 322 | keepOnAppending.set(onException(pendingView, e)); 323 | } 324 | } 325 | } 326 | if (initialize) { 327 | pendingView.setVisibility(View.GONE); 328 | } else { 329 | pendingView.setVisibility(View.VISIBLE); 330 | } 331 | return (pendingView); 332 | } 333 | if (reversedEndless && keepOnAppending.get()) { 334 | position--; 335 | } 336 | return (super.getView(position, convertView, parent)); 337 | } 338 | 339 | /** 340 | * Called if cacheInBackground() raises a runtime exception, to allow the UI 341 | * to deal with the exception on the main application thread. 342 | * 343 | * @param pendingView 344 | * View representing the pending row 345 | * @param e 346 | * Exception that was raised by cacheInBackground() 347 | * @return true if should allow retrying appending new data, false otherwise 348 | */ 349 | protected boolean onException(View pendingView, Exception e) { 350 | Log.e("EndlessAdapter", "Exception in cacheInBackground()", e); 351 | 352 | return (false); 353 | } 354 | 355 | protected AppendTask buildTask() { 356 | return (new AppendTask(this)); 357 | } 358 | 359 | @TargetApi(11) 360 | private void executeAsyncTask(AsyncTask task, T... params) { 361 | if (!initialize) { 362 | if (!isSerialized && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) { 363 | task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); 364 | } else { 365 | task.execute(params); 366 | } 367 | } 368 | } 369 | 370 | /** 371 | * A background task that will be run when there is a need to append more 372 | * data. Mostly, this code delegates to the subclass, to append the data in 373 | * the background thread and rebind the pending view once that is done. 374 | */ 375 | protected static class AppendTask extends AsyncTask { 376 | EndlessAdapter adapter = null; 377 | boolean tempKeep; 378 | private ListView listView; 379 | private int preeCount; 380 | 381 | protected AppendTask(EndlessAdapter adapter) { 382 | this.adapter = adapter; 383 | this.listView = adapter.getListView(); 384 | } 385 | 386 | @Override 387 | protected void onPreExecute() { 388 | if (adapter.isReversedEndless()) { 389 | preeCount = adapter.getCount(); 390 | listView.setSelection(0); 391 | } 392 | } 393 | 394 | @Override 395 | protected Exception doInBackground(Void... params) { 396 | Exception result = null; 397 | 398 | try { 399 | tempKeep = adapter.cacheInBackground(); 400 | } catch (Exception e) { 401 | result = e; 402 | } 403 | 404 | return (result); 405 | } 406 | 407 | @Override 408 | protected void onPostExecute(Exception e) { 409 | adapter.keepOnAppending.set(tempKeep); 410 | 411 | if (e == null) { 412 | adapter.appendCachedData(); 413 | } else { 414 | adapter.keepOnAppending.set(adapter.onException(adapter.pendingView, e)); 415 | } 416 | 417 | adapter.onDataReady(); 418 | 419 | if (adapter.isReversedEndless()) { 420 | int selectedPos = adapter.getCount() - preeCount; 421 | listView.setSelection(selectedPos); 422 | 423 | } 424 | } 425 | } 426 | 427 | /** 428 | * Inflates pending view using the pendingResource ID passed into the 429 | * constructor 430 | * 431 | * @param parent 432 | * @return inflated pending view, or null if the context passed into the 433 | * pending view constructor was null. 434 | */ 435 | protected View getPendingView(ViewGroup parent) { 436 | if (context != null) { 437 | LayoutInflater inflater = (LayoutInflater) context 438 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 439 | return inflater.inflate(pendingResource, parent, false); 440 | } 441 | 442 | throw new RuntimeException( 443 | "You must either override getPendingView() or supply a pending View resource via the constructor"); 444 | } 445 | 446 | /** 447 | * Getter method for the Context being held by the adapter 448 | * 449 | * @return Context 450 | */ 451 | protected Context getContext() { 452 | return (context); 453 | } 454 | 455 | protected PageState getPageState() { 456 | return pageState; 457 | } 458 | 459 | public void setPageState(PageState pageState) { 460 | this.pageState = pageState; 461 | } 462 | } 463 | --------------------------------------------------------------------------------