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:
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.
- You write the class attributes inside the
writeToParcel()
method. As you can see my song class has aString
and along
attribute. - 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 - You have to define a
CREATOR
(unpacker) variable that implements an inner interface ofParcelable
. 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