Instead of a pure MVC approach, Android provides a simple Adapter API that lets you bridge views with their underlying data source(s). There are several good examples on the web (search for Android Adapter) on how to use the built-in adapters that come with the android SDK. These links below provide two examples non-trivial examples from the Android documentations that you should check out if this is your first time using adapters:
- http://developer.android.com/resources/tutorials/views/hello-listview.html - shows a simple example on how to use the ArrayAdapter when your data source (as you would guess) is the form of a array (Java type or XML array resource).
- http://developer.android.com/resources/tutorials/views/hello-gridview.html - shows how to create grid view along with an Adapter to provide data for the the view.
Create Your Own Adapters
In non-trivial Android development, chances are you have a complex data graph to maintain states. Rather then trying to fit your data structure to work with the out-of-the-box adapters, it is relatively easy to just create your own. As a measure of practice, you should create a custom Adapter implementation for your views (specially ListView instances). Why? Your own Adapter class will be more flexible and you will add just enough functionality needed and avoid carrying around bloated code.
A Simple Implementation
The following is derived from a simple implementation which prompted this writing. I have a simple class Application which I want to bind to a ListView. Since we are going to use an Adapter we will break down the components as follow: list_layout.xml, item_layout, a data transfer object, and the activity where everything is displayed.
Main Layout – list_layout.xml
This is used for the Activity layout. It contains the ListView instance that will be bound to the adapter.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@+id/my_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"
android:textFilterEnabled="false"
android:stackFromBottom="false"/>
</LinearLayout>
Row Layout – item_layout.xml
This layout is used by the Adapter instance to generate the view used for each item (row) in the ListView. Use this layout to customize how you want each row in the bounded list view to appear. In this example, we are going to have a simple TextView instance in each row.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12sp">
<TextView android:id="@+id/data_item"
android:textSize="18sp"
android:textStyle="bold"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Data Transfer Object
This object is part of the internal object graph that is used to maintain state. It is used by the Adapter as the data source for information bound to the view.
public class MyData{
private String item;
public String getItem(){return name;}
public void setItem(String n){name = n;}
}
The Activity (Abbreviated)
The following shows an abbreviated version of a definition for an Activity class that uses the defined ListView (see above). Although it looks intimidating, this Activity class contains all the basics of a normal Activity class. However, part of the definition an Adapter class used to bind data to the ListView (see class MyListAdapter).
public class MyListActivity extends Activity{
ListView listView;
List<MyData> dataSource;
MyListAdapter adapter;
...
@Override
public void onCreate(Bundle savedInstanceState) {
this.setContentView(R.layout.list_layout);
listView = (ListView) findViewById(R.id.my_list);
dataSource = new ArrayList<MyData>();
adapter = new MyListAdapter(this, R.layout.item_layout, dataSource);
listView.setAdapter(adapter);
...
}
// private Adapter for my list
private static class MyListAdapter extends BaseAdapter {
private Activity parentActivity;
private int itemLayoutId;
private List<MyData> dataSource;
private LayoutInflater inflater;
// constructor for adapter
public MyListAdapter(Activity activity, int layoutId, List<MyData> ds){
parentActivity = activity;
itemLayoutId = layout;
dataSource = ds;
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public View getView(int pos, View convertView, ViewGroup parentView){
View view = null;
if(convertView == null){
view = inflater.inflate(itemLayoutId, parentView, false);
TextView textView = (TextView)view.findViewById(R.id.data_item);
String data = dataSource.get(pos).getItem();
textView.setText(data);
}else{
view = convertView;
}
return view;
}
}
}
If you have implemented an Activity before, the beginning should look familiar. In the onCreate() method, we bind the Activity to the main layout my_list. Next we pull out the ListView instance from that layout and set its adapter to an instance of the custom adapter class defined by MyListAdapter.
The class MyListAdapter is simple. It extends the BaseAdapter which is provided by the SDK as a starting point for custom adapters. You must override public method getView() as shown above. This method will be called by the View instance, bound to this adapter, to draw the portion of the View that displays the data at the specified position in the data set. In the snippet, we use and Inflater to reconstruct the View represented by R.layout.item_layout. Then bind the data to a TextView instance contained in the layout. The entire inflated View instance is returned by getView() to be used by the parent view.
Beware!
While getView() provides the core logic for your adapter to expose your custom data. I found that it’s also important to override the following methods shown below. If not you will spend hours trying to figure out why your list is not displaying properly.
private static class MyListAdapter extends BaseAdapter{
...
@Override
public int getCount() {
return (dataSource != null) ? dataSource.size() : 0;
}
@Override
public Object getItem(int idx) {
return (dataSource != null) ? dataSource.get(idx) : null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean hasStableIds(){
return true;
}
@Override
public int getItemViewType(int pos){
return IGNORE_ITEM_VIEW_TYPE;
}
@Override
public int getViewTypeCount(){
return 1;
}
}
While these methods look simple, they are used by the Adapter API to figure out how render the bounded views. You can find more information about them from the SDK Android doc page for Adapter.
I hope this was helpful to your Android development efforts! I have written enough. Until next time!