Hey! I'll be grateful to you, if you could let me know two things:1) What condition will make layout unavailable_item and empty_item to be inflated?2) Is it already implemented in your library? Or we have to provide the implementation?
Hi,
These two are going to be inflated if you call showUnavailable() and showEmpty() in derived class. It should be implemented as a part of data retrieval routine. “Empty” is whether dataset has no records to display and “unavailable” means something happen/error getting data.
It is a good point though. I need to add more support for data retrieval in the next version.
Please see previous tutorial before start reading this.
Simple list view with the same type of items is most common use case. However, sometimes it requires some sort of header or separator between items. DynamicViewAdapter can efficiently manage several types of item layouts.
For this tutorial: I’ve created new adapter inherited from DynamicViewAdapter. I’ve copied all the code from TestDynamicViewAdapter to this new class with name TestDynamicHeaderViewAdapter.
The new adapter is going to operate with two types of layouts: test_item.xml and header_item.xml. It will display every 10th item as header_item.
Now, there are several additional steps need to be performed to modify TestDynamicViewAdapter:
1. Create new view holder class;
2. Overide onDataLoad() with logic when every 10th item is a header item;
3. Overide onDataView with handling of multiple layouts;
Note: This example is not very practical since both layouts contains one TextView control and can be implemented using same layout and view holder. But I’m trying to keep the code as simple as possible so reader can focus on important aspects of implementation. In real life it could be two drastically different layouts with bunch of child views.
Create new view holder class
public class HeaderViewHolder extends ViewHolder { public final static int ResourceId = R.layout.header_item; private TextView headerText; public HeaderViewHolder(View convertView) { super(convertView, ResourceId); headerText = (TextView)view.findViewById(R.id.headerText); } public void render(AdapterItem item) { headerText.setText(String.format("Header: %s", item.value)); } }
TestDynamicHeaderViewAdapter: Overide onDataLoad with handling of multiple layouts
Despite the logic of 10th item we need to remember type of the created item somehow. The seconds parameter of addItem method was added for such purpose. So, we are going to store resource id there.
@Override protected void onDataLoad() { for (int i = 0; i < _model.getCount(); i++) { if (i % 10 == 0) { addItem(_model.getItem(i), HeaderViewHolder.ResourceId); } else { addItem(_model.getItem(i), ItemViewHolder.ResourceId); } } }
TestDynamicHeaderViewAdapter: Overide onDataView with handling of multiple layouts
Since we know that resource id is now storing in tag field we need to create appropriate viewholder (if needed) based on that. So, I moved code that decide which view holder create into separate method: createViewHolderByResourceId. Rest of the code remain the same as in TestDynamicViewAdapter:
@Override public View onDataView(int position, AdapterItem item, View convertView, ViewGroup parent) { int resourceId = (Integer) item.tag; convertView = tryInflateView(convertView, resourceId, parent); ViewHolder viewHolder = (ViewHolder) convertView.getTag(); if (viewHolder == null) { viewHolder = createViewHolderByResourceId(convertView, resourceId); } viewHolder.render(item); return convertView; } private ViewHolder createViewHolderByResourceId(View convertView, int resourceId) { ViewHolder result = null; switch (resourceId) { case ItemViewHolder.ResourceId: result = new ItemViewHolder(convertView); break; case HeaderViewHolder.ResourceId: result = new HeaderViewHolder(convertView); break; } return result; }
tryInflateView should take care of whether or not physically creating or re-creating view. This approach optimizes android resources usage and improves performance.
DynamicViewAdapter implements a basic routine of populating list with data, displaying progress, empty list or error state. It requests data from model page by page when last item (which indicates progress) about to become visible to the user.
DynamicViewAdapter extends BaseAdapter and can be used with ListView, GridView, Gallery and other “adapter consuming” controls. It is an abstract class and should be extended with the specific implementation. There are two reasons for that: 1) Data fetching process is model specific and should be implemented in inherited class; 2) XML Layout of list items should be also defined for the specific case.
Preparation
I’ve created test project with ListActivity and TestDynamicModel that simulates network request and fetch data by 20 items per request and up to 100 items. Also, I’ve created TestDynamicViewAdapter that extends DynamicViewAdapter:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); setListAdapter(new TestDynamicViewAdapter(this, new TestDynamicModel())); }
There are couple things im model class which require an explanation: (Full version of code is here)
public class TestDynamicModel { … private int _count = 0; private Handler _loadCompleted; public void loadMore() { _count += PageSize; (new BackgroundTask()).execute(); } … public String getItem(int position) { return String.format("Item %d", position); } private class BackgroundTask extends AsyncTask { @Override protected Integer doInBackground(String... params) { … Thread.sleep(5000); //sleep for 5 seconds // in order to simulate network latency … } @Override protected void onPostExecute(Integer result) { … _loadCompleted.sendEmptyMessage(0); … } } }
Adapter should call loadMore() every time it needs new page to load. Also, adapter has to provide loadCompleted handler to notify that data is ready. Model should be designed to perform all data operations in the separate thread.
TestDynamicViewAdapter
public class TestDynamicViewAdapter extends DynamicViewAdapter
In constructor: you have to define the layouts for three scenarios: progress, error/data unavailable and empty list. Also, you can re-use the layouts defined in the project (See res folder details).
If you are not going to use empty and error/unvailable items then you can put zeros in constructor parameters. Also, you have to implement handler for data completion event from the model. It is not necessary to do it in the same way it shown above but it should be defined somewhere.
This method should return true if there is more data to load. For instance it loads by 10 items, for list with 100 items it should return true until it riches (100th) last item in the list:
The following method is called when data item needs to be rendered. It is called from getView() (see more info getView()). The base class takes care of rendering progress and other control items and calls this function when actual data needs to be rendered.
Every View which get inflated from the XML resources will result in a memory object. Inflation operation will cost some resources as well. Android controls can recycle views which are no longer in visible area. Recycled view is passed as convertView parameter. Since, adapter can support different xml layouts as items of the same list we cannot reuse every given view. Use tryInflateView() to verify if view has expected layout. If it doesn’t then it will be re-created and xml layout will be re-inflated with id passed as second parameter of this function.
//Item inflating and rendering code. convertView = tryInflateView(convertView, R.layout.test_item, parent);
As a second step your implementation can use the so-called "ViewHolder" pattern. The findViewById() method is an expensive operation, therefore we should avoid doing this operation if not necessary. See more information about this pattern here in “Performance Optimization” section. DynamicViewAdapter supports "ViewHolder" pattern already. In order to use it we need create a class inherited from ViewHolder and override render() method. Here is how it should look like:
public class ItemViewHolder extends ViewHolder { public final static int ResourceId = R.layout.test_item; private TextView textView; public ItemViewHolder(View convertView) { super(convertView, ResourceId); textView = (TextView)view.findViewById(R.id.itemText); } public void render(AdapterItem item) { textView.setText(item.value.toString()); } }
In this article we didn’t explore empty and error states. I didn’t come up with a good example without complicating the test project. So it is up to you how to implement it. Use showUnavailable() and showEmpty() to display these to state. Also you can use showProgress() to append progress item to the list. Use the following functions to hide specific state tryEndProgress(),tryEndUnavailable() and tryEndEmpty().