Infinite List tutorial on Phone

If you reach this page, you should know what’s infinite scroll. We just jump right to HOW we can do it in Android-Binding.

You can get the tutorial source code from the project home page.

To begin with, please read another blog post I have, which is about multiple lists in a list view. We’ll start from the following Layout XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:binding="http://www.gueei.com/android-binding/"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <ListView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        binding:adapter="STITCH(
        	ADAPTER({source=LocationList, template=@layout/list_item}),
        	SECTION(., @layout/load_more_item)
        )"
        />
</LinearLayout>

This is very similar to what we have in the Multiple List in a list view, first, we use a STITCH to put two lists together in one list view:

  1. The Actual List (LocationList), loaded with ADAPTER();
  2. A SECTION() which is a single item list, I put “.” as the view model meaning the master view model itself

list_item.xml isn’t too interesting to put here, but here is the load_more_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:binding="http://www.gueei.com/android-binding/"
    android:id="@android:id/text1"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:gravity="center_vertical"
    android:paddingLeft="6dip"
    android:minHeight="?android:attr/listPreferredItemHeight"
    binding:text="IF(Loading, 'loading', 'Load More')"
    binding:onClick="LoadMoreItems"
/>

Which is very similar to simple_list_item, except that I expect someone can click on it and to load more items to the list.

There are two versions to the infinite list I am going to demonstrate. First one, which is simpler, is when the user reach the end of list, he can click the last item (which is the last xml i shown) and the list will continue to populate.

The other version is whenever the user reached the last item, the list will load automatically. Such behavior is differentiated in how we implemented the View Model, and here we are:

Version 1 (Click to load)

        public ArrayListObservable<String> LocationList =
			new ArrayListObservable<String>(String.class);

	public IntegerObservable Loaded =
			new IntegerObservable(0);

	public DependentObservable<Boolean> HasMore =
			new DependentObservable<Boolean>(Boolean.class){
				@Override
				public Boolean calculateValue(Object... args) throws Exception {
					return Loaded.get() < DATA.length;
				}
	};

	public BooleanObservable Loading =
			new BooleanObservable(false);

	public Command LoadMoreItems = new Command(){
		@Override
		public void Invoke(View view, Object... args) {
			if (!Loading.get()) loadMore();
		}
	};

        private void loadMore(){
		if (Loading.get()) return;
		Loading.set(true);

		ArrayList<String> add = new ArrayList<String>();
		int loaded = Loaded.get();
		for(int i=0; i<30; i++){
			if (DATA.length <= loaded) break;
			add.add(DATA[loaded]);
			loaded++;
		}

		Loaded.set(loaded);

		// Batch adding will results less notification to view => better performance
		LocationList.addAll(add);

		Loading.set(false);
	}

These are the public properties in the View Model, and the method to load items to the list. The loading should be done asynchronously, but to simplify the code, we just let it look like this.

This should be very simple, we click on the last item of the list (which should be showing “load more”) and the list is populated, with further 30 items.

Version 2 (Auto load when reaching or closing to last item)

We just need to modify a little bit on our View Model:

        public ArrayListObservable<String> LocationList =
			new ArrayListObservable<String>(String.class){
				@Override
				public void onLoad(int position) {
					super.onLoad(position);
					if (position>=Loaded.get()-1){
						loadMore();
					}
				}
	};

Looks simple enough. For all IObservableCollection, we have an event called “onLoad”, which is called when an item of that position is going to be displayed on screen (at least once when that row is going to be display, but not guaranteed to be only once). We check whether that item is the last (or close to last) item, if so, we call loadMore() to populate our list.

Final touches

The number of items to load, can be determined by screen size. For example, while 30 items is enough for a few pages in the phone, it might not even to fill up one screen for a tablet. Problem is, our View Model is not aware of how large of the screen (you can, but don’t do it!). Introducing a new Attribute for all views, to help resolving this problem!

Consider we have this layout for our tablet:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:binding="http://www.gueei.com/android-binding/"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        binding:text="FORMAT('Currently Loaded: %s', Loaded)"
        />
    <GridView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:numColumns="5"
        android:horizontalSpacing="10dip"
        android:verticalSpacing="10dip"
        android:stretchMode="columnWidth"
        android:gravity="center"
        binding:assign="{prop=ItemsPerLoad, value=70}"
        binding:adapter="STITCH(
        	ADAPTER({source=LocationList, template=@layout/list_item}),
        	SECTION(., @layout/load_more_item)
        )"
        />
</LinearLayout>

Instead of using ListView, we have more screen real estate to hold more, so a GridView could be much better. Result should looks like following picture:

The Grid View List in Tablet

Everything is almost the same as ListView version, but we put a new attribute to it: assign

“assign” is a new attribute introduced to Android-Binding, it serve to set some value back to the view model, from the layout xml. In the above example, we are setting the “ItemsPerLoad” property with a value of “70”, and we need to change the ViewModel a bit:

    public IntegerObservable ItemsPerLoad =
			new IntegerObservable(30);
    private void loadMore(){
		if (Loading.get()) return;
		Loading.set(true);

		ArrayList<String> add = new ArrayList<String>();
		int loaded = Loaded.get();
		for(int i=0; i<ItemsPerLoad.get(); i++){
			if (DATA.length <= loaded) break;
			add.add(DATA[loaded]);
			loaded++;
		}

		Loaded.set(loaded);

		// Batch adding will results less notification to view => better performance
		LocationList.addAll(add);

		Loading.set(false);
	}

By adding ItemsPerLoad and modify the loadMore() method, each time it is loading 70 items instead of 30.

Final words

In developing Android-Binding, we make a lots of effort in working with lists and templates. In the heart of MVVM, we should make View Model as independent to presentation profile as possible. We have demonstrated how we can make infinite list, without the need to use listeners.

The Android resource loading mechanism is very powerful, it allows versions layout or resources to be loaded according to the actual device configuration. In this tutorial, we looked at how we can take advantage of such mechanic and change the behavior of our program, base on screen sizes.

I have also included an async version (and delay each load for 5 seconds) to simulate more real world situation, in the source code.

About these ads