Passing complex objects to another Activity

03 Jun 2011 | Comments

Several months ago, I was faced with a problem of passing a complex object to another Activity. There are several ways of doing this:
I don't really understand the concepts behind an object being Parcelable or Serializable, so I was not comfortable using that approach. Deconstructing the object, on the other hand, is easier but it is quite hard to manage as the number of fields in the object increases.

And then I came across another solution: using Bundles. In this post, I will try to explain how to do that.

In this sample project, we want to pass an instance of an AttendeeObject from our main activity to another. To better illustrate how we can use this method for different data types, an AttendeeObject will hold three pieces of information: a String name, an integer age, and a boolean indicating the person's presence.

First, we construct the AttendeeObject like any old POJO. Create all the fields you want the object to contain and generate getters and setters for each. We then create the methods we would need to be able to pass, and subsequently receive, this object between activities.

To pass an AttendeeObject, we would need to put it in a Bundle and pass it using Intent#putExtra(String key, Bundle bundle).
public Bundle bundleAttendee(AttendeeObject attendee){
Bundle bundle = new Bundle();
bundle.putString(NAME, attendee.getName());
bundle.putInt(AGE, attendee.getAge());
bundle.putBoolean(PRESENCE, attendee.isPresent());

return bundle;
}
Here, we simply put into a Bundle the values in the object that we want to pass.

Next, we should be able to convert this bundle back to an AttendeeObject in the receiving Activity. This task is done by the following method:
public AttendeeObject unBundleAttendee(Bundle attendeeBundle){
AttendeeObject attendee = new AttendeeObject();
attendee.setName(attendeeBundle.getString(NAME));
attendee.setAge(attendeeBundle.getInt(AGE));
attendee.setIsPresent(attendeeBundle.getBoolean(PRESENCE));

return attendee;
}
Now, we are ready to use this in our application. I created a simple app that asks the user for the three values we would need to populate AttendeeObject.
I won't discuss the details of creating the layout file, but it might be worthy to note that the possible values for the Spinner are just "Yes" and "No".

When the user clicks on the "Create Attendee" button, the application creates a new AttendeeObject based on the values in the form and adds this to an ArrayList of AttendeeObjects.
private void onCreateAttendee() {
// Create a new AttendeeObject
AttendeeObject attendee = new AttendeeObject();
attendee.setName(mNameText.getText().toString());
attendee.setAge(Integer.valueOf(mAgeText.getText().toString()));
attendee.setIsPresent(mCurrentPresence);

// You can then do whatever you want with this object
// like adding it to an ArrayList or saving it to a database
mAttendees.add(attendee);

Toast.makeText(ComplexObjectDemo.this, "Attendee added!", Toast.LENGTH_SHORT).show();
}

When the user clicks on the "Submit Attendee" button, the app "packages" the chosen value into a Bundle and sets it as an extra to the Intent. For illustration purposes, the application always gets the second AttendeeObject in the ArrayList.
private void onSubmitObject() {
Intent intent = new Intent(ComplexObjectDemo.this, ComplexObjectReceiverDemo.class);

// For simplicity, we will always get the second value
Bundle value = createAttendeeBundle(1);

// In a "real" application, the Key should be defined in a constants file
intent.putExtra("test.complex.attendee", value);

startActivity(intent);
}

private Bundle createAttendeeBundle(int index) {
// Here, mAttendee is an instance of AttendeeObject
// and mAttendees is an ArrayList holding all the created attendees
return mAttendee.bundleAttendee(mAttendees.get(index));
}


Upon receiving the Intent, the new activity simply displays the values in the AttendeeObject passed into it. Here's how we can "unbundle" the Bundle:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_complex_demo_receiver);

// Get the extras passed to this Activity
setUpViews(getIntent().getExtras());
}

private void setUpViews(Bundle extras) {
// Again, the key should be in a constants file!
Bundle extraAttendee = extras.getBundle("test.complex.attendee");

AttendeeObject attendee = new AttendeeObject();

// Deconstruct the Bundle into the AttendeeObject
attendee = attendee.unBundleAttendee(extraAttendee);

// The AttendeeObject fields are now populated, so we set the texts
((TextView) findViewById(R.id.attendee_age_value))
.setText(String.valueOf(attendee.getAge()));
((TextView) findViewById(R.id.attendee_name_value))
.setText(attendee.getName());
((TextView) findViewById(R.id.attendee_presence_value))
.setText(String.valueOf(attendee.isPresent()));
}
Here's how the receiving activity looks like:Here's the complete AttendeeObject class.
package droidista.example;

import android.os.Bundle;

public class AttendeeObject {

public static final String NAME = "attendee.name";
public static final String AGE = "attendee.age";
public static final String PRESENCE = "attendee.presence";


private String mName;
private int mAge;
private boolean mIsPresent;

public AttendeeObject(){

}

public Bundle bundleAttendee(AttendeeObject attendee){
Bundle bundle = new Bundle();
bundle.putString(NAME, attendee.getName());
bundle.putInt(AGE, attendee.getAge());
bundle.putBoolean(PRESENCE, attendee.isPresent());

return bundle;
}

public AttendeeObject unBundleAttendee(Bundle attendeeBundle){
AttendeeObject attendee = new AttendeeObject();
attendee.setName(attendeeBundle.getString(NAME));
attendee.setAge(attendeeBundle.getInt(AGE));
attendee.setIsPresent(attendeeBundle.getBoolean(PRESENCE));

return attendee;
}

public String getName() {
return mName;
}

public void setName(String name) {
this.mName = name;
}

public int getAge() {
return mAge;
}

public void setAge(int age) {
this.mAge = age;
}

public boolean isPresent() {
return mIsPresent;
}

public void setIsPresent(boolean isPresent) {
this.mIsPresent = isPresent;
}
}
And our main activity:
package droidista.example;

import java.util.ArrayList;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;

public class ComplexObjectDemo extends Activity implements OnClickListener {

private Button mGoToNextIntent, mCreateAttendee, mClearFields;
private EditText mNameText, mAgeText;
private Spinner mPresence;

AttendeeObject mAttendee = new AttendeeObject();

private boolean mCurrentPresence;

private ArrayList<AttendeeObject> mAttendees = new ArrayList<AttendeeObject>();

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_complex_demo);

setUpViews();
}

private void setUpViews() {
setUpSpinner();

mNameText = (EditText)findViewById(R.id.name_text_input);
mAgeText = (EditText)findViewById(R.id.age_text_input);

mGoToNextIntent = (Button)findViewById(R.id.btn_submit_object);
mGoToNextIntent.setOnClickListener(this);

mCreateAttendee = (Button)findViewById(R.id.btn_create_attendee);
mCreateAttendee.setOnClickListener(this);

mClearFields = (Button) findViewById(R.id.btn_clear_fields);
mClearFields.setOnClickListener(this);
}

private void setUpSpinner() {
mPresence = (Spinner) findViewById(R.id.presence_spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.is_present_values, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mPresence.setAdapter(adapter);
}


private Bundle createAttendeeBundle(int index) {
// Here, mAttendee is an instance of AttendeeObject
// and mAttendees is an ArrayList holding all the created attendees
return mAttendee.bundleAttendee(mAttendees.get(index));
}

public void onClick(View v) {
switch(v.getId()){
case R.id.btn_create_attendee:
onCreateAttendee();
break;
case R.id.btn_submit_object:
onSubmitObject();
break;
case R.id.btn_clear_fields:
onClearFields();
break;
}
}

private void onClearFields() {
mAgeText.setText("");
mNameText.setText("");
mPresence.setSelection(0);
}

private void onSubmitObject() {

// For simplicity, we will always get the second value
Bundle value = createAttendeeBundle(1);

// In a "real" application, the Key should be defined in a constants file
Intent intent = new Intent(ComplexObjectDemo.this, ComplexObjectReceiverDemo.class);
intent.putExtra("test.complex.attendee", value);

// Start the new activity
startActivity(intent);
}

private void onCreateAttendee() {
// Create a new AttendeeObject
AttendeeObject attendee = new AttendeeObject();
attendee.setName(mNameText.getText().toString());
attendee.setAge(Integer.valueOf(mAgeText.getText().toString()));
attendee.setIsPresent(mCurrentPresence);

// You can then do whatever you want with this object
// like adding it to an ArrayList or saving it to a database
mAttendees.add(attendee);

// Notify the user
Toast.makeText(ComplexObjectDemo.this, "Attendee added!", Toast.LENGTH_SHORT).show();
}

public class MyOnItemSelectedListener implements OnItemSelectedListener {

public void onItemSelected(AdapterView parent, View view, int pos, long id) {
mCurrentPresence = (pos==0) ? true : false;
}

public void onNothingSelected(AdapterView parent) { /*Do nothing.*/ }
}
}
And here's the second activity:
package droidista.example;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class ComplexObjectReceiverDemo extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_complex_demo_receiver);

// Get the extras passed to this Activity
setUpViews(getIntent().getExtras());
}

private void setUpViews(Bundle extras) {
// Again, the key should be in a constants file!
Bundle extraAttendee = extras.getBundle("test.complex.attendee");
AttendeeObject attendee = new AttendeeObject();

// Deconstruct the Bundle into the AttendeeObject
attendee = attendee.unBundleAttendee(extraAttendee);

// The AttendeeObject fields are now populated, so we set the texts
((TextView) findViewById(R.id.attendee_age_value)).setText(String.valueOf(attendee.getAge()));
((TextView) findViewById(R.id.attendee_name_value)).setText(attendee.getName());
((TextView) findViewById(R.id.attendee_presence_value)).setText(String.valueOf(attendee.isPresent()));
}

}