Android Core

Android UI: Full Sample App

This article is part of our Academy Course titled Android UI Design – Basics.

In this course, you will get a look at the fundamentals of Android UI design. You will understand user input, views and layouts, as well as adapters and fragments. Check it out here!

1. Introduction

In this last article about Android UI, we will build an Android app that uses almost all the concepts we talked about in the previous articles. We talked about the most important aspects we need to consider when developing an Android app. We saw how to create a UI structure using layout managers and how we can place widgets; we described some best practices, we should use, while developing an app. Well, the app we will build will be based on the topics covered previously, so have another look at them to refresh your memory.

As an example, we will build a To Do app: this is a simple app where we can add todo items and manage them. We will cover how to create the UI layout structure, how to add widgets so that we can show text messages to the user and how to accept user input. An important aspect we will consider is how to build an app that can be used on several devices with different screen size and resolutions.

2. App structure

Before digging into the code details, the first thing we should consider when building an app is making some sketches that help us to understand the app navigation and user interaction. There are several tools we can use, some of them are free. Moreover, these sketches help us to have an idea how our app will look like and we could show them to our customers so that they can realize if the app we want to build respects their needs.

Coming back to our To do app, we can image we have these requirements we have to satisfy:

  • There should be a list of items (to do items).
  • A user can add an item to the existing ones.
  • Items should have a priority color.
  • The app should run on smart phone and tablets.

In a real app, the requirements will be much more complex of course, but this is just a stepping stone. We can imagine a simple navigation like this one:

Figure 1
Figure 1

Figure 2
Figure 2

This is a very simple navigation: at the start up, the app shows the current items list and when the user clicks on “Add Item” on the action bar. the app will show the add item screen. To keep things simple and get focused on the UI aspects we can suppose that the app will not save the items. It could be an interesting exercise for the reader to extend the app so that it saves the items.

Now that we roughly know what the navigation will be and how the app user interface will look, we can start creating our app using our IDE. In this case we will use Eclipse + ADT. We create a new Android project that we can call Todo. We will not cover how to create an Android project using Eclipse so we assume you are already familiar with this IDE. Check out our “Android Hello World” example if you are not familiar with the process.

2.1. Item List with ListView and Object Model

Now we have our project structure, we can focus our mind on designing the model that stands behind the app. In this case the model is very simple, it is just a class that holds the information about a new todo item:

public class Item implements Serializable {
	private String name;	
	private String descr;
	private Date date;
	private String note;
	private TagEnum tag;
            // Set and get methods
}

This will be the basic class that we will handle in our app. Looking at the Android project, we have just created, we can notice that under the layout directory there is a default layout called activity_main.xml. This is the default layout created by the tool.

By now we can suppose we have just a list of items in this layout: this layout will be used just for smart phone and we will consider later when the app runs on a tablet. The list item layout is very simple, it is just built by a standard ListView widget:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/listItmes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

If you notice, we instructed Android to use just the space necessary to hold the items in the list. We know that to use a ListView we have to implement an adapter. We could use a standard adapter provided by Android, but in this case these standard adapters are not enough, we want to implement a custom adapter because we would like to show some information for each row in the list. We would like that a row in the list looks like:

Figure 3
Figure 3

As you can notice, each row has an image on the left side that represents the todo priority and some information. By now we do not consider applying any style to our rows. To have a row in our ListView like that, we have to create a row layout that we will use in our custom adapter. So we can create a new file called item_layout.xml under layout directory. This file looks like this:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:id="@+id/tagView"
        android:layout_width="30dp"
        android:layout_height="20dp"
        android:background="@color/red" />

    <TextView
        android:id="@+id/nameView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_toRightOf="@id/tagView" />

    <TextView
        android:id="@+id/descrView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/nameView"
        android:layout_toRightOf="@id/tagView" />

    <TextView
        android:id="@+id/dateView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true" />

</RelativeLayout>

In this layout, we use a RelativeLayout manager to place easily widgets where we want. As you can see in this layout manager, views are placed according to other view positions. For example we want our todo name to be placed just after the image, so we use the attribute:

 android:layout_toRightOf="@id/tagView"

Moreover, we can place views respect to the parent, for example we want that the date information will be placed on the right side of the row and at the bottom:

android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"

Now that we have the layout, we have to build the adapter. We will extend an ArrayAdapter and override some methods so we can handle our model data and the new layout. We call this adapter ToDoItemAdaper, so we have:

public class ToDoItemAdapter extends ArrayAdapter<Item> {
	private Context ctx;
	private List<Item> itemList; 
	public ToDoItemAdapter(Context context, List<Item> itemList) {
		super(context, R.layout.item_layout);
		this.ctx = context;
		this.itemList = itemList;
	}
}

The constructor receives the Context and the itemList as parameters, the last one param holds the todo item list. You can notice that when we call the super method, we pass out custom layout called R.layout.item_layout. Now we have to override one of the most important method called getView, used to create the View and render the row layout:

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View v = convertView;
		ItemHolder h = null;
		if (v == null) {
			// Inflate row layout
			LayoutInflater inf = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			v = inf.inflate(R.layout.item_layout, parent, false);
			// Look for Views in the layout
			ImageView iv = (ImageView) v.findViewById(R.id.tagView);
			TextView nameTv = (TextView) v.findViewById(R.id.nameView);
			TextView descrView = (TextView) v.findViewById(R.id.descrView);
			TextView dateView = (TextView) v.findViewById(R.id.dateView);
			h = new ItemHolder();
			h.tagView = iv;
			h.nameView = nameTv;
			h.descrView = descrView;
			h.dateView = dateView;
			v.setTag(h);
		}
		else		
		   h = (ItemHolder) v.getTag();

		h.nameView.setText(itemList.get(position).getName());		
		h.descrView.setText(itemList.get(position).getDescr());
		h.tagView.setBackgroundResource(itemList.get(position).getTag().getTagColor());
		h.dateView.setText(sdf.format(itemList.get(position).getDate()));
		
		return v;
	}

In this method, we check at the beginning if the View we receive as parameter is null. In this case, we have to inflate our layout. If you notice, we used the ViewHolder pattern to make the ListView scrolling smoother. We have created a small inner class called ItemHolder that holds the references to the View inside our custom layout:

// ViewHolder pattern
static class ItemHolder {
	ImageView tagView;
	TextView nameView;
	TextView descrView;
	TextView dateView;		
}

One thing you should notice is how we handled the background color of the ImageView. We used setBackgroundResource to set the imageview background. This method accepts an int representing the resource id we want to use as background:

h.tagView.setBackgroundResource(itemList.get(position).getTag().getTagColor());

Looking at our model class we can notice that the getTag() method returns an instance of TagEnum class. This is an enumeration defined in this way:

public enum TagEnum {
	BLACK(R.color.black,"Black"), RED(R.color.red, "Red"), 
	GREEN(R.color.green, "Green"), BLUE(R.color.blue, "Blue"),YELLOW(R.color.yellow,"Yellow");
	private int code;
	private String name;
	private TagEnum(int code, String name) {
		this.code = code;
		this.name = name;
	}
	public int getTagColor() {
		return this.code;
	}
}

In the enumeration we define the different colors we want to support and as first parameter we pass a resource id. If you remember in a previous article we talked about how to define resource color in XML format. We know, already, we have to create a XML file under res/values that we can call colors.xml:

<resources>

    <color name="red" >#FF0000
    </color>

    <color name="green" >#00FF00
    </color>

    <color name="blue" >#0000FF
    </color>

    <color name="black" >#000000
    </color>

    <color name="yellow" >#FFAA00
    </color>

</resources>

In the enumeration color definition we referenced this color using R.color.color_name, so that when we use getTagColor method in the custom adapter getView, we receive the resource id that will be used by the image background. An important aspect to understand is that we did not hard-code the colors in the constructor: for example, we could use directly the color hex code like #FF0000 for red and so on.

Even if the result would be the same, it is not recommended to use hard-coded values in the source code. For example, if we want to change the red color to another one, we will have to find the hex color in the source code and change it, but if we had used resources to define colors, we would go directly to the file holding the color definitions and change the color we like.

Notice that in the enumeration, we used a bad practice: we wrote directly the color name. We used it purposely to show to you something you should not do. In this case, if we want to support multi-language app, we have to change the way we initialize the enumeration using name written in a string resource file.

2.2. Multi-device support and layout considerations

Remember that one of our requirements is that we have to build an app that supports both smart phones and tablets. Thinking about tablet screen dimensions, we realize that the screen is too big to hold just a list of items, so we could consider splitting the screen in two areas: one that holds the list and another one that we can use to show item details or even to show the user interface to add a new item. This is true if we use tablets, but if we have a smart phone the screen dimensions are not big enough to be divided in two areas.

At the same time, we do not want to develop two different code branches: one for smart phone and one for tablet. We would rewrite the same code changing just some details and dimensions. Android helps us to solve this problem: we talked about Fragment in a previous article. So we could create a fragment that handles the user interface to add a new item to the list. A fragment encapsulates a set of components and activity behaviors so that we can reuse this piece of code in different activities. The figures below depict the situation we have to handle:

Figure 4
Figure 4

Figure 5
Figure 5

When the app runs in a smart phone we have to handle two activities one for the list item and another that handles the user input, while in a tablet we can have only one activity.

We can suppose that the screen size is at least 600dp so we want to split the screen in a different areas and we define a new layout under res/layout-sw600dp:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/listItmes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    <FrameLayout
        android:id="@+id/frm1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

</LinearLayout>

where the FrameLayout will be “filled” dynamically depending on the navigation flow.

Of course, we can customize the layout in more details, according to the different screen sizes. In this case, we can simply create different layouts in different directories under res.

2.3. Add item user interface layout

If we run the app we have as a result an empty list with no items. We have to create a new user interface. So keeping in mind the considerations we made before, we create a Fragment that handles the add item functionality, calling it as NewItemFragment. A fragment has a complex life cycle but for this purpose we can override just onCreateView method. This method is responsible for creating the user interface. As always, we have to create the layout first. In our IDE under res/layout we create another xml file that we call add_item_layout.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/txtTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="@string/addItem" />

    <TextView
        android:id="@+id/itemName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/txtTitle"
        android:layout_marginStart="10dp"
        android:text="@string/addItemName" />

    <EditText
        android:id="@+id/edtName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemName"
        android:hint="@string/addItemNameHint" />

    <TextView
        android:id="@+id/itemDescr"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/edtName"
        android:layout_marginTop="10dp"
        android:text="@string/addItemDescr" />

    <EditText
        android:id="@+id/edtDescr"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemDescr"
        android:hint="@string/addItemDescrHint" />

    <TextView
        android:id="@+id/itemNote"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/edtDescr"
        android:layout_marginTop="10dp"
        android:text="@string/addItemNote" />

    <EditText
        android:id="@+id/edtNote"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemNote"
        android:hint="@string/addItemNoteHint" />

    <TextView
        android:id="@+id/itemDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/edtNote"
        android:layout_marginTop="10dp"
        android:text="@string/addItemDate" />

    <TextView
        android:id="@+id/inDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemDate"
        android:layout_marginTop="10dp" />

    <TextView
        android:id="@+id/itemTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/inDate"
        android:layout_marginTop="10dp"
        android:text="@string/addItemTime" />

    <TextView
        android:id="@+id/inTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemTime"
        android:layout_marginTop="10dp" />

    <TextView
        android:id="@+id/itemTag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/inTime"
        android:layout_marginTop="10dp"
        android:text="@string/addItemTag" />

    <Spinner
        android:id="@+id/tagSpinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemTag" />

    <!-- ADD button -->

    <Button
        android:id="@+id/addBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="@string/addButton" />

</RelativeLayout>

It is a very simple layout made by TextView and EditText: the first one is used to show text messages on the user interface and the other one is used to accept user inputs. There are two components that are important in this layout: the Spinner and the Date/Time picker.

A spinner is a UI component that shows only one item at time and lets the user to select one item among them. We use this component to show different tag colors/priorities. It suits perfectly our purpose, in fact we want the user to select one color among a color list.

2.4. Tag Color/Priority Spinner

To work correctly, a Spinner needs an array adapter behind it. Android provides a list of adapters we can use, but we want to customize them because we want to show an image with a color. Then, we have to create a custom adapter in the same way we did for the ListView. First we create the row layout called spinner_tag_layout.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:id="@+id/tagSpinnerImage"
        android:layout_width="30dp"
        android:layout_height="20dp" />

    <TextView
        android:id="@+id/tagNameSpinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/tagSpinnerImage" />

</RelativeLayout>

and finally we create our adapter:

public class TagSpinnerAdapter extends ArrayAdapter<TagEnum> {

	private Context ctx;
	private List<TagEnum> tagList;
	
	public TagSpinnerAdapter(Context ctx, List<TagEnum> tagList) {
		super(ctx, R.layout.spinner_tag_layout);
		this.ctx = ctx;
		this.tagList = tagList;
	}

	
	@Override
	public View getDropDownView(int position, View convertView, ViewGroup parent) {
		return _getView(position, convertView, parent);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		return _getView(position, convertView, parent);
	}

	private View _getView(int position, View convertView, ViewGroup parent) {
		View v = convertView;
		if (v == null) {
			// Inflate spinner layout
			LayoutInflater inf = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			v = inf.inflate(R.layout.spinner_tag_layout, parent, false);
		}
		
		// We should use ViewHolder pattern
		ImageView iv = (ImageView) v.findViewById(R.id.tagSpinnerImage);
		TextView tv = (TextView) v.findViewById(R.id.tagNameSpinner);
		
		TagEnum t = tagList.get(position);
		iv.setBackgroundResource(t.getTagColor());
		tv.setText(t.getName());
		return v;
	}

}

Analyzing the code above, we notice that this adapter handles TagEnum objects and we override two methods getView and getDropDownView. We handle these methods in the same way. As you can notice we did almost the same things already done for the ListView.

In the fragment that holds this UI components, we have to find the reference to the Spinner and set the custom layout we defined above:

Spinner sp = (Spinner) v.findViewById(R.id.tagSpinner);
TagSpinnerAdapter tsa = new TagSpinnerAdapter(getActivity(), tagList);
sp.setAdapter(tsa);

When user selects one item in the Spinner, we have to find a way to know which one item was selected.

As you remember, we can use listener to be informed when some events occur in a component. In this case we are interested on item selection event, so we create a listener for it:

sp.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
	@Override
	public void onItemSelected(AdapterView<?> adptView, View view,
		int pos, long id) {
		currentTag = (TagEnum) adptView.getItemAtPosition(pos);
	}

	@Override
	public void onNothingSelected(AdapterView<?> arg0) {
		// TODO Auto-generated method stub
				
		}
	});

and we store the result in a class attribute.


 

2.5. Date and Time picker

When we add a new todo item to the list, we want the user to select the date and the time. Android provides two components called DatePickerDialog and TimePickerDialog. As the name implies, these are two dialogs that can be opened to select the date and the time.

We are using fragments so we have to create two inner class that present to the user the date and time pickers. In this case we extend for both pickers the DialogFragment class and override the onCreateDialog method. In this method, we simply initialize the Date picker and return it as result:

public static class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {
	@Override
	    public Dialog onCreateDialog(Bundle savedInstanceState) {
	        // Use the current date as the default date in the picker
	        final Calendar c = Calendar.getInstance();
	        c.setTime(selDate);
	        int year = c.get(Calendar.YEAR);
	        int month = c.get(Calendar.MONTH);
	        int day = c.get(Calendar.DAY_OF_MONTH);

	        // Create a new instance of DatePickerDialog and return it
	        return new DatePickerDialog(getActivity(), this, year, month, day);
	    }
		
	@Override
	public void onDateSet(DatePicker view, int year, int monthOfYear,
				int dayOfMonth) {
			
		Calendar c = Calendar.getInstance();
		c.set(year, monthOfYear, dayOfMonth, 9, 0);
		selDate = c.getTime();
		tvDate.setText(sdfDate.format(selDate));
	}
}

From the code above you can notice that we simply set the current date to the DatePickerDialog in the onCreateDialog. We implement the DatePickerDialog.OnDateSetListener to be notified when user selects the date. In the callback method of this interface we simply store the date selected by the user.

In the same way, we handle the TimePickerFragment, but in this case we extend TimePickerDialog:

public static class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener {
	@Override
	 public Dialog onCreateDialog(Bundle savedInstanceState) {
	        // Use the current date as the default date in the picker
	        final Calendar c = Calendar.getInstance();
	        c.setTime(selDate);
	        int hour = c.get(Calendar.HOUR_OF_DAY);
	        int minute = c.get(Calendar.MINUTE);
	    // Create a new instance of TimePickerDialog and return it
	     return new TimePickerDialog(getActivity(), this, hour, minute,
		                DateFormat.is24HourFormat(getActivity()));
            }

          @Override
          public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
		Calendar c = Calendar.getInstance();
		c.setTime(selDate);
		c.set(Calendar.HOUR_OF_DAY, hourOfDay);
		c.set(Calendar.MINUTE, minute);	
		selDate = c.getTime();
		// We set the hour
		tvTime.setText(sdfTime.format(selDate));
	}
}

These are two dialogs that they do not appear by themselves, but we have to check if the user clicks on a date/time textview to open these dialogs, so we have:

tvDate = (TextView) v.findViewById(R.id.inDate);
tvTime = (TextView) v.findViewById(R.id.inTime);

to get the reference to the TextView and then we have simply implement a listener:

tvDate.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
		DatePickerFragment dpf = new DatePickerFragment();
		dpf.show(getFragmentManager(), "datepicker");
	}
});

As result we have:

Figure 6
Figure 6

The last part in developing this fragment is dedicated to handling the event when the user clicks on the add button. When this event occurs, the fragment should create an instance of Item class holding the data inserted by user and send it back to the activity that holds the fragment. The best practice suggests to use an interface and callback methods.

We can define this interface in the fragment class:

public interface AddItemListener {
	public void onAddItem(Item item);
}

It is a very simple interface made by just only one method. Now we have:

Button addBtn = (Button) v.findViewById(R.id.addBtn);
addBtn.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
		// We retrieve data inserted
		Item i = new Item();
		i.setName(edtName.getText().toString());
		i.setDescr(edtDescr.getText().toString());
		i.setNote(edtNote.getText().toString());
		i.setTag(currentTag);
		i.setDate(selDate);
		// Safe cast
		( (AddItemListener) getActivity()).onAddItem(i);
	}
});

By using an interface we decouple the fragment from the activity that holds it and this will be very useful as we will see later.

Coming back to the note made regarding smart phones and tablets and remembering the picture shown above, we know that if the app runs in a smart phone we have to use two activities so we create another one called NewItemActivity that will hold the fragment described above. This activity is very simple because it acts as a fragment container.

public class NewItemActivity extends Activity implements NewItemFragment.AddItemListener{
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		NewItemFragment nif = new NewItemFragment();
		getFragmentManager().beginTransaction().add(android.R.id.content, nif).commit();
	}

	@Override
	public void onAddItem(Item item) {
		// We get the item and return to the main activity
		Log.d("TODO", "onAddItem");
		Intent i = new Intent();
		i.putExtra("item", item);
		setResult(RESULT_OK,i);
		finish();
	}
}

In the onCreate method, we create a new instance of our fragment class and the using FragmentManager we show the fragment on the screen.

Notice that this activity implements the interface defined in the fragment because it has to be notified when the user wants to add a new item, so it implements the onAddItem method. We will cover the functionality of this method later.

2.6. Main Activity

The main activity is the heart of the app, it is the activity that is called at the start up. It sets up the initial layout and shows the item list that we previously described:

itemListView = (ListView) findViewById(R.id.listItmes);
adpt = new ToDoItemAdapter(this, itemList);
itemListView.setAdapter(adpt);

Now we have to check if our app runs on a smart phone or a tablet so that we can change the activity behavior. We can do it by verifying if there is a FrameLayout component in the layout definition:

if (findViewById(R.id.frm1) != null) 
   isTablet = true;

2.7. Action bar

In this activity we add a action bar (a well-known Android pattern) with an action: add new item. To define it, we create (if not exists) a XML file under res/menu:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/action_add"
        android:icon="@android:drawable/ic_menu_add"
        android:orderInCategory="0"
        android:showAsAction="always"/>

</menu>

As a result we obtain:

Figure 7
Figure 7

As a user clicks on the plus sign, we should show the user interface for adding a new item. The first thing we have to handle is the event when user clicks on ‘plus’ icon so we have to override a method in the activity class:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
	int menuId = item.getItemId();
	switch (menuId) {
		case R.id.action_add: {
			if (!isTablet) {
				Intent i = new Intent(this, NewItemActivity.class);
				startActivityForResult(i, ADD_ITEM);
				break;
			}
			else {
				Log.d("TODO", "Tablet");
				FragmentTransaction ft = getFragmentManager().beginTransaction();
				NewItemFragment nif = new NewItemFragment();
				ft.replace(R.id.frm1, nif);
				ft.commit();
			}
		}
	}
}

This method is very important because we define how our activity should behave. If the button clicked by the user is our add button, we first verify if our app is running on a smart phone. In this case, we know we have to start another activity and we start it waiting for a result.

If we start an activity waiting for its result in the called activity we should return the result and we do it in the NewItemActivity in this way:

@Override
public void onAddItem(Item item) {
	// We get the item and return to the main activity
	Log.d("TODO", "onAddItem");
	Intent i = new Intent();
	i.putExtra("item", item);
	setResult(RESULT_OK,i);
	finish();
}

In this method we create an Intent that holds the result and pass it back to the calling activity (MainActivity) and we finish the activity. In the MainActivity, then, we have to be ready to handle the result:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	Log.d("TODO", "OnResult");
	if (requestCode == ADD_ITEM) {
		if (resultCode == RESULT_OK) {
			Log.d("TODO", "OK");
			Item i = (Item) data.getExtras().getSerializable("item");
			itemList.add(i);
			adpt.notifyDataSetChanged();
		}
	}
}

In this activity we extract the item object stored inside the Intent that we receive as result and add it to the item list. When we finish we call notifyDataSetChange method so that the ListView can be updated.

If our app is running on a tablet we simply “fill” the FrameLayout with the NewItemFragment described above. In this case we start a transaction and replace the FrameLayout with the fragment and at the end we commit the transaction. In this case, we do not need to start another activity because we use the FrameLayout to show the fragment that handles the user interface for adding a new item. So we have:

Figure 8
Figure 8

In this case, we have to simply implement the interface specified by the fragment as we did before:

@Override
public void onAddItem(Item item) {
	itemList.add(item);
	adpt.notifyDataSetChanged();
	NewItemFragment nif = (NewItemFragment) getFragmentManager().findFragmentById(R.id.frm1);
	getFragmentManager().beginTransaction().remove(nif).commit();
}

Notice in the last part that we removed the fragment at the end.

3. Styling the app

By now we have not considered the application’s style, but we know we can apply style to every UI component. We can modify all the UI colors and looks, implementing our style and branding the app. For example we want to apply a style to the listView row making them a little more appealing. We create (if it does not already exist), a file called style.xml under res/values and here we can define our style as we discussed in a previous article:

<style name="dateStyle">
        <item name="android:textAppearance">?android:textAppearanceSmall</item>
        <item name="android:textColor">@color/red</item>
    </style>

     <style name="descrStyle">
        <item name="android:textStyle">italic</item>
        <item name="android:textAppearance">?android:textAppearanceSmall</item>
    </style>
    
     <style name="nameStyle">
         <item name="android:textAppearance">?android:textAppearanceMedium</item>
         <item name="android:textStyle">bold</item>
     </style>

We defined three different styles and we want to apply them to our listview row:

<TextView
    android:id="@+id/nameView"
    style="@style/nameStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:layout_toRightOf="@id/tagView" />
<TextView
    android:id="@+id/descrView"
    style="@style/descrStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/nameView"
    android:layout_toRightOf="@id/tagView" />
<TextView
    android:id="@+id/dateView"
    style="@style/dateStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true" />

Running the app with this style we have:

Figure 9
Figure 9

4. Conclusion

In this Android UI course we covered some important UI aspects, concepts and best practices we should follow when developing an Android app. In this last article we saw how to apply all this knowledge to build a real app using all the topics covered in the previous articles. Of course, this simple app can be improved and you can have it as an exercise to try to add new functionalities. For example, we did not cover how to modify or delete the items in the list. In this case we could listen when a user clicks on an item and you could show a menu with several options, or we could use the action bar changing it according to the user actions. For example we could create a multiple selection list view and when user clicks on “trash” icon in the action bar we remove all the items selected.

There are different aspects we can improve in this app and it will be a good exercise if you want to go deeper in Android development aspects.

5. Download the Source Code

This was a lesson on how to create an Android app UI from scratch. You may download the source code here: AndroidApp.zip

Francesco Azzola

He's a senior software engineer with more than 15 yrs old experience in JEE architecture. He's SCEA certified (Sun Certified Enterprise Architect), SCWCD, SCJP. He is an android enthusiast and he has worked for long time in the mobile development field.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button