Passer une énumération ou un objet à travers une intention (la meilleure solution)


221

J'ai une activité qui, lorsqu'elle est démarrée, a besoin d'accéder à deux listes de tableaux différentes. Les deux listes sont des objets différents que j'ai créés moi-même.

Fondamentalement, j'ai besoin d'un moyen de transmettre ces objets à l'activité à partir d'une intention. Je peux utiliser addExtras () mais cela nécessite une classe compatible Parceable. Je pourrais faire que mes cours soient passés en série, mais si je comprends bien, cela ralentit le programme.

Quelles sont mes options?

Puis-je réussir un Enum?

En passant: existe-t-il un moyen de passer des paramètres à un constructeur d'activité à partir d'une intention?


Peut-être que je manque quelque chose, mais comment une énumération est-elle liée à une liste de tableaux?
Martin Konecny

Réponses:


558

C'est une vieille question, mais tout le monde ne mentionne pas que les énumérations sont réellement Serializableet peuvent donc parfaitement être ajoutées à une intention en tant que supplément. Comme ça:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

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

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

La suggestion d'utiliser des variables statiques ou à l'échelle de l'application est une très mauvaise idée. Cela couple vraiment vos activités à un système de gestion d'état, et il est difficile à maintenir, à déboguer et à résoudre.


ALTERNATIVES:

Un bon point a été noté par tedzyc sur le fait que la solution fournie par Oderik vous donne une erreur. Cependant, l'alternative proposée est un peu lourde à utiliser (même en utilisant des génériques).

Si vous êtes vraiment préoccupé par les performances de l'ajout de l'énumération à une intention, je propose plutôt ces alternatives:

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: (générique, réutilisable et découplé de l'énumération)

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 (avec Kotlin):

Cela fait un moment, mais depuis que nous avons Kotlin, j'ai pensé ajouter une autre option pour le nouveau paradigme. Ici, nous pouvons utiliser des fonctions d'extension et des types réifiés (qui conservent le type lors de la compilation).

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

Il y a quelques avantages à le faire de cette façon.

  • Nous n'avons pas besoin de la «surcharge» d'un objet intermédiaire pour effectuer la sérialisation car tout est fait sur place grâce à inlinequoi remplacera les appels par le code à l'intérieur de la fonction.
  • Les fonctions sont plus familières car elles sont similaires à celles du SDK.
  • L'EDI remplira automatiquement ces fonctions, ce qui signifie qu'il n'est pas nécessaire d'avoir une connaissance préalable de la classe d'utilité.

L'un des inconvénients est que, si nous changeons l'ordre des Emums, toute ancienne référence ne fonctionnera pas. Cela peut être un problème avec des choses comme les intentions dans les intentions en attente car elles peuvent survivre aux mises à jour. Cependant, pour le reste du temps, ça devrait aller.

Il est important de noter que d'autres solutions, comme l'utilisation du nom au lieu de la position, échoueront également si nous renommons l'une des valeurs. Bien que, dans ces cas, nous obtenions une exception au lieu de la valeur Enum incorrecte.

Usage:

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

14
+1 pour avoir fait remarquer que c'est une "vraie mauvaise idée" de les étendre à l'ensemble de l'application.
bugfixr

3
En fait, j'ai travaillé sur un projet où je ne voulais tout simplement pas traiter de sérialisation ou de regroupement d'objets (beaucoup d'objets avec beaucoup de variables), et utiliser des variables globales statiques était bien ... jusqu'à ce qu'un coéquipier monte sur le projet. Le coût d'essayer de coordonner l'utilisation de ces globaux m'a fait aller "foirer. J'écris un générateur de code pour me faire des Parcelables". Le nombre de bugs a chuté de manière significative
Joe Plante

2
@Coeffect Oui, c'est une suggestion compréhensible, mais dans la plupart des cas, cela peut être qualifié d'optimisation prématurée, sauf si vous analysez des milliers d'énumérations (qui, par nature, ne devraient être que quelques-unes étant donné qu'elles sont utilisées pour gérer l'état) Sur un Nexus 4 vous obtenez une amélioration de 1 ms ( developerphil.com/parcelable-vs-serializable ) pas sûr que cela vaut le travail supplémentaire pour les jambes, mais là encore vous avez les autres alternatives que j'ai suggérées;)
pablisco

1
@rgv Sous le capot, Kotlin cross compile les enum classtypes en Java simple enum. Je suppose que la solution de contournement la plus simple consiste à rendre l' enum classimplémentation Serializable: enum class AwesomeEnum : Serializable { A, B, C }pas idéale, mais devrait fonctionner.
pablisco

1
@Pierre comme pour toute bonne réponse, il y a un "ça dépend". Il n'est pas nécessaire d'étendre Serialisable. La réponse initiale est valable. Ces options supplémentaires sont au cas où vous auriez un cas où il pourrait y avoir un goulot d'étranglement, comme si vous désérialisiez des millions d'enregistrements (espérons que non). Utilisez ce que vous voulez ...
pablisco

114

Vous pouvez faire en sorte que votre énumération implémente Parcelable, ce qui est assez facile pour les énumérations:

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

Vous pouvez ensuite utiliser Intent.putExtra (String, Parcelable).

MISE À JOUR: Veuillez noter le commentaire de wreckgar qui enum.values()alloue un nouveau tableau à chaque appel.

MISE À JOUR: Android Studio propose un modèle en direct ParcelableEnumqui implémente cette solution. (Sous Windows, utilisez Ctrl+ J)


3
Il est également possible d'utiliser les méthodes toString () et valueOf () de l'énumération au lieu des ordinaux.
Natix

2
L'utilisation de ordinal () peut être interrompue lorsque le développeur insère un nouveau membre enum. Bien sûr, renommer un membre enum cassera également name (). Mais, les développeurs sont plus susceptibles d'insérer un nouveau membre, au lieu de renommer, car le changement de nom l'oblige à re-factoriser l'ensemble du projet.
Cheok Yan Cheng

2
Je ne suis pas d'accord pour dire que des valeurs d'énumération supplémentaires (ou réorganisées) sont plus susceptibles que celles renommées. L'utilisation d'un IDE sophistiqué comme le refactoring IntelliJ IDEA n'est pas un gros problème. Mais votre argument est toujours valable: vous devez vous assurer que la sérialisation est cohérente dans toute implémentation de coexistence. C'est vrai pour tout type de sérialisation. Je suppose que dans la plupart des cas, les parcelles sont transmises dans une seule application où il n'y a qu'une seule implémentation, donc cela ne devrait pas être un problème.
Oderik

2
values ​​() crée un nouveau tableau à chaque appel, il est donc préférable de le mettre en cache par exemple. un tableau statique privé
wreckgar23

2
Le fait que dans des cas généraux (tels que le stockage db), ordinal () n'est pas sûr n'est pas pertinent pour les colis Android. Les colis ne sont pas destinés à un stockage à long terme (persistant). Ils meurent avec l'application. Ainsi, lorsque vous ajoutez / renommez une énumération, vous obtenez de nouvelles parcelles.
noamtm

24

Vous pouvez passer une énumération sous forme de chaîne.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Étant donné que les chaînes sont prises en charge, vous devriez pouvoir transmettre la valeur de l'énumération sans problème.


3
La mise en œuvre la plus simple de tous.
Avi Cohen

22

Pour passer une énumération par intention, vous pouvez convertir l'énumération en entier.

Ex:

public enum Num{A ,B}

Envoi (enum à entier):

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

Réception (entier à énumérer):

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

Meilleures salutations. :)


8
ou vous pouvez l'envoyer sous forme de chaîne (afin qu'elle soit lisible) avec Num.A.name (), puis la récupérer à l'aide de Num.ValueOf (intent.getStringExtra ("TEST"))
Benoit Jadinon

1
Je pense que le chemin de Benoit est plus sûr, car temp.ordinal () n'est pas préféré en pratique car la valeur ordinal () peut changer. Voir cet article: stackoverflow.com/questions/2836256/…
Shnkc

15

Si vous en avez vraiment besoin, vous pouvez sérialiser une énumération en tant que chaîne, en utilisant name()et valueOf(String), comme suit:

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

Cela ne fonctionne évidemment pas si vos énumérations ont un état mutable (ce qu'elles ne devraient pas vraiment).


4

Il peut être possible de rendre votre Enum implémentation Serializable puis vous pouvez le passer via l'Intent, car il existe une méthode pour le passer en tant que sérialisable. Le conseil d'utiliser int au lieu d'enum est faux. Les énumérations sont utilisées pour faciliter la lecture et la maintenance de votre code. Ce serait un grand pas en arrière dans les âges sombres de ne pas pouvoir utiliser Enums.


2
Tout type Enum étend la superclasse Enum par défaut, qui implémente déjà Serializable.
Arcao

2

à propos du message d'Oderik:

Vous pouvez faire en sorte que votre énumération implémente Parcelable, ce qui est assez facile pour les énumérations:

public enum MyEnum implémente Parcelable {...} Vous pouvez alors utiliser Intent.putExtra (String, Parcelable).

Si vous définissez une variable MyEnum myEnum, puis faites intention.putExtra ("Parcelable1", myEnum), vous obtiendrez un message d'erreur "La méthode putExtra (String, Parcelable) est ambiguë pour le type Intent". car il existe également une méthode Intent.putExtra (String, Parcelable) et le type `` Enum '' lui-même implémente l'interface Serializable, de sorte que le compilateur ne sait pas quelle méthode (intention.putExtra (String, Parcelable / ou Serializable)).

Suggérez de supprimer l'interface Parcelable de MyEnum et de déplacer le code principal dans l'implémentation Parcelable de la classe wrap, comme ceci (Father2 est un Parcelable et contient un champ 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());
}

}

alors nous pouvons faire:

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

5
Vous pouvez explicitement choisir la signature correcte en "coulant" l'argument, par exempleintent.putExtra("myEnum", (Parcelable) enumValue);
Oderik

Utiliser l'ordinal est parfait!
slott

C'est une façon très compliquée de dire bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWiStErRob

2

vous pouvez utiliser le constructeur enum pour qu'enum ait un type de données primitif.

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

vous pouvez utiliser pour passer int en tant qu'extras, puis le retirer d'Enum en utilisant sa valeur.


2

La plupart des réponses qui utilisent le concept Parcelable ici sont en code Java. Il est plus facile de le faire à Kotlin.

Annotez simplement votre classe enum avec @Parcelize et implémentez l'interface Parcelable.

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

1

J'aime simple.

  • L'activité Fred a deux modes - HAPPYet SAD.
  • Créez une statique IntentFactoryqui crée votre Intentpour vous. Passez-le comme Modevous le souhaitez.
  • Le IntentFactoryutilise le nom de la Modeclasse comme nom de l'extra.
  • Le IntentFactoryconvertit le Modeen un Stringutilisantname()
  • Lors de l'entrée en onCreateservice, utilisez ces informations pour les reconvertir en a Mode.
  • Vous pouvez également utiliser ordinal()et Mode.values(). J'aime les chaînes parce que je peux les voir dans le débogueur.

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

curieux .. qui appelle l'IntentFactory? pouvez-vous expliquer comment une activité différente appellerait Fred et comment Fred peut s'assurer que le mode est passé?
erik

0

Je pense que votre meilleur pari sera de convertir ces listes en quelque chose de parcellaire comme une chaîne (ou une carte?) Pour le faire parvenir à l'activité. L'activité devra ensuite la reconvertir en tableau.

La mise en œuvre de parcelles personnalisées est une douleur dans le cou à mon humble avis, donc je l'éviterais si possible.


0

Pensez à suivre l'énumération ::

public static  enum MyEnum {
    ValueA,
    ValueB
}

Pour passer ::

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

Pour récupérer à partir de l'intention / bundle / arguments ::

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

0

Si vous voulez simplement envoyer une énumération, vous pouvez faire quelque chose comme:

Déclarez d'abord une énumération contenant une valeur (qui peut être transmise par intention):

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

Ensuite:

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

Et quand vous voulez l'obtenir:

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

Part de gâteau!


0

Utiliser les fonctions d'extension 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] }

Cela vous donne la possibilité de passer plusieurs du même type d'énumération, ou par défaut d'utiliser le nom de 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

N'utilisez pas d'énumérations. Raison n ° 78 de ne pas utiliser d'énumérations. :) Utilisez des entiers, qui peuvent facilement être déportés via Bundle et Parcelable.


8
@hackbod - quelles sont les 77 autres raisons? ;) Sérieusement cependant - il semble y avoir de nombreux avantages à énumérer et ils ne sont pas exactement difficiles à «éloigner» - avez-vous une chance de développer vos raisons contre eux?
ostergaard

3
@hackbod Veuillez développer. Si les énumérations ne doivent pas être utilisées, supprimez-les de l'API.
dcow

2
Les énumérations font partie de la spécification du langage Java, elles sont donc un peu difficiles à supprimer et ont toujours une implémentation Java conforme :)
tad

1
qu'en est-il mEnum.ordinal()? c'est retourner la position de l'élément
Saif Hamed

3
La valeur de Enum.ordinal()est fixée au moment de la compilation. La seule fois où un commentaire s'applique serait de transmettre des données entre des applications avec différentes versions de l'énumération, ou entre des mises à jour d'application qui modifient l'ordre des éléments dans l'énumération. Ce genre de chose est dangereux lorsque vous utilisez quelque chose qui n'est pas primitif. Pour passer des intentions entre les activités au sein d'une seule application, Enum.ordinal()devrait être entièrement sûr.
Ian McLaird
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.