Passing enum or object through an intent (the best solution)
Asked Answered
B

15

259

I have an activity that when started needs access to two different ArrayLists. Both Lists are different Objects I have created myself.

Basically I need a way to pass these objects to the activity from an Intent. I can use addExtras() but this requires a Parceable compatible class. I could make my classes to be passed serializable but as I understand this slows down the program.

What are my options?

Can I pass an Enum?

As an aside: is there a way to pass parameters to an Activity Constructor from an Intent?

Brio answered 14/5, 2010 at 17:32 Comment(1)
Maybe I'm missing something, but how is an enum related to an ArrayList?Vulgarism
M
651

This is an old question, but everybody fails to mention that Enums are actually Serializable and therefore can perfectly be added to an Intent as an extra. Like this:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

intent.putExtra("AwesomeEnum", AwesomeEnum.SOMETHING);

AwesomeEnum result = (AwesomeEnum) intent.getSerializableExtra("AwesomeEnum");

The suggestion to use static or application-wide variables is a really bad idea. This really couples your activities to a state managing system, and it is hard to maintain, debug and problem bound.


ALTERNATIVES:

A good point was noted by tedzyc about the fact that the solution provided by Oderik gives you an error. However, the alternative offered is a bit cumbersome to use (even using generics).

If you are really worried about the performance of adding the enum to an Intent I propose these alternatives instead:

OPTION 1:

public enum AwesomeEnum {
  SOMETHING, OTHER;
  private static final String name = AwesomeEnum.class.getName();
  public void attachTo(Intent intent) {
    intent.putExtra(name, ordinal());
  }
  public static AwesomeEnum detachFrom(Intent intent) {
    if(!intent.hasExtra(name)) throw new IllegalStateException();
    return values()[intent.getIntExtra(name, -1)];
  }
}

Usage:

// Sender usage
AwesomeEnum.SOMETHING.attachTo(intent);
// Receiver usage
AwesomeEnum result = AwesomeEnum.detachFrom(intent);

OPTION 2: (generic, reusable and decoupled from the enum)

public final class EnumUtil {
    public static class Serializer<T extends Enum<T>> extends Deserializer<T> {
        private T victim;
        @SuppressWarnings("unchecked") 
        public Serializer(T victim) {
            super((Class<T>) victim.getClass());
            this.victim = victim;
        }
        public void to(Intent intent) {
            intent.putExtra(name, victim.ordinal());
        }
    }
    public static class Deserializer<T extends Enum<T>> {
        protected Class<T> victimType;
        protected String name;
        public Deserializer(Class<T> victimType) {
            this.victimType = victimType;
            this.name = victimType.getName();
        }
        public T from(Intent intent) {
            if (!intent.hasExtra(name)) throw new IllegalStateException();
            return victimType.getEnumConstants()[intent.getIntExtra(name, -1)];
        }
    }
    public static <T extends Enum<T>> Deserializer<T> deserialize(Class<T> victim) {
        return new Deserializer<T>(victim);
    }
    public static <T extends Enum<T>> Serializer<T> serialize(T victim) {
        return new Serializer<T>(victim);
    }
}

Usage:

// Sender usage
EnumUtil.serialize(AwesomeEnum.Something).to(intent);
// Receiver usage
AwesomeEnum result = 
EnumUtil.deserialize(AwesomeEnum.class).from(intent);

OPTION 3 (with Kotlin):

It's been a while, but since now we have Kotlin, I thought I would add another option for the new paradigm. Here we can make use of extension functions and reified types (which retains the type when compiling).

inline fun <reified T : Enum<T>> Intent.putExtra(victim: T): Intent =
    putExtra(T::class.java.name, victim.ordinal)

inline fun <reified T: Enum<T>> Intent.getEnumExtra(): T? =
    getIntExtra(T::class.java.name, -1)
        .takeUnless { it == -1 }
        ?.let { T::class.java.enumConstants[it] }

There are a few benefits of doing it this way.

  • We don't require the "overhead" of an intermediary object to do the serialization as it's all done in place thanks to inline which will replace the calls with the code inside the function.
  • The functions are more familiar as they are similar to the SDK ones.
  • The IDE will autocomplete these functions which means there is no need to have previous knowledge of the utility class.

One of the downsides is that, if we change the order of the Emums, then any old reference will not work. This can be an issue with things like Intents inside pending intents as they may survive updates. However, for the rest of the time, it should be ok.

It's important to note that other solutions, like using the name instead of the position, will also fail if we rename any of the values. Although, in those cases, we get an exception instead of the incorrect Enum value.

Usage:

// Sender usage
intent.putExtra(AwesomeEnum.SOMETHING)
// Receiver usage
val result = intent.getEnumExtra<AwesomeEnum>()
Merlynmermaid answered 17/3, 2012 at 19:51 Comment(12)
+1 for pointing out that it's a "real bad idea" to make them application wide.Fifteenth
I actually did work on a project where I just didn't want to deal with serialization or bundling objects (lots of objects with a lot of variables in them), and using static global variables was fine... until a teammate got onto the project. The cost of trying to coordinate the use of those globals made me go "screw it. I'm writing a code generator to make me some Parcelables". The number of bugs dropped significantlyLeucocytosis
Furthermore, on the 'complicated' examples there is no external constant or casting so could be considered more convenient. One disadvantage is that you can only put one of the values in the enumeration. But the question was about keeping a state and generally we use one of them for these cases. There is room for improvement, like adding a cache to the Serializer but I think they are valid alternatives.Merlynmermaid
The Android docs actually suggest avoiding Parcel.writeSerializable due to extremely large overhead. Just something to keep in mind..Tank
@Tank Yeah, it's an understandable suggestion, but in most cases this can be qualified as premature optimisation unless you are parsing thousands of enums (which by nature they should be only a few given that they are used to handle state) On a Nexus 4 you get 1ms improvement (developerphil.com/parcelable-vs-serializable) not sure it's worth the extra leg work, but then again you have the other alternatives I suggested ;)Merlynmermaid
In Kotlin enum don't seem to be serialisable.Dunleavy
@Dunleavy Under the hood Kotlin cross compiles enum class types to a plain Java enum. I guess the easier workaround is to make the enum class implement Serializable: enum class AwesomeEnum : Serializable { A, B, C } Not ideal, but should work.Merlynmermaid
I'm having a hard time understanding, if the first is correct, why bother with the rest? public enum myEnum {A, B} and doing i.putExtra("myenum",myEnum.A); + myEnum num = (myEnum)i.getSerializableExtra("myenum"); this is all you need right? or do I need public enum myEnum implements Serializable {A, B} ?Superlative
@Superlative as with any good answer, there is a "it depends". There is no need to extend Serialisable. The initial answer is valid. Those extra options are in case that you have a case where there may be a bottle neck, like if you are deserialising millions of records (hope not). Use what you see fit...Merlynmermaid
+1. The Kotlin solution has a bit of problem for pending intents. Say unknowingly the order is changed (in the next iteration of the app) when the enum is de-serialized the caller will receive incorrect enum. So please add that note.Fashoda
That's not coupling up your app, that's regular over engineering. All other options are still wide public stuff.Inexhaustible
Absolute magic, simplest answer, worked OOB. AwesomeEnum result = (AwesomeEnum) intent.getSerializableExtra("AwesomeEnum"); Stun
I
114

You can make your enum implement Parcelable which is quite easy for enums:

public enum MyEnum implements Parcelable {
    VALUE;


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(final Parcel dest, final int flags) {
        dest.writeInt(ordinal());
    }

    public static final Creator<MyEnum> CREATOR = new Creator<MyEnum>() {
        @Override
        public MyEnum createFromParcel(final Parcel source) {
            return MyEnum.values()[source.readInt()];
        }

        @Override
        public MyEnum[] newArray(final int size) {
            return new MyEnum[size];
        }
    };
}

You can then use Intent.putExtra(String, Parcelable).

UPDATE: Please note wreckgar's comment that enum.values() allocates a new array at each call.

UPDATE: Android Studio features a live template ParcelableEnum that implements this solution. (On Windows, use Ctrl+J)

Ibnrushd answered 21/9, 2011 at 9:48 Comment(9)
It is also possible enum's toString() and valueOf() methods instead of ordinals.Khachaturian
That's true but why would you do that? For me the concept of Parcelables is a very cheap way of serialization. Using the ordinals for serialization is the cheapest way I can imagine regarding performance and memory consumption. If you don't care about efficiency you could also go with Serializable.Ibnrushd
Using ordinal() might break, when developer inserting new enum member. Of course, renaming enum member will break name() too. But, developers are more likely to insert new member, instead of renaming, as renaming requires him to re-factor the entire project.Radke
I don't agree that additional (or reordered) enum values are more likely than renamed ones. Using a sophisticated IDE like IntelliJ IDEA refactoring is not a big deal. But your point is still good: you must make sure that the serialization is consistent throughout any coexistend implementation. That's true for any kind of serialisation. I presume in most cases parcelables are passed around whithin one app where only one implementation exists, so that should not be an issue.Ibnrushd
values() creates a new array on every call, so best to cache it in eg. a private static arrayCharley
The fact that in general cases (such as db storage), ordinal() is not safe is not relevant for Android parcels. Parcels are not for long-term (persistent) storage. They die with the app. So when you add/rename an enum, you'll get new parcels.Cheng
Important notes: readFromParcel should be empty or not change any fields, writeToParcel should only write once a unique value (int, string, etc.) that will be used to parse enum constant later, there is no need for private constructor which has Parcel parameter and createFromParcel should return the enum constant which parsed from the written unique value. You don't have to use ordinal().Immaculate
This is a problem in case of pending intents (say the order is changed).Fashoda
@GurupadMamadapur implementation changes may be an issue for any kind of serialization. It's not a problem specific to this solution. I didn't investigate it, but I hope pending intents will be discarded during reinstallation of an app. If multiple apps are involved (or even if not) you proably shouldn't use parcelables for intent communication but stick to the system solutions using strings and ints and so on.Ibnrushd
T
28

You can pass an enum through as a string.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Given strings are supported you should be able to pass the value of the enum around with no problem.

Terreverte answered 9/10, 2012 at 15:29 Comment(1)
The simplest implementation of them all.Somato
F
22

For passing an enum by intent, you can convert enum into integer.

Ex:

public enum Num{A ,B}

Sending(enum to integer):

Num send = Num.A;
intent.putExtra("TEST", send.ordinal());

Receiving(integer to enum):

Num rev;
int temp = intent.getIntExtra("TEST", -1);
if(temp >= 0 && temp < Num.values().length)
    rev = Num.values()[temp];

Best regards. :)

Feticide answered 5/8, 2011 at 9:58 Comment(2)
or you can send it as a string (so it is readable) with Num.A.name() and then get it back using Num.ValueOf(intent.getStringExtra("TEST"))Knawel
I think Benoit's way is more secure, since temp.ordinal() is not preferred in practice because ordinal() value may change. See this post: #2836756Alicyclic
B
15

If you really need to, you could serialize an enum as a String, using name() and valueOf(String), as follows:

 class Example implements Parcelable { 
   public enum Foo { BAR, BAZ }

   public Foo fooValue;

   public void writeToParcel(Parcel dest, int flags) {
      parcel.writeString(fooValue == null ? null : fooValue.name());
   }

   public static final Creator<Example> CREATOR = new Creator<Example>() {
     public Example createFromParcel(Parcel source) {        
       Example e = new Example();
       String s = source.readString(); 
       if (s != null) e.fooValue = Foo.valueOf(s);
       return e;
     }
   }
 }

This obviously doesn't work if your enums have mutable state (which they shouldn't, really).

Bedfordshire answered 20/2, 2011 at 0:10 Comment(0)
H
4

It may be possible to make your Enum implement Serializable then you can pass it via the Intent, as there is a method for passing it as a serializable. The advice to use int instead of enum is bogus. Enums are used to make your code easier to read and easier to maintain. It would a large step backwards into the dark ages to not be able to use Enums.

Hyperborean answered 6/7, 2011 at 0:47 Comment(1)
Any Enum type extends Enum superclass by default, which already implements Serializable.Vomitory
A
3

Most of the answers that are using Parcelable concept here are in Java code. It is easier to do it in Kotlin.

Just annotate your enum class with @Parcelize and implement Parcelable interface.

@Parcelize
enum class ViewTypes : Parcelable {
TITLE, PRICES, COLORS, SIZES
}
Absorber answered 31/12, 2019 at 10:0 Comment(0)
H
2

about Oderik's post:

You can make your enum implement Parcelable which is quite easy for enums:

public enum MyEnum implements Parcelable { ... } You can than use Intent.putExtra(String, Parcelable).

If you define a MyEnum variable myEnum, then do intent.putExtra("Parcelable1", myEnum), you will get a "The method putExtra(String, Parcelable) is ambiguous for the type Intent" error message. because there is also a Intent.putExtra(String, Parcelable) method, and original 'Enum' type itself implements the Serializable interface, so compiler does not know choose which method(intent.putExtra(String, Parcelable/or Serializable)).

Suggest that remove the Parcelable interface from MyEnum, and move the core code into wrap class' Parcelable implementation, like this(Father2 is a Parcelable and contain an enum field):

public class Father2 implements Parcelable {

AnotherEnum mAnotherEnum;
int mField;

public Father2(AnotherEnum myEnum, int field) {
    mAnotherEnum = myEnum;
    mField = field;
}

private Father2(Parcel in) {
    mField = in.readInt();
    mAnotherEnum = AnotherEnum.values()[in.readInt()];
}

public static final Parcelable.Creator<Father2> CREATOR = new Parcelable.Creator<Father2>() {

    public Father2 createFromParcel(Parcel in) {
        return new Father2(in);
    }

    @Override
    public Father2[] newArray(int size) {
        return new Father2[size];
    }

};

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(mField);
    dest.writeInt(mAnotherEnum.ordinal());
}

}

then we can do:

AnotherEnum anotherEnum = AnotherEnum.Z;
intent.putExtra("Serializable2", AnotherEnum.X);   
intent.putExtra("Parcelable2", new Father2(AnotherEnum.X, 7));
Harebell answered 23/12, 2012 at 2:2 Comment(3)
You can explicitly pick the correct signature by "casting" the argument, for example intent.putExtra("myEnum", (Parcelable) enumValue);Ibnrushd
Using ordinal is perfect!Danyel
This a really complicated way of saying bundle.putExtra("key", AnotherEnum.X.ordinal()).Yiyid
F
2

you can use enum constructor for enum to have primitive data type..

public enum DaysOfWeek {
    MONDAY(1),
    TUESDAY(2),
    WEDNESDAY(3),
    THURSDAY(4),
    FRIDAY(5),
    SATURDAY(6),
    SUNDAY(7);

    private int value;
    private DaysOfWeek(int value) {
        this.value = value;
    }

    public int getValue() {
        return this.value;
    }

    private static final SparseArray<DaysOfWeek> map = new SparseArray<DaysOfWeek>();

    static
    {
         for (DaysOfWeek daysOfWeek : DaysOfWeek.values())
              map.put(daysOfWeek.value, daysOfWeek);
    }

    public static DaysOfWeek from(int value) {
        return map.get(value);
    }
}

you can use to pass int as extras then pull it from enum using its value.

Fuddle answered 8/6, 2016 at 2:18 Comment(0)
P
1

I like simple.

  • The Fred activity has two modes -- HAPPY and SAD.
  • Create a static IntentFactory that creates your Intent for you. Pass it the Mode you want.
  • The IntentFactory uses the name of the Mode class as the name of the extra.
  • The IntentFactory converts the Mode to a String using name()
  • Upon entry into onCreate use this info to convert back to a Mode.
  • You could use ordinal() and Mode.values() as well. I like strings because I can see them in the debugger.

    public class Fred extends Activity {
    
        public static enum Mode {
            HAPPY,
            SAD,
            ;
        }
    
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.betting);
            Intent intent = getIntent();
            Mode mode = Mode.valueOf(getIntent().getStringExtra(Mode.class.getName()));
            Toast.makeText(this, "mode="+mode.toString(), Toast.LENGTH_LONG).show();
        }
    
        public static Intent IntentFactory(Context context, Mode mode){
            Intent intent = new Intent();
            intent.setClass(context,Fred.class);
            intent.putExtra(Mode.class.getName(),mode.name());
    
            return intent;
        }
    }
    
Phenylalanine answered 4/6, 2013 at 23:17 Comment(1)
curious.. who calls the IntentFactory? can you elaborate on how a different activity would call Fred and how Fred can insure that the Mode is passed?Vallo
F
1

Use Kotlin Extension Functions

inline fun <reified T : Enum<T>> Intent.putExtra(enumVal: T, key: String? = T::class.qualifiedName): Intent =
    putExtra(key, enumVal.ordinal)

inline fun <reified T: Enum<T>> Intent.getEnumExtra(key: String? = T::class.qualifiedName): T? =
    getIntExtra(key, -1)
        .takeUnless { it == -1 }
        ?.let { T::class.java.enumConstants[it] }

This gives you the flexibility to pass multiple of the same enum type, or default to using the class name.

// Add to gradle
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

// Import the extension functions
import path.to.my.kotlin.script.putExtra
import path.to.my.kotlin.script.getEnumExtra

// To Send
intent.putExtra(MyEnumClass.VALUE)

// To Receive
val result = intent.getEnumExtra<MyEnumClass>()
Flowering answered 25/12, 2019 at 21:52 Comment(0)
S
0

I think your best bet is going to be to convert those lists into something parcelable such as a string (or map?) to get it to the Activity. Then the Activity will have to convert it back to an array.

Implementing custom parcelables is a pain in the neck IMHO so I would avoid it if possible.

Savaii answered 14/5, 2010 at 19:46 Comment(0)
M
0

Consider Following enum ::

public static  enum MyEnum {
    ValueA,
    ValueB
}

For Passing ::

 Intent mainIntent = new Intent(this,MyActivity.class);
 mainIntent.putExtra("ENUM_CONST", MyEnum.ValueA);
 this.startActivity(mainIntent);

To retrieve back from the intent/bundle/arguments ::

 MyEnum myEnum = (MyEnum) intent.getSerializableExtra("ENUM_CONST");
Mohur answered 2/1, 2013 at 17:11 Comment(0)
C
0

If you just want to send an enum you can do something like:

First declare an enum containing some value(which can be passed through intent):

 public enum MyEnum {
    ENUM_ZERO(0),
    ENUM_ONE(1),
    ENUM_TWO(2),
    ENUM_THREE(3);
    private int intValue;

    MyEnum(int intValue) {
        this.intValue = intValue;
    }

    public int getIntValue() {
        return intValue;
    }

    public static MyEnum getEnumByValue(int intValue) {
        switch (intValue) {
            case 0:
                return ENUM_ZERO;
            case 1:
                return ENUM_ONE;
            case 2:
                return ENUM_TWO;
            case 3:
                return ENUM_THREE;
            default:
                return null;
        }
    }
}

Then:

  intent.putExtra("EnumValue", MyEnum.ENUM_THREE.getIntValue());

And when you want to get it:

  NotificationController.MyEnum myEnum = NotificationController.MyEnum.getEnumByValue(intent.getIntExtra("EnumValue",-1);

Piece of cake!

Clearheaded answered 23/12, 2019 at 9:37 Comment(0)
M
-2

Don't use enums. Reason #78 to not use enums. :) Use integers, which can easily be remoted through Bundle and Parcelable.

Momentary answered 15/5, 2010 at 18:23 Comment(9)
I use enums to standardize arbitrary types, normally for more readable switches, etc. Is it really that bad to pass the enum as a serializable and cast it when retrieving from the intent? I get that passing an integer instead of an enum should be safer and faster, but that way I'll never know what is the meaning of that number.Lucchesi
@Momentary - what are the other 77 reasons? ;) Seriously though - there do seem to be many benefits to enum and they're not exactly difficult to 'remote' - any chance you can expand on your reasons against them?Simp
@Momentary Please elaborate. If enums should not be used then remove them from the API.Sophisticate
Enums are part of the Java language specification, so they're a little tough to remove and still have a compliant Java implementation :)Dodge
what about mEnum.ordinal()? it's return the position of the elementDayan
@SaifHamed, no. Enum.ordinal() is not reliable, see @CommonsWare's comment for elaboration.Undertint
The value of Enum.ordinal() is fixed at compile time. The only time that comment applies would be passing data between apps with different versions of the enum, or across app updates that change the order of elements within the enum. That sort of thing is dangerous whenever you use anything that's not a primitive. For passing intents between activities within a single app, Enum.ordinal() should be entirely safe.Clarisaclarise
@naXa that comment has been removed, do you remember what was in it?Yiyid
@TWiStErRob, read IanMcLaird's comment above and try to guess =)Undertint

© 2022 - 2024 — McMap. All rights reserved.