Android Development 22

Android Development 22In this tutorial I will connect the ListFragment to the Census App, so that when you click on an item we’ll be able update the Contacts. This tutorial began at part 18, so if you missed that video start there.

All the Contacts are updated and we also cover how to store and pass contact IDs between activities. We also streamline the code by separating duplicate code out into another class and much more.

If you like videos like this, it helps to tell Google+ with a click here

Code From the Video

CensusApp.java

package com.newthinktank.censusapp;

//We will use the android.support.v4.app.Fragment
//support library so our app runs on older versions
//of Android

import java.util.UUID;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;

// Change Activity to FragmentActivity

// The FragmentManager ads Fragments to an Activity's view

// NEW Change to extend FragmentActivityBuilder so that
// that class can handle the work FragmentManager must do

public class CensusApp extends FragmentActivityBuilder {

	/* This work is now handled by FragmentActivityBuilder
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_census_app);
		
		FragmentManager fragManager = getSupportFragmentManager();
		
		// Check if the FragmentManager knows about the Fragment 
		// id we refer to
		
		Fragment theFragment = fragManager.findFragmentById(R.id.fragmentContainer);
		
		// Check if the Fragment was found
		
		if(theFragment == null){
			
			// If the Fragment wasn't found then we must create it
			
			theFragment = new ContactFragment();
			
			// Creates and commits the Fragment transaction
			// Fragment transactions add, attach, detach, replace
			// and remove Fragments.
			
			// add() gets the location to place the Fragment into and
			// the Fragment itself.
			
			fragManager.beginTransaction()
				.add(R.id.fragmentContainer, theFragment)
				.commit();
			
		}
	}
	
	*/
	
	// NEW : Call for FragmentActivityBuilder to have the FragmentManager
	// add the right Fragment to the Activity

	@Override
	protected Fragment createFragment() {
		
		// Get the Contact ID that was passed over
		
		UUID contactIdNumber = (UUID) getIntent()
				.getSerializableExtra(ContactFragment.CONTACT_ID);
		
		// Create an instance of ContactFragment and pass in
		// the ID so the proper Contact data is displayed
		
		return new ContactFragment().newContactFragment(contactIdNumber);
		
	}
	
	// END OF NEW

	
}

ContactListActivity.java

package com.newthinktank.censusapp;

//We will use the android.support.v4.app.Fragment
//support library so our app runs on older versions
//of Android

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;

//Change Activity to FragmentActivity

// The FragmentManager ads Fragments to an Activity's view

//NEW Change to extend FragmentActivityBuilder so that
//that class can handle the work FragmentManager must do

public class ContactListActivity extends FragmentActivityBuilder {

	/* This work is now handled by FragmentActivityBuilder
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_census_app);

		FragmentManager fragManager = getSupportFragmentManager();

		// Check if the FragmentManager knows about the Fragment 
		// id we refer to

		Fragment theFragment = fragManager.findFragmentById(R.id.fragmentContainer);

		// Check if the Fragment was found

		if(theFragment == null){

			// If the Fragment wasn't found then we must create it
			// We change this from ContactFragment, which we used 
			// in CensusApp

			theFragment = new FragmentContactList();

			// Creates and commits the Fragment transaction
			// Fragment transactions add, attach, detach, replace
			// and remove Fragments.

			// add() gets the location to place the Fragment into and
			// the Fragment itself.

			fragManager.beginTransaction()
			.add(R.id.fragmentContainer, theFragment)
			.commit();

		}
	}
	
	*/
	
	// NEW : Call for FragmentActivityBuilder to have the FragmentManager
	// add the right Fragment to the Activity

	@Override
	protected Fragment createFragment() {
		return new FragmentContactList();
	}


}

FragmentActivityBuilder.java

package com.newthinktank.censusapp;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;

// NEW : This class will use the FragmentManager to ad 
// Fragments to multiple Activity views

public abstract class FragmentActivityBuilder extends FragmentActivity{
	
	// This method must be implemented so that the right
	// type of Fragment can be returned.
	// CensusApp gets ContactFragment()
	// ContactListActivity gets FragmentContactList()
	
	protected abstract Fragment createFragment();

	@Override
	protected void onCreate(Bundle arg0) {
		super.onCreate(arg0);

		setContentView(R.layout.activity_census_app);

		FragmentManager fragManager = getSupportFragmentManager();

		// Check if the FragmentManager knows about the Fragment 
		// id we refer to

		Fragment theFragment = fragManager.findFragmentById(R.id.fragmentContainer);

		// Check if the Fragment was found

		if(theFragment == null){

			// If the Fragment wasn't found then we must create it

			// NEW We can generate many types of Fragments by having 
			// CreateFragment define the type. So
			// theFragment = new ContactFragment();
			// is replaced by 
			
			theFragment = createFragment();
			

			// Creates and commits the Fragment transaction
			// Fragment transactions add, attach, detach, replace
			// and remove Fragments.

			// add() gets the location to place the Fragment into and
			// the Fragment itself.

			fragManager.beginTransaction()
			.add(R.id.fragmentContainer, theFragment)
			.commit();

		}

	}

}

FragmentContactList.java

package com.newthinktank.censusapp;

import java.util.ArrayList;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.TextView;

// The ListFragment displays a list of items in a 
// ListView, by binding to our ArrayList using an
// ArrayAdapter in this situation.

public class FragmentContactList extends ListFragment {
	
	// Stores the list of Contacts
	
	private ArrayList<Contact> contactList;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		// Change the title for the current Activity
		
		getActivity().setTitle(R.string.fragment_contact_list_title);
		
		// Get the ArrayList from AllContacts
		
		contactList = AllContacts.get(getActivity()).getContactList();
		
		ContactAdapter contactAdapter = new ContactAdapter(contactList);
		
		// Provides the data for the ListView by setting the Adapter 
		
		setListAdapter(contactAdapter);
		
	}
	
	// NEW
	// Handle what happens when an item in the ListFragment is clicked
	
	@Override
	public void onListItemClick(ListView l, View v, int position, long id) {
		
		Log.e("CENSUS APP", "LIST ITEM CLICKED");
		
		Contact clickedContact = ((ContactAdapter) getListAdapter()).getItem(position);
		
		// Pass the Context with getActivity and the Activity to
		// start being ContactActivity
		
		Intent newIntent = new Intent(getActivity(), CensusApp.class);
		
		// Add the IdNumber when calling for the Activity to display
		// so that the right data is loaded
		
		newIntent.putExtra(ContactFragment.CONTACT_ID, 
				clickedContact.getIdNumber());
		
		startActivityForResult(newIntent, 0);
		
	}
	

	@Override
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		
		((ContactAdapter)getListAdapter()).notifyDataSetChanged();
		
	}
	
	// END OF NEW

	private class ContactAdapter extends ArrayAdapter<Contact> {

		public ContactAdapter(ArrayList<Contact> contacts) {
	    	
	    		// An Adapter acts as a bridge between an AdapterView and the 
				// data for that view. The Adapter also makes a View for each 
				// item in the data set. (Each list item in our ListView)
			
				// The constructor gets a Context so it so it can use the 
				// resource being the simple_list_item and the ArrayList
				// android.R.layout.simple_list_item_1 is a predefined 
				// layout provided by Android that stands in as a default
	    	
	            super(getActivity(), android.R.layout.simple_list_item_1, contacts);
	    }
		
		// getView is called each time it needs to display a new list item
		// on the screen because of scrolling for example.
		// The Adapter is asked for the new list row and getView provides
		// it.
		// position represents the position in the Array from which we will 
		// be pulling data.
		// convertView is a pre-created list item that will be reconfigured 
		// in the code that follows.
		// ViewGroup is our ListView
		
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			
			// Check if this is a recycled list item and if not we inflate it
			
			if(convertView == null){
				
				convertView = getActivity().getLayoutInflater()
						.inflate(R.layout.list_item_contact, null);
				
			}
			
			// Find the right data to put in the list item
			
			Contact theContact = getItem(position);
			
			// Put the right data into the right components
			
			TextView contactNameTextView =
	                (TextView)convertView.findViewById(R.id.contact_name);
			
			contactNameTextView.setText(theContact.getName());
			
	        TextView streetTextView =
	                (TextView)convertView.findViewById(R.id.contact_street);
	        
	        streetTextView.setText(theContact.getStreetAddress());
	        
	        CheckBox contactedCheckBox =
	                (CheckBox)convertView.findViewById(R.id.contact_contacted_checkbox);
	        
	        contactedCheckBox.setChecked(theContact.getContacted());
			
			// Return the finished list item for display
			
	        return convertView;
			
		}
		
	}

}

ContactFragment.java

package com.newthinktank.censusapp;

import java.util.UUID;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;

public class ContactFragment extends Fragment {
	
	// NEW Store the ID NUMBER for the current Contact
	// This is the key of the key / value pair that will
	// store the Contacts Id Number
	
	public static final String CONTACT_ID =
			"com.newthinktank.censusapp.contact_id";
	
	// END OF NEW

	private Contact contact;
	private EditText contactNameEditText;

	private EditText contactStreetEditText;
	private EditText contactCityEditText;
	private EditText contactPhoneEditText;
	
	private CheckBox contactedCheckBox;
	
	// NEW
	
	public static ContactFragment newContactFragment(UUID contactId){
		
		// A Bundle is used to pass data between Activitys
		
		Bundle passedData = new Bundle();
		
		// Put the Contacts ID in the Bundle
		
		passedData.putSerializable(CONTACT_ID, contactId);
		
		ContactFragment contactFragment = new ContactFragment();
		
		contactFragment.setArguments(passedData);
		
		return contactFragment;
		
	}
	
	// END OF NEW
	

	// Generate this with Right Click - Source - Override/Implement methods
	// This method is called when the Fragment is called for.
	// We initialize everything here.

	@Override
	public void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);

		// NEW Replace contact = new Contact();
		// Get the value from CONTACT_ID that was passed in
		
		UUID contactId = (UUID) getArguments().getSerializable(CONTACT_ID);
		
		// Get the Contact with the matching ID
		
		contact = AllContacts.get(getActivity()).getContact(contactId);
		
		// END OF NEW
		
	}

	// Used to inflate the Fragment, or show it on the screen

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {

		// Pass in the layout to inflate, the views parent and whether
		// to add the inflated view to the parent.
		// We mark this false because the Activity will add the view.

		View theView = inflater.inflate(R.layout.fragment_contact, container, false);

		// Get a reference to the EditText

		contactNameEditText = (EditText) theView.findViewById(R.id.contactNameEditText);

		// If text in the EditText box is edited it will change the
		// name.
		
		contactStreetEditText = (EditText) theView.findViewById(R.id.contactStreetEditText);
		contactCityEditText = (EditText) theView.findViewById(R.id.contactCityEditText);
		contactPhoneEditText = (EditText) theView.findViewById(R.id.contactPhoneEditText);
		
		// All the EditText components will use just one TextWatcher
		// which auto updates Contact.java

		TextWatcher editTextWatcher = new TextWatcher() {

			@Override
			public void onTextChanged(CharSequence arg0, int arg1, int arg2,
					int arg3) {

				if (contactNameEditText.hasFocus() == true){

					contact.setName(arg0.toString());

				} else if (contactStreetEditText.hasFocus() == true){
					
					contact.setStreetAddress(arg0.toString());
					
				} else if (contactCityEditText.hasFocus() == true){
					
					contact.setCity(arg0.toString());
					
				} else if (contactPhoneEditText.hasFocus() == true){
					
					contact.setPhoneNumber(arg0.toString());
					
				}

			}

			@Override
			public void afterTextChanged(Editable s) {
				// TODO Auto-generated method stub

			}

			@Override
			public void beforeTextChanged(CharSequence s, int start,
					int count, int after) {
				// TODO Auto-generated method stub

			}
		};

		
		contactStreetEditText.addTextChangedListener(editTextWatcher);
		contactCityEditText.addTextChangedListener(editTextWatcher);
		contactPhoneEditText.addTextChangedListener(editTextWatcher);

		contactNameEditText.addTextChangedListener(editTextWatcher);

		// Create CheckBox Listener
		
		contactedCheckBox = (CheckBox) theView.findViewById(R.id.contactedCheckBox);
		
		contactedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){

			@Override
			public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
				
				// NEW Change the value of checked to the opposite
				// of what it was if clicked
				
				contact.setContacted(!contact.getContacted());
				
				// END OF NEW
				
			}
			
		});
		
		// NEW
		// Get the values for the current Contact and put them in
		// the right Components
		
		contactNameEditText.setText(contact.getName());
		contactStreetEditText.setText(contact.getStreetAddress());;
		contactCityEditText.setText(contact.getCity());
		contactPhoneEditText.setText(contact.getPhoneNumber());
		contactedCheckBox.setChecked(contact.getContacted());
		
		// END OF NEW


		// Pass in the layout to inflate, the views parent and whether
		// to add the inflated view to the parent.
		// We mark this false because the Activity will add the view.

		return theView;

	}

}

list_item_contact.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <CheckBox android:id="@+id/contact_contacted_checkbox"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:layout_alignParentRight="true"
    android:enabled="false"
    android:focusable="false"
    android:padding="4dp" />
    
    <TextView
        android:id="@+id/contact_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@id/contact_contacted_checkbox"
        android:padding="4dp"
        android:text="@string/list_item_contact_name"
        android:textStyle="bold" />
    
    <TextView
        android:id="@+id/contact_street"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/contact_name"
        android:layout_toLeftOf="@id/contact_contacted_checkbox"
        android:padding="4dp"
        android:text="@string/list_item_contact_street" />

</RelativeLayout>

23 Responses to “Android Development 22”

  1. Richard says:

    Hello Derek, the tutorial series is excellent!

    Im not sure where to post requests so here it goes:

    I would like to make Reference apps based on html documents; for example I have a homebrew RPG website with basic html links to rules, and spell lists which link to hundreds of spells.

    Is there an easy way to integrate all the html content with minimal retyping/reformating etc? (like copying the html pages in a project folder and having an app shell to access and navigate with options like home, back, next, bookmark?)
    Having the content integrated in the app, so that you can get it from a Play Store and consult/read it offline.

    If thats not possible, a tutorial for a reader app with internal links between pages would be great.

    best regards

    Richard

    • Derek Banas says:

      Hello Richard,

      I have a couple of tutorials on parsing xml and json in this tutorial series. You could also pull in the page and then pull out the information you need with regular expressions. You may also find this useful TagSoup.

      I’ll do my best to make a tutorial on how to parse regular html in Android.

  2. Marcel says:

    Hey Derek,

    I really love your tutorials! Helped me a lot to develope my own application 🙂

    Is it possible to make a tutorial video about the Navigation Drawer and ActionBars?

    Best regards!

    Marcel

  3. Asalam says:

    Hello Derek,
    Is it possible for you to upload the tutorials in some other video sharing website in addition to youtube. I’m from a country where unfortunately, youtube is banned 🙁 I will really appreciate if you can share your videos in facebook or other sharing sites.

    Thanks

    • Derek Banas says:

      Hello,

      I’d be happy to upload them, but I don’t know where to upload. Youku for example wouldn’t allow me to upload all the videos for some reason. Some where ok and other were not.

      If you give me a list of sites you can use I’ll see what I can do.

      Thank you
      Derek

      • Asalam says:

        Thanks Derek for replying. Though I myself have never done much video sharing. But I think you can create a facebook page and upload videos there. In addition couple of other video sharing sites that I’m aware of are
        a) metacafe.com
        b) dailymotion.com

        Thanks
        Asalam

        • Derek Banas says:

          Hi Asalam,

          I have a few problems using these sites, but 1 of them may work.

          1. Facebook randomly won’t allow me to upload certain videos. That is why I no longer use Facebook
          2. Metacafe only allows videos to be 10 minutes or less. They have also blocked some of my videos
          3. Dailymotion might work. I’ll give it a try

          Thanks
          Derek

      • Asalam says:

        Hi Derek,
        My previous comment has been in moderation. May be because i used website addresses. Anyways, my suggestion for video sharing were
        a) You can use facebook page and share videos
        b) metacafe and dailymotion work similar to youtube and can be used for video sharing.

        Thanks

        • Derek Banas says:

          Sorry it took so long to respond. I have to approve comments on my own because my site gets attacked all of the time. It looks like DailyMotion might work. I check it out.

  4. Anonymous says:

    This is copy from Android programming Big nerd ranch

  5. didrocks says:

    Hey Derek! Thanks for your very helpful set of tutorials, they are really clear and well prepared. 🙂

    However, I’m wondering why in the android, when people use fragments, it seems that you (and everyone else) is using setArguments/getArguments to pass data around, where in your factory method (newContactFragment) you could have used a directement assignement: contactFragment.contactId = passedData.

    Is it something linked to fragment life management? (but you are only reusing the argument in onCreate(), so only at creation time, not after a pause/resume).
    Thanks for shedding some lights on this!

    • Derek Banas says:

      It is probably because we all learned from the same tutorials and that just stuck. I do my best to keep up on deprecated functions with Android. Thankfully they aren’t fully deprecating code. Since I make apps all of the time I have less and less time to look back at improved new options. I hope that makes sense.

  6. yun zhou says:

    A bug in class ContactFragment, function onCreateView
    Step to reproduce
    1 On contact list, click the first person
    2 On contact page, do nothing just hit back button
    3 On the contact list, the name disappear

    The reason is these code
    contactNameEditText.setText(contact.getName());
    contactStreetEditText.setText(contact.getStreetAddress());
    contactCityEditText.setText(contact.getCity());
    contactPhoneEditText.setText(contact.getPhoneNumber());
    is after your set the listener, these code will invoke the listener as well.
    The bad news is the focus in always on the name text field. so the name
    field will be overwritten by phoneNumber.
    You can log the contact before you return the view.
    So these setText codes should be put before set the listener.

    • Derek Banas says:

      Thank you for posting the error. One problem with posting videos fast is that I don’t have time to optimize the code. I’ll eventually start making complete apps that are optimized.

  7. Miki says:

    Hey Derek,
    Great tutorials! My question to you and everyone that reading this is, dont I need something that make my onListItemClick to work? I have made a copy of your application but with products instead of contacts and the only thing I do not get to work is when I press in the list the details should show. Only thing I get when I press in my textview is “TextView does not support text selection. Action mode cancelled.” I have tried to add and mix this stuff in the xml file:
    android:enabled=”false”
    android:clickable=”true”
    android:focusable=”false”
    android:focusableInTouchMode=”false”
    but non of them changed anything more than I can’t modify in the list (which was good but in the end did not help me). Please could someone help me with my problem?

  8. José Gonçalves says:

    Hi Derek,

    I have a question, on my code I get a java.lang.NullPointerException everytime I click one of the contacts, which seems to be caused by the listeners on ContactFragment. I also copied your code entirely (maintaning the package name to match mine) and still get the same error

    Since I’m so new to this, I would appreciate if you could help me figure out what seems to be the problem. I commented all the setName() from ContactFragment.java and then the contact info opens (with all fields empty), and as soon as I put a letter in one of the EditText the app crashes again with the null pointer exception.

    The error in logcat is in here: http://pastebin.com/NJ2hYgXk

    Thanks in advance,

    José

Leave a Reply

Your email address will not be published.

Google+