Passare enum o oggetto attraverso un intento (la migliore soluzione)


221

Ho un'attività che, quando avviata, necessita dell'accesso a due diverse ArrayList. Entrambe le liste sono oggetti diversi che ho creato da solo.

Fondamentalmente ho bisogno di un modo per passare questi oggetti all'attività da un Intento. Posso usare addExtras () ma questo richiede una classe compatibile Parceable. Potrei rendere le mie lezioni passabili serializzabili ma, a quanto ho capito, questo rallenta il programma.

Quali sono le mie opzioni?

Posso passare un Enum?

A parte: c'è un modo per passare parametri a un costruttore di attività da un intento?


Forse mi manca qualcosa, ma in che modo un enum è collegato a una ArrayList?
Martin Konecny,

Risposte:


558

Questa è una vecchia domanda, ma tutti non menzionano che gli Enum sono in realtà Serializablee quindi possono essere perfettamente aggiunti a un Intento come extra. Come questo:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

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

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

Il suggerimento di utilizzare variabili statiche o a livello di applicazione è una pessima idea. Questo abbina davvero le tue attività a un sistema di gestione statale ed è difficile da mantenere, eseguire il debug e limitare i problemi.


ALTERNATIVE:

Tedzyc ha sottolineato un buon punto sul fatto che la soluzione fornita da Oderik ti dà un errore. Tuttavia, l'alternativa offerta è un po 'ingombrante da usare (anche usando i generici).

Se sei davvero preoccupato per la prestazione di aggiungere l'enum a un Intento, propongo invece queste alternative:

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

OPZIONE 2: (generico, riutilizzabile e disaccoppiato dall'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);
    }
}

Uso:

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

OPZIONE 3 (con Kotlin):

È passato un po 'di tempo, ma da allora abbiamo Kotlin, ho pensato di aggiungere un'altra opzione per il nuovo paradigma. Qui possiamo fare uso delle funzioni di estensione e dei tipi reificati (che conserva il tipo durante la compilazione).

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

Ci sono alcuni vantaggi nel farlo in questo modo.

  • Non è necessario il "sovraccarico" di un oggetto intermedio per eseguire la serializzazione poiché è tutto realizzato sul posto grazie al inlinequale sostituirà le chiamate con il codice all'interno della funzione.
  • Le funzioni sono più familiari in quanto simili a quelle dell'SDK.
  • L'IDE completerà automaticamente queste funzioni, il che significa che non è necessario avere una conoscenza precedente della classe di utilità.

Uno degli aspetti negativi è che, se cambiamo l'ordine degli Emum, qualsiasi vecchio riferimento non funzionerà. Questo può essere un problema con cose come gli Intenti all'interno di quelli in sospeso in quanto potrebbero sopravvivere agli aggiornamenti. Tuttavia, per il resto del tempo, dovrebbe essere ok.

È importante notare che anche altre soluzioni, come l'utilizzo del nome anziché della posizione, falliranno se rinominiamo uno dei valori. Sebbene, in quei casi, otteniamo un'eccezione invece del valore Enum errato.

Uso:

// Sender usage
intent.putExtra(AwesomeEnum.SOMETHING)
// Receiver usage
val result = intent.getEnumExtra<AwesomeEnum>()

14
+1 per aver sottolineato che è una "cattiva idea" per renderli più ampi.
bugfixr

3
In realtà ho lavorato su un progetto in cui non volevo occuparmi di serializzazione o raggruppamento di oggetti (molti oggetti con molte variabili al loro interno) e l'uso di variabili globali statiche andava bene ... fino a quando un compagno di squadra non è entrato nel progetto. Il costo del tentativo di coordinare l'uso di quei globi mi ha fatto andare "fanculo. Sto scrivendo un generatore di codice per farmi dei pacchi". Il numero di bug è diminuito in modo significativo
Joe Plante

2
Sì, è un suggerimento comprensibile, ma nella maggior parte dei casi questo può essere qualificato come ottimizzazione prematura a meno che tu non stia analizzando migliaia di enum (che per natura dovrebbero essere solo alcuni dato che sono usati per gestire lo stato) Su un Nexus 4 ottieni un miglioramento di 1 ms ( developerphil.com/parcelable-vs-serializable ) non sei sicuro che valga la pena di lavorare con le gambe, ma poi hai di nuovo le altre alternative che ho suggerito;)
pablisco,

1
@rgv Sotto il cofano la croce di Kotlin compila i enum classtipi in un semplice Java enum. Immagino che la soluzione più semplice sia realizzare l' enum classattrezzo Serializable: enum class AwesomeEnum : Serializable { A, B, C }non ideale, ma dovrebbe funzionare.
pablisco,

1
@Pierre come con qualsiasi buona risposta, c'è un "dipende". Non è necessario estendere Serializzabile. La risposta iniziale è valida Quelle opzioni extra sono nel caso in cui tu abbia un caso in cui potrebbe esserci un collo di bottiglia, come se stai deserializzando milioni di dischi (spero di no). Usa ciò che ritieni opportuno ...
Pablisco,

114

Puoi rendere il tuo enum implementare Parcelable che è abbastanza facile per gli enum:

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

È quindi possibile utilizzare Intent.putExtra (String, Parcelable).

AGGIORNAMENTO: si prega di notare il commento di wreckgar che enum.values()alloca un nuovo array ad ogni chiamata.

AGGIORNAMENTO: Android Studio presenta un modello live ParcelableEnumche implementa questa soluzione. (Su Windows, usa Ctrl+ J)


3
È anche possibile i metodi toString () e valueOf () di enum anziché ordinali.
Natix,

2
L'uso di ordinal () potrebbe interrompersi quando lo sviluppatore inserisce un nuovo membro enum. Naturalmente, anche la ridenominazione del membro enum interromperà anche name (). Tuttavia, è più probabile che gli sviluppatori inseriscano un nuovo membro, invece di rinominarlo, poiché la ridenominazione gli richiede di ricodificare l'intero progetto.
Cheok Yan Cheng

2
Non sono d'accordo sul fatto che valori enum aggiuntivi (o riordinati) siano più probabili di quelli rinominati. L'uso di un IDE sofisticato come il refactoring IntelliJ IDEA non è un grosso problema. Ma il tuo punto è ancora valido: devi assicurarti che la serializzazione sia coerente durante qualsiasi implementazione coesistente. Questo è vero per qualsiasi tipo di serializzazione. Presumo che nella maggior parte dei casi i pacchi vengano trasferiti all'interno di un'app in cui esiste una sola implementazione, quindi non dovrebbe essere un problema.
Oderik,

2
valori () crea un nuovo array ad ogni chiamata, quindi è meglio memorizzarlo nella cache ad es. un array statico privato
wreckgar23

2
Il fatto che in casi generali (come l'archiviazione db), ordinale () non sia sicuro non è rilevante per i pacchi Android. I pacchi non sono destinati allo stoccaggio a lungo termine (persistente). Muoiono con l'app. Quindi quando aggiungi / rinomina un enum, otterrai nuovi pacchi.
Noamtm,

24

Puoi passare un enum attraverso una stringa.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Dato che le stringhe sono supportate, dovresti essere in grado di trasmettere il valore dell'enum senza problemi.


3
L'implementazione più semplice di tutte.
Avi Cohen,

22

Per passare un enum per intento, puoi convertire enum in numero intero.

Ex:

public enum Num{A ,B}

Invio (da enum a intero):

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

Ricezione (intero da enum):

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

I migliori saluti. :)


8
oppure puoi inviarlo come stringa (quindi è leggibile) con Num.A.name () e poi recuperarlo usando Num.ValueOf (intent.getStringExtra ("TEST"))
Benoit Jadinon,

1
Penso che il modo di Benoit sia più sicuro, dal momento che temp.ordinal () non è preferito nella pratica perché il valore ordinale () può cambiare. Vedi questo post: stackoverflow.com/questions/2836256/...
Shnkc

15

Se è davvero necessario, è possibile serializzare un enum come stringa, utilizzando name()e valueOf(String), come segue:

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

Questo ovviamente non funziona se i tuoi enum hanno uno stato mutevole (cosa che non dovrebbero, davvero).


4

Potrebbe essere possibile rendere Serializable implementabile da Enum, quindi passarlo tramite Intent, in quanto esiste un metodo per passarlo come serializzabile. Il consiglio di usare int invece di enum è falso. Gli enum sono usati per rendere il tuo codice più facile da leggere e da mantenere. Sarebbe un grande passo indietro nei secoli bui per non poter usare Enums.


2
Qualsiasi tipo di Enum estende la superclasse Enum per impostazione predefinita, che implementa già Serializable.
Arcao,

2

sul post di Oderik:

Puoi rendere il tuo enum implementare Parcelable che è abbastanza facile per gli enum:

public enum MyEnum implementa Parcelable {...} Puoi usare Intent.putExtra (String, Parcelable).

Se definisci una variabile MyEnum myEnum, quindi esegui intent.putExtra ("Parcelable1", myEnum), otterrai un messaggio di errore "Il metodo putExtra (String, Parcelable) è ambiguo per il tipo Intent". perché esiste anche un metodo Intent.putExtra (String, Parcelable) e il tipo originale 'Enum' implementa l'interfaccia Serializable, quindi il compilatore non sa scegliere quale metodo (intent.putExtra (String, Parcelable / o Serializable)).

Suggerire di rimuovere l'interfaccia Parcelable da MyEnum e spostare il codice principale nell'implementazione parcelable della classe di wrapping, in questo modo (Father2 è un parcelable e 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());
}

}

allora possiamo fare:

AnotherEnum anotherEnum = AnotherEnum.Z;
intent.putExtra("Serializable2", AnotherEnum.X);   
intent.putExtra("Parcelable2", new Father2(AnotherEnum.X, 7));

5
Puoi scegliere esplicitamente la firma corretta "lanciando" l'argomento, ad esempiointent.putExtra("myEnum", (Parcelable) enumValue);
Oderik

L'uso di ordinale è perfetto!
Slott

Questo è un modo davvero complicato di dire bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWiStErRob,

2

puoi usare enum constructor per enum per avere un tipo di dati 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);
    }
}

puoi usare per passare int come extra e poi estrarlo da enum usando il suo valore.


2

La maggior parte delle risposte che utilizzano il concetto Parcelable qui sono in codice Java. È più facile farlo in Kotlin.

Basta annotare la tua classe enum con @Parcelize e implementare l'interfaccia Parcelable.

@Parcelize
enum class ViewTypes : Parcelable {
TITLE, PRICES, COLORS, SIZES
}

1

Mi piace semplice.

  • L'attività di Fred ha due modalità: HAPPYe SAD.
  • Crea uno statico IntentFactoryche crei il tuo Intentper te. Passalo come Modevuoi.
  • Il IntentFactoryutilizza il nome delMode classe come il nome del supplementare.
  • Le IntentFactoryconverte i Modead un Stringutilizzoname()
  • All'entrata in onCreateuso queste informazioni per riconvertire in a Mode.
  • Puoi usare ordinal()e Mode.values()pure. Mi piacciono le stringhe perché posso vederle nel 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;
        }
    }

curioso .. chi chiama IntentFactory? puoi approfondire come una diversa attività chiamerebbe Fred e in che modo Fred può assicurare che la Modalità venga superata?
Erik,

0

Penso che la tua scommessa migliore sarà quella di convertire quegli elenchi in qualcosa di paragonabile come una stringa (o una mappa?) Per portarlo all'attività. Quindi l'attività dovrà riconvertirla in un array.

L'implementazione di pacchi personalizzati è un dolore al collo IMHO quindi lo eviterei se possibile.


0

Considera il seguente enum ::

public static  enum MyEnum {
    ValueA,
    ValueB
}

Per il passaggio ::

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

Per recuperare dall'intento / bundle / argomenti ::

 MyEnum myEnum = (MyEnum) intent.getSerializableExtra("ENUM_CONST");

0

Se vuoi solo inviare un enum puoi fare qualcosa del tipo:

Per prima cosa dichiarare un enum contenente un valore (che può essere passato attraverso l'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;
        }
    }
}

Poi:

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

E quando vuoi ottenerlo:

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

Pezzo di torta!


0

Usa le funzioni di estensione di 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] }

Questo ti dà la flessibilità di passare multipli dello stesso tipo di enum, o di default di usare il nome della classe.

// 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>()

-2

Non usare enum. Motivo # 78 per non usare enumerazioni. :) Usa numeri interi, che possono essere facilmente remotati tramite Bundle e Parcelable.


8
@hackbod - quali sono gli altri 77 motivi? ;) Seriamente però - sembrano esserci molti benefici da enumerare e non sono esattamente difficili da "remoti" - c'è qualche possibilità che tu possa ampliare le tue ragioni contro di loro?
ostergaard,

3
@hackbod Please elaborate. Se non è possibile utilizzare enum, rimuoverli dall'API.
dc

2
Enumerazioni sono parte della specifica del linguaggio Java, quindi sono un po 'difficile da rimuovere e avere ancora un'implementazione compatibile Java :)
tad

1
che dire mEnum.ordinal()? restituisce la posizione dell'elemento
Saif Hamed,

3
Il valore di Enum.ordinal()è fissato al momento della compilazione. L'unica volta che si applica un commento sarebbe il passaggio di dati tra app con versioni diverse dell'enum o tra aggiornamenti di app che modificano l'ordine degli elementi all'interno dell'enum. Questo genere di cose è pericoloso ogni volta che usi qualcosa che non è un primitivo. Per passare gli intenti tra le attività all'interno di una singola app, Enum.ordinal()dovrebbe essere completamente sicuro.
Ian McLaird,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.