Passing an object between activities using an Intent

Published on: July 23, 2013 Written by: Thokozani Mhlongo

Right, A few months ago I have been creating an Android that manages playlists. Its a simple application that I created just to test passing the objects I defined from one activity to the next. At first I thought...

I'm sure Android handles everything for me...

The answer is....well...not exactly. For this to happen you have to implement an interface called Parcelable. But before we get into that I should first highlight what happens if you attempt to do pass your objects that do not implement this interface. Have a look at this screenshot:

passing objects using intents

If you look at the dialog, I am attempting to pass an object of type PlayList to SongActivity but its getting mad at me. It thinks its a boolean but then its not. It then suggests I change it to an Integer of which is it not. This is because it doesn't know how to pass this object unless I tell it. In other words, it doesn't know how to encode my object unless I dictate. The Intent doesn't even know of the kind of data my object has. That dictating mechanism is to implement the Parcelable interface.

When you implement this interface you have to write all your class' data inside a Parcel. This is what the Intent use to pass information between Services and Activities. So with that said lets look at our code.

We have two classes in this example. A Song class and PlayList class. Both of these classes have to implement this interface because the playlist class has to put its contents inside a Parcel, and so does our Song class. Remember, every class has to define its encoding (pack) process and consequently must define a way to decode (unpack) itself from the parcel. This is our Song class definition:

package com.parcelableexample.object;

import android.os.Parcel;
import android.os.Parcelable;

public class Song implements Parcelable {
    private String songTitle;
    private long songDuration;
    
    public Song() {}
    public Song(String title, long duration) {
        this.songTitle = title;
        this.songDuration = duration;
    }
    
    public void setSongTitle(String title) {
        this.songTitle = title;
    }
    
    public void setSongDuration(long duration) {
        this.songDuration = duration;
    }
    
    public String getSongTitle() {
        return this.songTitle;
    }
    
    public long getSongDuration() {
        return this.songDuration;
    }
    
    //The unpacker from the parcel (as I like to call it)
    public static final Parcelable.Creator<Song> CREATOR = new Parcelable.Creator<Song>() {

        @Override
        public Song createFromParcel(Parcel source) {
            return new Song(source); //Using private constructor to unpack (Defined at the end of this class)
        }

        @Override
        public Song[] newArray(int size) {
            return new Song[size];
        }
        
    };
    
    @Override
    public int describeContents() {
        return 0;
    }
    
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //This is where we put this class' attributes into the parcel
        dest.writeString(this.songTitle);
        dest.writeLong(this.songDuration);
    }
    
    /** The constructor used to unpack the contents of this class that's in the parcel */
    private Song(Parcel source) {
        this.songTitle = source.readString();
        this.songDuration = source.readLong();
    }
}

 

There are three things to notice about this class.

  1. You write the class attributes inside the writeToParcel() method. As you can see my song class has a String and a long attribute.
  2. I defined a private constructor that unpacks the contents from the parcel that's passed to it. This constructor is used inside the CREATOR. Now another vital thing to notice about this constructor is that you have to read the values exactly the way you wrote them. If your wrote a string first then you must read a string first
  3. You have to define a CREATOR (unpacker) variable that implements an inner interface of Parcelable. You will receive an exception if you don't implement it.

Now let's define out PlayList class:

package com.parcelableexample.object;

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

import android.os.Parcel;
import android.os.Parcelable;

public class PlayList implements Parcelable {

    private List<Song> songs;
    
    public PlayList() {}
    public PlayList(List<Song> songs) {
        this.songs = songs;
    }
    
    public void setSongs(List<Song> songs) {
        this.songs = songs;
    }
    
    public List<Song> getSongs() {
        return this.songs;
    }
    
    @Override
    public int describeContents() {
        return 0;
    }
    
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //This is where we put this class' attributes into the parcel
        dest.writeTypedList(this.songs);
    }
    
    public static final Parcelable.Creator<PlayList> CREATOR = new Creator<PlayList>() {
        
        @Override
        public PlayList[] newArray(int size) {
            return new PlayList[size];
        }
        
        @Override
        public PlayList createFromParcel(Parcel source) {
            return new PlayList(source);
        }
    };
    
    private PlayList(Parcel source) {
        this.songs = new ArrayList<Song>();
        source.readTypedList(this.songs, Song.CREATOR); //This is how the Song class will unpack itself through its CREATOR
    }
}


Just like our Song class, PlayList also has to implement Parcelable. Remember this is the object we will be passing to the next activity. But since it uses another object that we defined and that the OS knows nothing about we had to also implement Parcelable on that class too. If you notice the private constructor of our PlayList class, it reads a list of type Song and we pass the CREATOR (This CREATOR is from the Song class) variable so that each item can unpack itself inside the list too. Now all that's left to do is test our classes. This is our main activity layout which only has a single button to lauch the second activity:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:padding="20dp" >

    <Button
        android:id="@+id/btn_view_songs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:text="@string/songs_loaded" />

</LinearLayout>

 

Now let's define our main activity that creates the playlist:

package com.parcelableexample;

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

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

import com.parcelableexample.object.PlayList;
import com.parcelableexample.object.Song;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("Playlists");
        
        Button btnViewSongs = (Button) findViewById(R.id.btn_view_songs);
        btnViewSongs.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //Hard coding songs
                Song freckles = new Song("Eleven.Five - Freckles (Original Mix)", 360000); //6 minutes
                Song startlight = new Song("Claes Rosen - Starlight (Blood Groove and Kikis Remix)", 330000); //5.5 minutes
                Song goodMorning = new Song("Justin Oh - Good Morning (eleven.five Remix)", 300000); //5 minutes
                
                //Adding songs to a list
                List<Song> songs = new ArrayList<Song>();
                songs.add(freckles);
                songs.add(startlight);
                songs.add(goodMorning);
                
                //Adding songs to the Playlist object
                PlayList playList = new PlayList();
                playList.setSongs(songs);
                
                Intent intent = new Intent(MainActivity.this, SongsActivity.class);
                intent.putExtra("playlist", playList);
                
                startActivity(intent);
            }
        });
    }

}


As you can see, on our button click event I have put some of my favorite songs (Yes, I love Progressive House :-) ) and added them onto my playlist object. I then pass that object through the Intent object, and since our classes implement Parcelable the compiler won't complain this time around. Okay, Now that we've passed our object to the next activity it's time to retreive it. Here is our songs layout file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/songs" >
    

</LinearLayout>

 

This is our songs layout for our songs activity. We will be listing our songs. We could have used a ListView (in fact we should have used a ListView). That's a tutorial for another day though. This is how our song activity looks like:

package com.parcelableexample;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.parcelableexample.object.PlayList;
import com.parcelableexample.object.Song;

public class SongsActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_songs);
        setTitle("Songs");
        
        Intent intent = getIntent();
        PlayList playList = (PlayList) intent.getParcelableExtra("playlist");
        
        //Songs container
        LinearLayout songContainerView = (LinearLayout) findViewById(R.id.songs);
        
        //Lets populate the songs
        for(int i = 0; i < playList.getSongs().size(); i++) {
            Song song = playList.getSongs().get(i);
            
            LinearLayout songItemView = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.song_item, null);
            TextView songTitleView = (TextView) songItemView.findViewById(R.id.song_title);
            TextView songDurationView = (TextView) songItemView.findViewById(R.id.song_duration);
            
            songTitleView.setText(song.getSongTitle());
            songDurationView.setText("" + song.getSongDuration());
            
            //Add the song to the container
            songContainerView.addView(songItemView, i);
        }
    }

}

 

We use the method getParcelableExtra() for retieving our custom object and yes we have to cast it. The rest of the logic is quite simple. Here is our song item XML definition:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="20dp" >
    
    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="bold"
        android:textAllCaps="true"
        android:textIsSelectable="false"
        android:id="@+id/song_title"
        android:paddingBottom="4dp" />
    
    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="13sp"
        android:textAllCaps="true"
        android:textIsSelectable="false"
        android:id="@+id/song_duration"
        android:paddingBottom="10dp" />
    
    <!-- Seperator -->
    <LinearLayout android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#aaaaaa"></LinearLayout>

</LinearLayout>

 

This is the final resulr:

Comments