Pasar enumeración u objeto a través de una intención (la mejor solución)

221

Tengo una actividad que cuando se inicia necesita acceso a dos ArrayLists diferentes. Ambas listas son objetos diferentes que yo mismo he creado.

Básicamente, necesito una forma de pasar estos objetos a la actividad desde un intento. Puedo usar addExtras () pero esto requiere una clase compatible con Parceable. Podría hacer que mis clases se pasen serializables pero, según tengo entendido, esto ralentiza el programa.

¿Cuáles son mis opciones?

¿Puedo pasar una enumeración?

Como comentario: ¿hay alguna manera de pasar parámetros a un Constructor de actividades desde una Intención?

jax
fuente
Tal vez me falta algo, pero ¿cómo se relaciona una enumeración con una ArrayList?
Martin Konecny

Respuestas:

558

Esta es una vieja pregunta, pero todos no mencionan que Enums son realmente Serializabley, por lo tanto, se pueden agregar perfectamente a un Intent como extra. Me gusta esto:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

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

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

La sugerencia de usar variables estáticas o de toda la aplicación es una muy mala idea. Esto realmente combina sus actividades con un sistema de gestión de estado, y es difícil de mantener, depurar y resolver problemas.


ALTERNATIVAS:

Tedzyc señaló un buen punto sobre el hecho de que la solución proporcionada por Oderik le da un error. Sin embargo, la alternativa ofrecida es un poco engorrosa de usar (incluso con genéricos).

Si realmente le preocupa el rendimiento de agregar la enumeración a una intención, le propongo estas alternativas:

OPCIÓN 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)];
  }
}

Uso:

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

OPCIÓN 2: (genérico, reutilizable y desacoplado de la enumeración)

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);
    }
}

Uso:

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

OPCIÓN 3 (con Kotlin):

Ha pasado un tiempo, pero desde ahora tenemos a Kotlin, pensé que agregaría otra opción para el nuevo paradigma. Aquí podemos hacer uso de funciones de extensión y tipos reified (que retiene el tipo al compilar).

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] }

Hay algunos beneficios de hacerlo de esta manera.

  • No requerimos la "sobrecarga" de un objeto intermediario para realizar la serialización, ya que todo se hace en su lugar gracias a lo inlinecual reemplazará las llamadas con el código dentro de la función.
  • Las funciones son más familiares ya que son similares a las del SDK.
  • El IDE completará automáticamente estas funciones, lo que significa que no es necesario tener conocimientos previos de la clase de utilidad.

Una de las desventajas es que, si cambiamos el orden de los Emums, cualquier referencia anterior no funcionará. Esto puede ser un problema con cosas como Intentos dentro de intentos pendientes, ya que pueden sobrevivir a las actualizaciones. Sin embargo, por el resto del tiempo, debería estar bien.

Es importante tener en cuenta que otras soluciones, como usar el nombre en lugar de la posición, también fallarán si cambiamos el nombre de cualquiera de los valores. Aunque, en esos casos, obtenemos una excepción en lugar del valor incorrecto Enum.

Uso:

// Sender usage
intent.putExtra(AwesomeEnum.SOMETHING)
// Receiver usage
val result = intent.getEnumExtra<AwesomeEnum>()
pablisco
fuente
14
+1 por señalar que es una "idea realmente mala" hacer que su aplicación sea amplia.
corrección de errores el
3
Realmente trabajé en un proyecto en el que simplemente no quería lidiar con la serialización o la agrupación de objetos (muchos objetos con muchas variables en ellos), y usar variables globales estáticas estaba bien ... hasta que un compañero de equipo llegó al proyecto. El costo de tratar de coordinar el uso de esos globales me hizo "joderlo. Estoy escribiendo un generador de código para hacerme algunas Parcelables". El número de errores disminuyó significativamente
Joe Plante
2
@ Coeffect Sí, es una sugerencia comprensible, pero en la mayoría de los casos esto puede calificarse como optimización prematura a menos que esté analizando miles de enumeraciones (que por naturaleza deberían ser solo unas pocas, dado que se usan para manejar el estado) en un Nexus 4 obtienes una mejora de 1 ms ( developerphil.com/parcelable-vs-serializable ) no estoy seguro de que valga la pena el trabajo extra, pero de nuevo tienes las otras alternativas que sugerí;)
pablisco
1
@rgv Bajo el capó Kotlin cross compila enum classtipos en un Java simple enum. Supongo que la solución más fácil es hacer el enum classimplemento Serializable: enum class AwesomeEnum : Serializable { A, B, C }no es ideal, pero debería funcionar.
pablisco 22/03/18
1
@Pierre como con cualquier buena respuesta, hay un "depende". No hay necesidad de extender Serialisable. La respuesta inicial es válida. Esas opciones adicionales son en caso de que tenga un caso en el que pueda haber un cuello de botella, como si está deserializando millones de registros (espero que no). Use lo que mejor le parezca ...
pablisco
114

Puede hacer que su enumeración implemente Parcelable, que es bastante fácil para las enumeraciones:

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];
        }
    };
}

Luego puede usar Intent.putExtra (String, Parcelable).

ACTUALIZACIÓN: tenga en cuenta el comentario de wreckgar que enum.values()asigna una nueva matriz en cada llamada.

ACTUALIZACIÓN: Android Studio presenta una plantilla en vivo ParcelableEnumque implementa esta solución. (En Windows, use Ctrl+ J)

Oderik
fuente
3
También es posible los métodos toString () y valueOf () de enum en lugar de ordinales.
Natix
2
El uso de ordinal () puede romperse cuando el desarrollador inserta un nuevo miembro enum. Por supuesto, cambiar el nombre del miembro enum también romperá name (). Sin embargo, es más probable que los desarrolladores inserten nuevos miembros, en lugar de renombrarlos, ya que el renombrado requiere que vuelva a factorizar todo el proyecto.
Cheok Yan Cheng
2
No estoy de acuerdo en que los valores de enumeración adicionales (o reordenados) sean más probables que los renombrados. El uso de un IDE sofisticado como IntelliJ IDEA refactoring no es un gran problema. Pero su punto sigue siendo bueno: debe asegurarse de que la serialización sea coherente en cualquier implementación de coexistend. Eso es cierto para cualquier tipo de serialización. Supongo que, en la mayoría de los casos, las parcelables se pasan dentro de una aplicación donde solo existe una implementación, por lo que no debería ser un problema.
Oderik
2
values ​​() crea una nueva matriz en cada llamada, por lo que es mejor almacenarla en caché, por ejemplo. una matriz estática privada
wreckgar23
2
El hecho de que, en casos generales (como db storage), ordinal () no sea seguro, no es relevante para las parcelas de Android. Las parcelas no son para el almacenamiento a largo plazo (persistente). Mueren con la aplicación. Entonces, cuando agrega / cambia el nombre de una enumeración, obtendrá nuevas parcelas.
noamtm
24

Puede pasar una enumeración como una cadena.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Las cadenas dadas son compatibles, debería poder pasar el valor de la enumeración sin ningún problema.

Cameron Lowell Palmer
fuente
3
La implementación más simple de todos ellos.
Avi Cohen
22

Para pasar una enumeración por intención, puede convertir la enumeración en entero.

Ex:

public enum Num{A ,B}

Envío (enum a entero):

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

Recibiendo (entero a enum):

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

Atentamente. :)

TORTUGA
fuente
8
o puede enviarlo como una cadena (para que sea legible) con Num.A.name () y luego recuperarlo usando Num.ValueOf (intent.getStringExtra ("TEST"))
Benoit Jadinon
1
Creo que el camino de Benoit es más seguro, ya que temp.ordinal () no se prefiere en la práctica porque el valor ordinal () puede cambiar. Ver esta publicación: stackoverflow.com/questions/2836256/…
Shnkc
15

Si realmente lo necesita, puede serializar una enumeración como una Cadena, usando name()y valueOf(String), de la siguiente manera:

 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;
     }
   }
 }

Obviamente, esto no funciona si sus enumeraciones tienen un estado mutable (que en realidad no deberían).

Jan Berkel
fuente
4

Es posible hacer que su Enum implemente Serializable, luego puede pasarlo a través de Intent, ya que hay un método para pasarlo como serializable. El consejo de usar int en lugar de enum es falso. Las enumeraciones se utilizan para hacer que su código sea más fácil de leer y más fácil de mantener. Sería un gran paso atrás en la edad oscura no poder usar Enums.

Damien Cooke
fuente
2
Cualquier tipo de Enum extiende la superclase de Enum de forma predeterminada, que ya implementa Serializable.
Arcao
2

sobre la publicación de Oderik:

Puede hacer que su enumeración implemente Parcelable, que es bastante fácil para las enumeraciones:

public enum MyEnum implementa Parcelable {...} Puede usar Intent.putExtra (String, Parcelable).

Si define una variable MyEnum myEnum, luego haga intent.putExtra ("Parcelable1", myEnum), obtendrá un mensaje de error "El método putExtra (String, Parcelable) es ambiguo para el tipo Intent". porque también hay un método Intent.putExtra (String, Parcelable), y el tipo original 'Enum' implementa la interfaz serializable, por lo que el compilador no sabe elegir qué método (intent.putExtra (String, Parcelable / o Serializable)).

Sugiera que elimine la interfaz Parcelable de MyEnum y mueva el código central a la implementación Parcelable de la clase wrap, de esta manera (Father2 es Parcelable y contiene un campo enum):

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());
}

}

entonces podemos hacer:

AnotherEnum anotherEnum = AnotherEnum.Z;
intent.putExtra("Serializable2", AnotherEnum.X);   
intent.putExtra("Parcelable2", new Father2(AnotherEnum.X, 7));
tedzyc
fuente
55
Puede elegir explícitamente la firma correcta "lanzando" el argumento, por ejemplointent.putExtra("myEnum", (Parcelable) enumValue);
Oderik
¡Usar ordinal es perfecto!
slott
Esta es una forma realmente complicada de decir bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWiStErRob
2

puede usar el constructor enum para que enum tenga un tipo de datos primitivo.

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);
    }
}

puede usar para pasar int como extras y luego extraerlo de enum usando su valor.

niczm25
fuente
2

La mayoría de las respuestas que utilizan el concepto Parcelable aquí están en código Java. Es más fácil hacerlo en Kotlin.

Simplemente anote su clase de enumeración con @Parcelize e implemente la interfaz Parcelable.

@Parcelize
enum class ViewTypes : Parcelable {
TITLE, PRICES, COLORS, SIZES
}
Ramakrishna Joshi
fuente
1

Me gusta simple

  • La actividad de Fred tiene dos modos: HAPPYy SAD.
  • Cree una estática IntentFactoryque cree su Intentpara usted. Pásalo como Modequieras.
  • El IntentFactoryutiliza el nombre de laMode clase como el nombre de la extra.
  • El IntentFactoryconvierte el Modea un Stringusoname()
  • Al entrar en onCreateuso esta información para volver a convertir a Mode.
  • Podrías usar ordinal()y Mode.values()también. Me gustan las cadenas porque puedo verlas en el depurador.

    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;
        }
    }
JohnnyLambada
fuente
curioso .. quien llama a la IntentFactory? ¿Puedes explicar cómo una actividad diferente llamaría a Fred y cómo Fred puede asegurar que se pase el Modo?
erik
0

Creo que su mejor opción será convertir esas listas en algo parcelable, como una cadena (¿o un mapa?) Para llevarlo a la Actividad. Luego, la Actividad tendrá que convertirla nuevamente en una matriz.

Implementar parcelables personalizados es un dolor en el cuello en mi humilde opinión, por lo que lo evitaría si es posible.

Brad Hein
fuente
0

Considere seguir enum ::

public static  enum MyEnum {
    ValueA,
    ValueB
}

Por pasar ::

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

Para recuperar de la intención / paquete / argumentos ::

 MyEnum myEnum = (MyEnum) intent.getSerializableExtra("ENUM_CONST");
murmullo
fuente
0

Si solo desea enviar una enumeración, puede hacer algo como:

Primero declare una enumeración que contenga algún valor (que se puede pasar por intento):

 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;
        }
    }
}

Luego:

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

Y cuando quieras obtenerlo:

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

Pedazo de pastel!

MHN
fuente
0

Utilice las funciones de extensión de Kotlin

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] }

Esto le brinda la flexibilidad de pasar múltiples del mismo tipo de enumeración, o el uso predeterminado del nombre de la clase.

// 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>()
Gibolt
fuente
-2

No uses enumeraciones. Razón # 78 para no usar enumeraciones. :) Use números enteros, que pueden ser remotos fácilmente a través de Bundle y Parcelable.

hackbod
fuente
8
@hackbod: ¿cuáles son las otras 77 razones? ;) En serio, sin embargo, parece que hay muchos beneficios para enumerar y no son exactamente difíciles de "controlar", ¿hay alguna posibilidad de que pueda ampliar sus razones en contra de ellos?
ostergaard
3
@hackbod Por favor, explique. Si no se deben usar enumeraciones, elimínelas de la API.
dcow
2
Las enumeraciones son parte de la especificación del lenguaje Java, por lo que son un poco difícil de eliminar y aún así tener una aplicación Java compatible :)
tad
1
¿qué pasa mEnum.ordinal()? es devolver la posición del elemento
Saif Hamed
3
El valor de Enum.ordinal()se fija en tiempo de compilación. El único momento en que se aplica el comentario sería pasar datos entre aplicaciones con diferentes versiones de la enumeración, o entre actualizaciones de aplicaciones que cambian el orden de los elementos dentro de la enumeración. Ese tipo de cosas es peligroso cuando usas cualquier cosa que no sea primitiva. Para pasar intentos entre actividades dentro de una sola aplicación, Enum.ordinal()debe ser completamente seguro.
Ian McLaird