6

I have an array containing serializable objects and am trying to use Intent's putExtra() and Bundle's getSerializable(). However, I am getting a class cast exception and don't understand why.

Below is the code. UserInfo is my own class which implements Serializable, and I have been able to pass individual UserInfo objects between activities. I've only ran into this problem when attempting to use an array.

Code sending the serializable:

Intent intent = new Intent( JOIN_GROUP ); //JOIN_GROUP is a constant string

String user_ids[] = packet.userIDs();

int length = user_ids.length;
UserInfo users[] = new UserInfo[length];

for ( int i = 0; i < length; ++i )
    users[i] = getUserByID( user_ids[i] );

intent.putExtra( USERS_IN_GROUP, users );

Code retrieving the serializable:

Bundle extra = intent.getExtras();          
String action = intent.getAction();

if ( action.equals(IMService.JOIN_GROUP) )
{   
    //CLASS CAST EXCEPTION
    UserInfo users[] = (UserInfo[]) extra.getSerializable( IMService.USERS_IN_GROUP ); 
}

Here is the error:

enter image description here

Question

I'm aware I could probably just use a different data structure, but I would like to understand why the array does not work since arrays are serializable?

EDIT: SnyersK was able to get a simpler but similar scenario to work. So I tried the same thing, and I still get the same exception. It turned my array of Tests into an Object when retrieving the array, which results in the casting exception.

My Test object:

package types;

import java.io.Serializable;

public class Test implements Serializable {

    private static final long serialVersionUID = 1L;

    private String hello = "hello";

}

Code to pass the array of Test objects:

Test myArray[] = new Test[] { new Test(), new Test() };
Intent i = new Intent( this, Login.class );
i.putExtra( "key", myArray );

Code to retrieve the array of Test objects:

Bundle extras = getIntent().getExtras();
Test array[] = (Test[]) extras.getSerializable( "key" ); //Class Cast Exception
Kacy
  • 3,330
  • 4
  • 29
  • 57
  • Update the question with some error log. – Janny Feb 25 '15 at 13:22
  • Seen this? http://stackoverflow.com/a/14333555/233048 – mach Feb 25 '15 at 13:23
  • I just tried to pass an array of a serializable object myself, and i'm not seeing any errors. You're 100% positive you're not putting another extra using the same string before you're launching your second activity? – SnyersK Feb 25 '15 at 14:07
  • @SnyersK That is strange. I am 100% positive. There is only one place in one class where I make this happen. And changing the data structure to an ArrayList worked. I added many log statements to the code as well to make sure these two snippets were being executed. You're saying you didn't get a class cast exception on an array of your own custom serializable objects? For whatever reason, the runtime object turns into an instance of the Object class instead of remaining as a UserInfo array. – Kacy Feb 25 '15 at 16:49
  • Yes, i wrote a quick test using a custom object `Test` which only contained a string and implemented Serializable. I made an array containing 5 objects and put this as an extra when starting a second activity. It all worked just fine. I had no errors and my objects were all there in my second activity. – SnyersK Feb 25 '15 at 16:52
  • @SnyersK That is super weird. I don't know why it didn't work for me then. Maybe my eclipse or installed plugins are buggy? Idk. Just changing the array to an ArrayList spit out an ArrayList on the other end. Using the array spit out an Object on the other end. – Kacy Feb 25 '15 at 17:45
  • That's really weird indeed. I'm using Android Studio, but your IDE shouldn't be able to affect this. I was using a very simple object though, maybe that's why it worked for me. I did notice that your custom object does have to implement serializable, but since you said that your `UserInfo` does, I really don't know what the problem could be. – SnyersK Feb 26 '15 at 09:54
  • @SnyersK I tried out what you said, and I got the same exception. I used 2 different activities than the original 2 involved in the problem. I edited my question with the Test object I used, and the code to pass/retrieve the object (which was place in the activities' onCreate methods. – Kacy Feb 26 '15 at 11:48
  • I literally just copied your code and it's running fine here. On what version of android are you testing? I'm using a nexus 5 running 5.0.1 – SnyersK Feb 26 '15 at 12:14
  • @SnyersK I tried it on 2 phones. `Model number: HTC Sensation 4G, Android version: 2.3.4` and `Model number: Galaxy Nexus, Android version 4.2.2`. – Kacy Feb 26 '15 at 14:29
  • My guess is that It has something to do with the version. I just tested on a Galaxy Nexus virtual device running 4.2.2 and i get the classCastException like you say. Same thing on my Galaxy S3 running android 4.3. – SnyersK Feb 26 '15 at 14:57
  • @SnyersK ah hah! We have found the source of the problem ! Considering both the putExtra() and getSerializable() methods are supposed to support API level 1 (according to the docs), this is definitely a bug. Post an answer, and I'll accept it. Goob job, Snyers. – Kacy Feb 26 '15 at 16:42

4 Answers4

12

Apparently this is a bug on older versions of Android. (Version 5.0.1 is the only version tested on which it works like expected, perhaps it works from 5.0 and up)

The bug

Let's say we have an Array of an object called User which implements Serializable.

User[] users;

When we try to pass this array to another activity through an intent like this

intent.putExtra("myKey", users);

our users will get converted to Object[].

If we try to get our array from the intent in the second activity like so

User[] users = getIntent().getSerializableExtra("myKey");

We will get a ClassCastException.

From the docs, we can conclude that this shouldn't be a problem since Array is a serializable class, and putExtra(String key, Serializable value) has been added since api 1.

If you want to pass an Array through an intent on Android versions prior to Android 5.0.1 (maybe some older versions work aswell, but up to Android 4.3 it is NOT working) You'll have to work around it. You could do this by converting your Array to an ArrayList.

EDIT

another workaround is copying your extra to a new array, thus, not having to cast it. I'm not sure about the positive/negative consequences of the method though. It would look like this:

Object[] array = (Object[]) getIntent().getSerializableExtra("key");
User[] parsedArray = Arrays.copyOf(array, array.length, User[].class);

I found this method here: How to convert object array to string array in Java.

SnyersK
  • 1,296
  • 8
  • 23
  • 1
    You should mention that User implements Serializable. – Kacy Feb 26 '15 at 17:40
  • Sorry, not trying to be nit-picky, It gets converted to `Object`, not `Object[]`. And if anyone's worried about a waste of space with using an `ArrayList`, they can always pass in the initial capacity they want to the constructor, achieving nearly the same amount of space as using a basic array. (I say "nearly" because ArrayLists have a few more instance variables, but no big deal.) Good answer. – Kacy Feb 26 '15 at 17:49
  • My stacktrace says it's an `Object[]`. `Caused by: java.lang.ClassCastException: java.lang.Object[] cannot be cast to my.package.testing.Test[]` – SnyersK Feb 27 '15 at 14:25
  • Oh okay I think I just didn't expland the stack traces prodcued in the logcat all the way (and tbh idk how). Thanks again. – Kacy Feb 27 '15 at 15:12
  • 1
    i've seen other people write it as `[Ljava.lang.Object;` aswell. I suppose the ' [L ' means it's an array. I added another possible workaround btw. – SnyersK Feb 27 '15 at 15:18
  • this is the best solution ever for this bug – Abraham Putra Prakasa Dec 16 '16 at 08:34
2

Replace

intent.putExtra( USERS_IN_GROUP, users );

with

intent.putExtra( USERS_IN_GROUP, new ArrayList<UserInfo>(Arrays.asList(users)));
Yash Sampat
  • 30,051
  • 12
  • 94
  • 120
  • I get a compiler error using this: `The method putExtra(String, boolean) in the type Intent is not applicable for the arguments (String, List)`. I thought that would work too considering the description for asList() says it returns a serializable List. – Kacy Feb 25 '15 at 13:29
  • try bundle.putparcelable() – Yash Sampat Feb 25 '15 at 13:36
  • @Zygotelnit I believe you, but the problem is the compiler error when putting the extra. At the moment it doesn't matter what I cast the returned object from `getSerializable()` to since that won't happen until run time. – Kacy Feb 25 '15 at 13:37
  • UserInfo needs to implement `Serializable` I guess – Yash Sampat Feb 25 '15 at 13:40
  • It does. I've been able to use getSerializable() on it for the past few weeks. – Kacy Feb 25 '15 at 13:47
  • I gave this an upvote for now since it's a simple solution to the problem but doesn't answer my question about why the array does not work. I'm just going to change the data structure to an ArrayList instead of converting it to a list and passing it to the ArrayList constructor, but it's the same idea. – Kacy Feb 25 '15 at 14:30
  • It's a solution to the exception but does not answer the question (at the bottom of my post). I'll accept the first answer that does. – Kacy Feb 25 '15 at 14:42
0

try like this:

Intent intent = new Intent( JOIN_GROUP ); //JOIN_GROUP is a constant string

String user_ids[] = packet.userIDs();

int length = user_ids.length;
UserInfo users[] = new UserInfo[length];

for ( int i = 0; i < length; ++i )
    users[i] = getUserByID( user_ids[i] );

Bundle bnd=new Bundle();
bnd.putSerializable(USERS_IN_GROUP, users);

while sending you are attaching to intent and while retrvng you are reading from the bundle (which is intent extras).

Rathan Kumar
  • 2,567
  • 2
  • 17
  • 24
  • I don't think this is the problem. I've been able to pass serializable objects successfully using my approach for the past few weeks. I only ran into the problem when using an array. So I'm not sure why this would solve it. – Kacy Feb 25 '15 at 14:28
0

Is the UserInfo inner class or a separate java class file?

If it is an inner class, then the parent class also should be Serializable.

If not, make UserInfo class implement Parcelable and use

intent.putParcelableArrayListExtra(USERS_IN_GROUP, new ArrayList<UserInfo>(Arrays.asList(users)));
Vamsi
  • 878
  • 1
  • 8
  • 25
  • It's a separate class. – Kacy Feb 25 '15 at 13:55
  • Implementing Parcelable takes much more effort than implementing Serializable, and efficiency is not an issue at the moment so Serializable is what I'd like to use. Also, I would have to change many lines of code since the methods to pass UserInfo as Serializable objects are different than Parcelable. I'm making a chatting app so UserInfo objects are passed around quite a bit. – Kacy Feb 25 '15 at 14:36