Getting Started with Android Development – Part 2

This is a blog post by iOS Tutorial Team member Ali Hafizji, an iOS and Android developer living in India. This tutorial is a continuation of Getting Started with Android Development, this site’s first foray into developing for a non-Apple platform! Check it out if you haven’t already. If you were following along last time, […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share

This is a blog post by iOS Tutorial Team member Ali Hafizji, an iOS and Android developer living in India.

The one quote Steve Jobs never said :-)

The one quote Steve Jobs never said :-)

The one quote Steve Jobs never said :-)

This tutorial is a continuation of Getting Started with Android Development, this site’s first foray into developing for a non-Apple platform! Check it out if you haven’t already.

If you were following along last time, you created a “Hello World” Android app, got it working on a device or emulator, and then built a simple master/detail app called QuoteReader that displays a list of quotes by our hero, Steve Jobs.

This tutorial takes you through some major improvements to the QuoteReader project, explaining some key Android concepts along the way. By the end, you’ll have learned the following:

  • How layouts work in Android.
  • The most common XML layout attributes and their uses.
  • How to modify QuoteReader to allow quote edits, as well as full-size images.
  • How to allow the user to rate each quote.
  • How to add and delete quotes.

Getting Started: Understanding Layouts

First I’m going to introduce a key concept for Android development that we did not cover in Part One.

Layouts are Android’s solution to the variety of screens that come on Android devices. These screens can have different pixel densities, dimensions, and aspect ratios.

Yes that’s right – iOS developers have it a lot easier with just the iPad and iPhone screen dimensions to worry about! :]

Typical Android devices even allow changing the screen orientation (portrait or landscape) while applications are running, so the layout infrastructure needs to be able to respond on the fly. Layouts are intended to give developers a way to express the physical relationship of views as they are drawn on the screen.

Layouts in Android are in the form of a tree, with a single root and a hierarchy of views. Look back at any of the XML layout files in the previous tutorial, and you’ll see that the XML tags create such a hierarchy, with a screen layout as the root of the tree. Each view in the tree is termed the parent of the views it contains and the child of the view that contains it.

When the layout is inflated by Android (a fancy way of saying converting from XML to actual in-memory view objects), it goes through two phases:

  • Measure phase: During this phase, Android traverses the layout tree from top to bottom, and each view declares the amount of vertical height and horizontal width it needs to display itself in the final display.
  • Layout phase: This phase is also a top-down traversal, but each view now positions each of its children in the size declared by it during the measure pass.

Once both phases are complete, the views are drawn on the screen.

Don’t worry if you’re still a bit confused how this works in practice – you will see soon! :]

Commonly-Used XML Attributes

Learning the exact use of all XML attributes can seem a bit daunting at first. It definitely takes some time to master the ins-and-outs of layout parameters. In this section, I’ll discuss some of the most widely-used layout parameters and their exact uses.

First of all, layout attributes that start with “layout_” have to do with a view’s LayoutParams, which are used by views to tell their parents how they should be… yep, laid out. :]

In other words, these attributes are used to set the size of the view during the measure phase of layout inflation.

The most commonly-used of these attributes are layout_width and layout_height, as they are used to define the size of each view. The most commonly-used values for these two attributes are:

  • match_parent and fill_parent: These set the dimension of the view to be as big as the parent. We used this in main.xml and quote_detail.xml already.
  • wrap_content: This sets the dimension to just enough to fit its contents. We used this in quote_detail.xml already.

The easiest way to understand this is visually. So here’s an example of a button with layout_width set as fill_parent:

And here’s an example of a button with layout_width set as wrap_content:

Next, have a look at the layout_gravity and gravity attributes. These two can be quite confusing initially. Just remember that the one beginning with “layout” has to do with the position of the view in the parent. Hence layout_gravity defines how a view will be positioned in the parent view, while gravity simply defines how the content of the view will be placed within itself.

The image below has layout_gravity set to center_horizontal:

The image below has gravity set to left:

Next is layout_weight. While you won’t be using this attribute in any of the views that you’ll create in this tutorial, you should know about it. This parameter is only used with LinearLayouts and it allows you to prioritize the importance of child views.

For example, if you have multiple views in a vertical LinearLayout, you can define how each of the views will take up extra space so that the entire screen is filled. This is really useful. It means you don’t have to define fixed sizes for each child – instead, the Android system handles everything for you.

The image below has three buttons placed in a vertical LinearLayout, but none have a layout weight assigned:

Whereas in the next image, the three buttons have been given weights of 1, 2 and 3, respectively:

Changing the Data Source

Before you use your new knowledge of layouts to improve the QuoteReader app, you need to make a few architectural changes to the QuoteReader.

First, you need to create a class that contains the data of each and every item in your list. In its present state, QuoteReader maintains three different ArrayLists for the thumbnails, quotes and full-size images. The problem with this approach is that there is no correlation between any of the items. That is, if I remove a thumbnail from the thumbnails ArrayList, it won’t remove the corresponding quote from the quotes ArrayList.

To correct this, first create a separate package to maintain your data source. Right-click on the src folder and select New->package. Name the package “com.tutorial.quotereader.datasource.” Drag the DataSource class into this package, and click OK when the popup appears.

Now create a new class for your item. Right-click on the new package and select New->Class. Name the class DataSourceItem, and click Finish. This class will be responsible for holding the thumbnail (bitmap), full-size image (bitmap), quote (string) and the rating (float value) of that quote.

Add the following private variables and getters/setters to the class:

private Bitmap mThumbnail;
private Bitmap mHdImage;
private String mQuote;
private float mRating;
 
public Bitmap getmThumbnail() {
    return mThumbnail;
}
 
public Bitmap getmHdImage() {
    return mHdImage;
}
 
public String getmQuote() {
    return mQuote;
}
 
public float getmRating() {
    return mRating;
}
 
public void setmRating(float mRating) {
    this.mRating = mRating;
}
 
public void setmHdImage(Bitmap mHdImage) {
    this.mHdImage = mHdImage;
}
 
public void setmQuote(String mQuote) {
    this.mQuote = mQuote;
}

You might get some errors for a missing import for the Bitmap class – to resolve this, go to Source\Organize Imports and it will automatically add the required import. If you ever get missing imports in the rest of this tutorial, you can use this same technique. Handy, eh?

Next, add two constructors. The code below should be quite self-explanatory:

public DataSourceItem() {
    mQuote = "New Quote is added";
}

public DataSourceItem(Bitmap thumbnail, Bitmap hdImage, String quote) {
    if(thumbnail == null || hdImage == null || quote == null)
        throw new IllegalArgumentException();
    mThumbnail = thumbnail;
    mHdImage = hdImage;
    mQuote = quote;

    mRating = 2.5f;
}

Now that you have your item ready, you need to modify the DataSource class so that instead of having three ArrayLists, you have just one, and each object in the array list is of type DataSourceItem.

Open DataSource.java and replace the contents with the following:

package com.tutorial.quotereader.datasource;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.drawable.BitmapDrawable;

import com.razeware.QuoteReader.R;

public class DataSource {

    private Context mContext;
    private ArrayList<DataSourceItem> mItemsData;

    public DataSource(Context context) {
        this.mContext = context;
        mItemsData = new ArrayList<DataSourceItem>();
        setupItemsData();
    }

    public ArrayList<DataSourceItem> getmItemsData() {
        return mItemsData;
    }

    private void setupItemsData() {
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_1)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_hd_1)).getBitmap(), mContext.getResources().getString(R.string.quote_1)));
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_2)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_hd_2)).getBitmap(), mContext.getResources().getString(R.string.quote_2)));
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_3)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_hd_3)).getBitmap(), mContext.getResources().getString(R.string.quote_3)));
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_4)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_hd_4)).getBitmap(), mContext.getResources().getString(R.string.quote_4)));
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_5)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_hd_5)).getBitmap(), mContext.getResources().getString(R.string.quote_5)));
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_6)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_hd_6)).getBitmap(), mContext.getResources().getString(R.string.quote_6)));
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_7)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_hd_7)).getBitmap(), mContext.getResources().getString(R.string.quote_7)));
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_8)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_hd_8)).getBitmap(), mContext.getResources().getString(R.string.quote_8)));
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_9)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_hd_9)).getBitmap(), mContext.getResources().getString(R.string.quote_9)));
        mItemsData.add(new DataSourceItem(((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.steve_10)).getBitmap(), 
                ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.apple_hd)).getBitmap(), mContext.getResources().getString(R.string.quote_10)));
    }

    public int getDataSourceLength() {
        return mItemsData.size();
    }

}

Now you are going to change how the data model is handled across different activities. In the earlier tutorial, you did this by creating new DataSource objects in each activity. This approach will not work any longer, because you’re going to modify the data and you want it to be same across all activities. To do this, you’ll use the singleton design pattern.

Add the following to DataSource.java:

private static DataSource mDataSource;
public static DataSource getDataSourceInstance(Context context) {
	if(mDataSource == null)
		mDataSource = new DataSource(context);
	return mDataSource;
}

Going forward, you will use only this static method to get a reference to the data source.

Contributors

Over 300 content creators. Join our team.