Monthly Archives: November 2012

Easy measuring of custom Views with specific aspect ratio

Introduction

A lot of the custom Views I have made all have to respect some aspect ratio. In order to make the measuring of these Views easier (and more modular, to avoid that horrible copy-pasting of code), I have created a helper class that will help you measure your custom Views that respect an aspect ratio.

The solution

Behold: The ViewAspectRatioMeasurer! (Available for download in the GitHub repository: ViewAspectRatioMeasurer.java. You can also find the code in the bottom of this post)

How to use

Simply instantiate the ViewAspectRatioMeasurer when your custom View is constructed, and run the measure() method in your onMeasure() method:

public class MyView extends View {

	public MyView(Context context) {
		super(context);
	}
	
	// The aspect ratio to be respected by the measurer
	private static final double VIEW_ASPECT_RATIO = 2.5;
	
	private ViewAspectRatioMeasurer varm = new ViewAspectRatioMeasurer( VIEW_ASPECT_RATIO );

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		varm.measure(widthMeasureSpec, heightMeasureSpec);
		setMeasuredDimension( varm.getMeasuredWidth(), varm.getMeasuredHeight() );
	}
	
}

How it works

When measuring, there are four possible options in regards to whether the height and width are fixed or dynamic. Dynamic being using "wrap_content" as size, and fixed is "match_parent" or an explicit dimension (e.g. "100dp"). These four options are:

Both width and height fixed: Fixed (Aspect ratio isn’t respected)
Width dynamic, height fixed: Set width depending on height
Width fixed, height dynamic: Set height depending on width
Both width and height dynamic: Largest size possible

All of these options (except the first) respect the provided aspect ratio.

The code

For the lazy, I have included the source code below:

/**
 * This class is a helper to measure views that require a specific aspect ratio.<br />
 * <br />
 * The measurement calculation is differing depending on whether the height and width
 * are fixed (match_parent or a dimension) or not (wrap_content)
 * 
 * <pre>
 *                | Width fixed | Width dynamic |
 * ---------------+-------------+---------------|
 * Height fixed   |      1      |       2       |
 * ---------------+-------------+---------------|
 * Height dynamic |      3      |       4       |
 * </pre>
 * Everything is measured according to a specific aspect ratio.<br />
 * <br />
 * <ul>
 * <li>1: Both width and height fixed:   Fixed (Aspect ratio isn't respected)</li>
 * <li>2: Width dynamic, height fixed:   Set width depending on height</li>
 * <li>3: Width fixed, height dynamic:   Set height depending on width</li>
 * <li>4: Both width and height dynamic: Largest size possible</li>
 * </ul>
 * 
 * @author Jesper Borgstrup
 */
public class ViewAspectRatioMeasurer {
	
	private double aspectRatio;
	
	/**
	 * Create a ViewAspectRatioMeasurer instance.<br/>
	 * <br/>
	 * Note: Don't construct a new instance everytime your <tt>View.onMeasure()</tt> method
	 * is called.<br />
	 * Instead, create one instance when your <tt>View</tt> is constructed, and
	 * use this instance's <tt>measure()</tt> methods in the <tt>onMeasure()</tt> method.
	 * @param aspectRatio
	 */
	public ViewAspectRatioMeasurer(double aspectRatio) {
		this.aspectRatio = aspectRatio; 
	}
	
	/**
	 * Measure with the aspect ratio given at construction.<br />
	 * <br />
	 * After measuring, get the width and height with the {@link #getMeasuredWidth()}
	 * and {@link #getMeasuredHeight()} methods, respectively.
	 * @param widthMeasureSpec The width <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
	 * @param heightMeasureSpec The height <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
	 */
	public void measure(int widthMeasureSpec, int heightMeasureSpec) {
		measure(widthMeasureSpec, heightMeasureSpec, this.aspectRatio);
	}

	/**
	 * Measure with a specific aspect ratio<br />
	 * <br />
	 * After measuring, get the width and height with the {@link #getMeasuredWidth()}
	 * and {@link #getMeasuredHeight()} methods, respectively.
	 * @param widthMeasureSpec The width <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
	 * @param heightMeasureSpec The height <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
	 * @param aspectRatio The aspect ratio to calculate measurements in respect to 
	 */
	public void measure(int widthMeasureSpec, int heightMeasureSpec, double aspectRatio) {
		int widthMode = MeasureSpec.getMode( widthMeasureSpec );
		int widthSize = widthMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( widthMeasureSpec );
		int heightMode = MeasureSpec.getMode( heightMeasureSpec );
		int heightSize = heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( heightMeasureSpec );
		
		if ( heightMode == MeasureSpec.EXACTLY && widthMode == MeasureSpec.EXACTLY ) {
			/* 
			 * Possibility 1: Both width and height fixed
			 */
			measuredWidth = widthSize;
			measuredHeight = heightSize;
			
		} else if ( heightMode == MeasureSpec.EXACTLY ) {
			/*
			 * Possibility 2: Width dynamic, height fixed
			 */
			measuredWidth = (int) Math.min( widthSize, heightSize * aspectRatio );
			measuredHeight = (int) (measuredWidth / aspectRatio);
			
		} else if ( widthMode == MeasureSpec.EXACTLY ) {
			/*
			 * Possibility 3: Width fixed, height dynamic
			 */
			measuredHeight = (int) Math.min( heightSize, widthSize / aspectRatio );
			measuredWidth = (int) (measuredHeight * aspectRatio);
			
		} else {
			/* 
			 * Possibility 4: Both width and height dynamic
			 */
			if ( widthSize > heightSize * aspectRatio ) {
				measuredHeight = heightSize;
				measuredWidth = (int)( measuredHeight * aspectRatio );
			} else {
				measuredWidth = widthSize;
				measuredHeight = (int) (measuredWidth / aspectRatio);
			}
			
		}
	}
	
	private Integer measuredWidth = null;
	/**
	 * Get the width measured in the latest call to <tt>measure()</tt>.
	 */
	public int getMeasuredWidth() {
		if ( measuredWidth == null ) {
			throw new IllegalStateException( "You need to run measure() before trying to get measured dimensions" );
		}
		return measuredWidth;
	}

	private Integer measuredHeight = null;
	/**
	 * Get the height measured in the latest call to <tt>measure()</tt>.
	 */
	public int getMeasuredHeight() {
		if ( measuredHeight == null ) {
			throw new IllegalStateException( "You need to run measure() before trying to get measured dimensions" );
		}
		return measuredHeight;
	}
	
}

HSV Color Picker Dialog

Introduction

After being unable to find any really good color picker dialogs for Android, I decided to make my own, and this is how it turned out:

The HSVColorPickerDialog

I made the dialog a two-part color picker based on the HSV color model: First you select the hue and saturation parts in a circle, and then finally select the value in a gradient below as seen in the screenshots below. The dialog is available for download in the GitHub repository: HSVColorPickerDialog.java

How to use it

The HSVColorPickerDialog is based on the AlertDialog, and it is really easy to use:

public HSVColorPickerDialog(Context context, int initialColor, final OnColorSelectedListener listener);

public interface OnColorSelectedListener {
    /**
     * @param color The color code selected, or null if no color. No color is only
     * possible if {@link HSVColorPickerDialog#setNoColorButton(int) setNoColorButton()}
     * has been called on the dialog before showing it
     */
    public void colorSelected( Integer color );
}

You call the constructor with a Context, a color that is to be selected as default, and a OnColorSelectedListener that is invoked when the
user has selected a color and pressed OK:

Example

HSVColorPickerDialog cpd = new HSVColorPickerDialog( MainActivity.this, 0xFF4488CC, new OnColorSelectedListener() {
    @Override
    public void colorSelected(Integer color) {
        // Do something with the selected color
    }
});
cpd.setTitle( "Pick a color" );
cpd.show();

Notice how the callback’s color parameter is an Integer and not an int? That is because the dialog has the option of letting the user select no color, in which case the Integer is null. To allow the user to do this, simply call the following method on the dialog before showing it:

cpd.setNoColorButton( R.string.no_color );

This makes the dialog add a third button in the bottom like this:

Making queries easier with QueryBuilder

Introduction

Writing SQL queries (especially WHERE-clauses) can be tedious and redundant and it can be way too easy to make errors when manipulating the strings that comprise the clauses.

The answer

In order to simplify and streamline the query writing that I do up to, say, ten times an hour making content-intensive apps, I came up with the QueryBuilder. This class makes your queries more readable, reusable and less error-prone by exposing self-explanatory methods that take care of the string/object manipulation for you. Hell, it even has search multi-column free-text search capabilities!

Where can I download it?

It is available free of charge in the BuzzingAndroid GitHub repository right here: QueryBuilder.java

Now that you have it ready to use, let’s dive right in!

The interface

// Constructor
public QueryBuilder();

// Selection methods
public QueryBuilder whereId(int id);
public QueryBuilder whereId(long id);
public QueryBuilder whereColumnIsNull(String column);
public QueryBuilder whereColumnIsNotNull(String column);
public QueryBuilder whereColumnEquals(String column, Object value);
public QueryBuilder whereColumnNotEquals(String column, Object value);
public QueryBuilder whereColumnGreaterThan(String column, Object value);
public QueryBuilder whereColumnGreaterThanOrEqual(String column, Object value);
public QueryBuilder whereColumnLessThan(String column, Object value);
public QueryBuilder whereColumnLessThanOrEqual(String column, Object value);
public QueryBuilder whereColumnInSet(String column, Object[] set);
public QueryBuilder whereColumnInSet(String column, long[] set);
public QueryBuilder whereColumnInSet(String column, int[] set);
public QueryBuilder whereColumnNotInSet(String column, Object[] set);
public QueryBuilder whereColumnNotInSet(String column, long[] set);
public QueryBuilder whereColumnNotInSet(String column, int[] set);
public QueryBuilder addSelection(String extraSelection, Object... extraSelectionArgs);

// Search methods
public QueryBuilder setSearchColumns(String... columns);
public QueryBuilder setSearchQuery(String query);
public FilterQueryProvider createSearchFilterQueryProvider(final Context context, final Uri uri);

// Set projection
public QueryBuilder select(String... columns);

// Set sort order
public QueryBuilder orderBy(String sortOrder);

// Build selection string and argument array
public Pair<String, String[]> buildSelection();

// Query methods
public Cursor query(Context context, Uri uri);
public Cursor query(ContentProviderClient provider, Uri uri) throws RemoteException;
public void queryAsync(Context context, Uri uri, final AsyncQueryCallback callback);
public CursorLoader createCursorLoader(Context context, Uri uri);

// Update methods
public int update(Context context, ContentValues values, Uri uri);
public int update(ContentProviderClient provider, ContentValues values, Uri uri) throws RemoteException;
public ContentProviderOperation createUpdateOperation(ContentValues values, Uri uri);

// Delete methods
public int delete(Context context, Uri uri);
public int delete(ContentProviderClient provider, Uri uri) throws RemoteException;
public ContentProviderOperation createDeleteOperation(Uri uri);

Examples

So… how do you use this fine piece of code, you say? First, let’s create a sample SQL table, so we can get right to some examples:

CREATE TABLE users (
    _id INTEGER PRIMARY KEY AUTOINCREMENT, -- This column is named _id after BaseColumns._ID. The whereId() methods search this column
    firstname TEXT NOT NULL,
    lastname TEXT NOT NULL,
    email TEXT,
    age INTEGER NOT NULL
);

For the following examples, I assume that you have set up some kind of content provider for the table that supports query(), insert() and delete(), and
that this table can be reached through some Uri uri.

Basics

Let’s start by querying the id of all users with firstname ‘Jesper’ – easy:

Cursor c = new QueryBuilder()
    .select( "_id" )
    .whereColumnEquals( "firstname", "Jesper" )
    .query( context, uri );

In the same way, we can also update or delete users with firstname ‘Jesper':

// Update
ContentValues values = new ContentValues();
values.put( "lastname", "Borgstrup" );
new QueryBuilder()
    .whereColumnEquals( "firstname", "Jesper" )
    .update( context, values, uri );

// Delete
new QueryBuilder()
    .whereColumnEquals( "firstname", "Jesper" )
    .delete( context, uri );

Null checks

The two methods whereColumnIsNull() and whereColumnIsNotNull() are used to check if a value is null or not.
If we want to delete all users that don’t have an email address (read: email is null), it is as easy as:

new QueryBuilder()
	.whereColumnIsNull( "email" )
	.delete( context, uri );

Chaining selections

We can chain more selections together – to find all users with firstname ‘Jesper’ and lastname ‘Borgstrup’ whose email address is not null:

Cursor c = new QueryBuilder()
    .whereColumnEquals( "firstname", "Jesper" )
    .whereColumnEquals( "lastname", "Borgstrup" )
    .whereColumnIsNotNull( "email" )
    .query( context, uri );

Everytime you call a where*()-method or addSelection(), the selection is added to the selections
with the SQL AND operator. That is, all of the chained selections must be true for a given row to be selected.

Comparisons

There are four comparison methods for the four comparison operators, greater-than (>), greater-than-or-equal (>=), less-than (<) and less-than-or-equal (lt;=).

So, in order to select the first and last names of all users that is at least 18 years old, the following expressing should be used:

Cursor c = new QueryBuilder()
    .select( "firstname", "lastname" )
    .whereColumnGreaterThanOrEqual( "age", 18 )
    .query( context, uri );

Set operations

The QueryBuilder can also query for values in a set/not in a set. If we wanted to delete all users whose firstname is neither ‘Casper’, ‘Jesper’ nor ‘Jonathan’, here is how we would do it:

String[] acceptedFirstnames = { "Casper", "Jesper", "Jonathan" };
new QueryBuilder()
    .whereColumnNotInSet( "firstname", acceptedFirstnames )
    .delete( context, uri );

The whereColumnInSet() methods are the opposites of whereColumnNotInSet(). (Pretty self-explainatory, right=)

More querying

Besides doing “normal” queries, QueryBuilder is also capable of doing asynchronous queries through the AsyncQueryCallback interface in the following way:

new QueryBuilder()
    .select( "_id", "firstname", "lastname", "email", "age" )
    .queryAsync( context, uri, new AsyncQueryCallback() {
        @Override
        public void queryCompleted(Cursor c) {
            // Do something with the data
        }
    });

Here, the query is being carried out in a background thread and the resulting cursor is brought to us in the callback – in the same thread that called the queryAsync() method.
Needless to say, this is great for querying on the UI thread.

Speaking of querying on the UI thread, the Android support library has a class CursorLoader, that is made for this purpose exactly. Of course, QueryBuilder supports creating these:

new QueryBuilder()
    .select( "_id", "firstname", "lastname", "email", "age" )
    .createCursorLoader( context, uri );

Free-text search

I promised you in the beginning of this article that QueryBuilder had multi-column free-text search capabilities, and I’m not going to disappoint you.

With QueryBuilder, you set one or more columns to search through, gives it a search query which is broken down into search tokens, delimited by whitespace (as by the java.util.StringTokenizer class), and each of these tokens has to appear in at least one of the columns in order for a row to be selected.

Let’s try it out:

new QueryBuilder()
    .select( "_id", "firstname", "lastname", "email", "age" )
    .setSearchColumns( "firstname", "lastname" )
    .setSearchQuery( "sper strup" )
    .query( context, uri );

To explain, this query would match the user with firstname “Jesper” and lastname “Borgstrup”, but not firstname “Jesper” and lastname “Dover”, nor firstname “Jonathan” and lastname “skibstrup”.
However the strangely named user with firstname “Casperitiustruppolous” would be selected solely on his firstname (as it contains both “sper” and “strup”)

This free-text searching can also be exposed through a FilterQueryProvider that fits nicely into a CursorAdapter:

CursorAdapter adapter;
adapter.setFilterQueryProvider( new QueryBuilder()
                                    .select( "_id", "firstname", "lastname", "email", "age" )
                                    .setSearchColumns( "firstname", "lastname" )
                                    .setSearchQuery( "sper borg" )
                                    .createSearchFilterQueryProvider( context, uri ) );

The less-often used methods

Lastly, QueryBuilder also supports building ContentProviderOperations for use in a ContentProvider.applyBatch() batch operation through the following methods:

public ContentProviderOperation createUpdateOperation(ContentValues values, Uri uri);
public ContentProviderOperation createDeleteOperation(Uri uri);

[/java]

Also, querying, updating and deleting by the use of a ContentProviderClient instead of a Context is supported. These functions become especially handy when
dealing with content in SyncAdapters.

Conclusion

QueryBuilder makes selection of SQL data more readable and less error-prone by exposing descriptively-named methods that abstracts the string manipulation of multiple selections.

Also, it looks cooler. I hope you can use it, because I sure can

SyncAdapter change in Jelly Bean

Recently, when trying one of my apps that uses a SyncAdapter out on the new Jelly Bean emulator, I got the following exception:

java.lang.IllegalStateException: Already released
at android.content.ContentProviderClient.release(ContentProviderClient.java:242)
at android.content.AbstractThreadedSyncAdapter$SyncThread.run(AbstractThreadedSyncAdapter.java:263)

I apparently made the error of releasing the ContentProviderClient passed in onPerformSync() by calling provider.release();.at the end. While this would seem to me to be good practice, and Android didn’t complain up to Jelly Bean, this now throws an exception and causes havoc with random background crashes.

So to sum up: Don’t release() the ContentProviderClient when your SyncAdapter has finished

Tab swipe between fragments using ActionBarSherlock

Introduction

First post in my new Android blog, so I thought I should start out with something lightweight, yet pretty cool: Swiping between action bar tabs using ActionBarSherlock.

The Android Developer tools include a wizard to make this for the native action bar, but this requires your project to break backwards compatibility for devices that aren’t updated to API 14 (Ice Cream Sandwich).

This easily extendable snippet of code allows you to implement swiping between tabs on all devices in any project that uses ActionBarSherlock.

The custom activity to inherit from

Instead of letting your activity inherit from SherlockActivity or any of the likes, now let your activity inherit from the following TabSwipeActivity:

TabSwipeActivity.java (on GitHub):

import java.util.ArrayList;
import java.util.List;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.ActionBar.TabListener;
import com.actionbarsherlock.app.SherlockFragmentActivity;

public abstract class TabSwipeActivity extends SherlockFragmentActivity {

    private ViewPager mViewPager;
    private TabsAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
    	/*
    	 * Create the ViewPager and our custom adapter
    	 */
        mViewPager = new ViewPager(this);
        adapter = new TabsAdapter( this, mViewPager );
        mViewPager.setAdapter( adapter );
        mViewPager.setOnPageChangeListener( adapter );

        /*
         * We need to provide an ID for the ViewPager, otherwise we will get an exception like:
         *
         * java.lang.IllegalArgumentException: No view found for id 0xffffffff for fragment TestFragment{40de5b90 #0 id=0xffffffff android:switcher:-1:0}
		 * at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:864)
		 *
		 * The ID 0x7F04FFF0 is large enough to probably never be used for anything else
         */
        mViewPager.setId( 0x7F04FFF0 );

        super.onCreate(savedInstanceState);

        /*
         * Set the ViewPager as the content view
         */
        setContentView(mViewPager);
    }

    /**
     * Add a tab with a backing Fragment to the action bar
     * @param titleRes A string resource pointing to the title for the tab
     * @param fragmentClass The class of the Fragment to instantiate for this tab
     * @param args An optional Bundle to pass along to the Fragment (may be null)
     */
    protected void addTab(int titleRes, Class fragmentClass, Bundle args ) {
        adapter.addTab( getString( titleRes ), fragmentClass, args );
    }
    /**
     * Add a tab with a backing Fragment to the action bar
     * @param titleRes A string to be used as the title for the tab
     * @param fragmentClass The class of the Fragment to instantiate for this tab
     * @param args An optional Bundle to pass along to the Fragment (may be null)
     */
    protected void addTab(CharSequence title, Class fragmentClass, Bundle args ) {
        adapter.addTab( title, fragmentClass, args );
    }

    private static class TabsAdapter extends FragmentPagerAdapter implements TabListener, ViewPager.OnPageChangeListener {

        private final SherlockFragmentActivity mActivity;
        private final ActionBar mActionBar;
        private final ViewPager mPager;

        /**
         * @param fm
         * @param fragments
         */
        public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
            super(activity.getSupportFragmentManager());
            this.mActivity = activity;
            this.mActionBar = activity.getSupportActionBar();
            this.mPager = pager;

            mActionBar.setNavigationMode( ActionBar.NAVIGATION_MODE_TABS );
        }

        private static class TabInfo {
            public final Class fragmentClass;
            public final Bundle args;
            public TabInfo(Class fragmentClass,
                    Bundle args) {
                this.fragmentClass = fragmentClass;
                this.args = args;
            }
        }

        private List mTabs = new ArrayList();

        public void addTab( CharSequence title, Class fragmentClass, Bundle args ) {
            final TabInfo tabInfo = new TabInfo( fragmentClass, args );

            Tab tab = mActionBar.newTab();
            tab.setText( title );
            tab.setTabListener( this );
            tab.setTag( tabInfo );

            mTabs.add( tabInfo );

            mActionBar.addTab( tab );
            notifyDataSetChanged();
        }

        @Override
        public Fragment getItem(int position) {
            final TabInfo tabInfo = mTabs.get( position );
            return (Fragment) Fragment.instantiate( mActivity, tabInfo.fragmentClass.getName(), tabInfo.args );
        }

        @Override
        public int getCount() {
            return mTabs.size();
        }

        public void onPageScrollStateChanged(int arg0) {
        }

        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        public void onPageSelected(int position) {
            mActionBar.setSelectedNavigationItem( position );
        }

        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            TabInfo tabInfo = (TabInfo) tab.getTag();
            for ( int i = 0; i < mTabs.size(); i++ ) {
                if ( mTabs.get( i ) == tabInfo ) {
                    mPager.setCurrentItem( i );
                }
            }
        }

        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        }

        public void onTabReselected(Tab tab, FragmentTransaction ft) {
        }
    }
}

Using TabSwipeActivity

The only extra method(s) that TabSwipeActivity supports are the addTab() methods:

void addTab(int titleRes, Class fragmentClass, Bundle args );
void addTab(CharSequence title, Class fragmentClass, Bundle args );

Each of these will add a tab and assign a fragment to that tab. Only difference between the two is that the first accepts a string resource ID for the tab title, and the second accepts any CharSequence.

Example usage

TabSwipeTestActivity.java:

import android.os.Bundle;

public class TabSwipeTestActivity extends TabSwipeActivity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		addTab( "Tab 1", TestFragment.class, TestFragment.createBundle( "Fragment 1") );
		addTab( "Tab 2", TestFragment.class, TestFragment.createBundle( "Fragment 2") );
		addTab( "Tab 3", TestFragment.class, TestFragment.createBundle( "Fragment 3") );
	}

}

Note that your activity shouldn’t call setContentView(). TabSwipeActivity does that for you!

TestFragment.java:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class TestFragment extends Fragment {

	public static final String EXTRA_TITLE = "title";

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		TextView txt = new TextView( inflater.getContext() );
		txt.setGravity( Gravity.CENTER );
		txt.setText( "Fragment" );

		if ( getArguments() != null && getArguments().containsKey( EXTRA_TITLE ) ) {
			txt.setText( getArguments().getString( EXTRA_TITLE ) );
		}
		return txt;
	}

	public static Bundle createBundle( String title ) {
		Bundle bundle = new Bundle();
		bundle.putString( EXTRA_TITLE, title );
		return bundle;
	}

}