242 votes

Passer un enum ou un objet à travers une intention (la meilleure solution)

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

En fait, j'ai besoin d'un moyen de transmettre ces objets à l'activité à partir d'un Intent. Je peux utiliser addExtras() mais cela nécessite une classe compatible avec Parceable. Je pourrais faire en sorte que mes classes soient sérialisées, mais si je comprends bien, cela ralentit le programme.

Quelles sont mes options ?

Puis-je passer un Enum ?

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

0 votes

Peut-être que quelque chose m'échappe, mais comment un enum est-il lié à une ArrayList ?

622voto

pablisco Points 2478

C'est une vieille question, mais tout le monde oublie de dire que les Enums sont en fait Serializable et peut donc parfaitement être ajouté à un Intent en tant qu'extra. Comme ceci :

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 réellement vos activités à un système de gestion d'état, et c'est difficile à maintenir, à déboguer et à résoudre les problèmes.


ALTERNATIVES :

Un bon point a été relevé 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'enum à un Intent, je propose ces alternatives à la place :

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

Utilisation :

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

Utilisation :

// 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 comme nous avons maintenant Kotlin, j'ai pensé ajouter une autre option pour le nouveau paradigme. Ici, nous pouvons utiliser les fonctions d'extension et les 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 à procéder ainsi.

  • Nous n'avons pas besoin de l'"overhead" d'un objet intermédiaire pour effectuer la sérialisation, car tout est fait en place grâce à la fonction inline qui 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'IDE complétera automatiquement ces fonctions, ce qui signifie qu'il n'est pas nécessaire d'avoir une connaissance préalable de la classe utilitaire.

L'un des inconvénients est que, si nous changeons l'ordre des Emums, toutes les anciennes références ne fonctionneront plus. Cela peut être un problème avec des choses comme les intents à l'intérieur des intents en attente, car ils peuvent survivre aux mises à jour. Cependant, pour le reste du temps, cela devrait être correct.

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

Utilisation :

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

15 votes

+1 pour avoir souligné que c'est une "très mauvaise idée" de les rendre larges d'application.

3 votes

En fait, j'ai travaillé sur un projet pour lequel je ne voulais tout simplement pas m'occuper de la sérialisation ou du regroupement d'objets (beaucoup d'objets contenant beaucoup de variables), et l'utilisation de variables globales statiques me convenait... jusqu'à ce qu'un coéquipier se joigne au projet. Le coût d'essayer de coordonner l'utilisation de ces variables globales m'a fait dire "et puis merde. Je vais écrire un générateur de code pour me faire des Parcelables". Le nombre de bogues a diminué de manière significative

0 votes

En outre, dans les exemples "compliqués", il n'y a pas de constante externe ou de moulage, ce qui peut être considéré comme plus pratique. Un inconvénient est que vous ne pouvez mettre qu'une seule des valeurs dans l'énumération. Mais la question portait sur la conservation d'un état et nous utilisons généralement l'une d'entre elles dans ce cas. Des améliorations sont possibles, comme l'ajout d'un cache au Serializer, mais je pense que ce sont des alternatives valables.

115voto

Oderik Points 908

Vous pouvez faire en sorte que votre enum implémente Parcelable, ce qui est assez facile pour les 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];
        }
    };
}

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

UPDATE : Veuillez noter le commentaire de Wreckgar selon lequel enum.values() alloue un nouveau tableau à chaque appel.

MISE À JOUR : Android Studio propose un modèle en direct ParcelableEnum qui met en œuvre cette solution. (Sous Windows, utilisez Ctrl + J )

3 votes

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

0 votes

C'est vrai, mais pourquoi faire ça ? Pour moi, le concept de Parcelables est un moyen très économique de sérialisation. Utiliser les ordinaux pour la sérialisation est le moyen le moins cher que je puisse imaginer en ce qui concerne les performances et la consommation de mémoire. Si vous ne vous souciez pas de l'efficacité, vous pouvez également opter pour Serializable.

2 votes

L'utilisation de la fonction ordinal() peut être interrompue lorsque le développeur insère un nouveau membre de l'enum. Bien sûr, renommer un membre de l'enum cassera aussi name(). Mais, les développeurs sont plus susceptibles d'insérer un nouveau membre, plutôt que de renommer, car le renommage nécessite de remanier l'ensemble du projet.

26voto

Vous pouvez passer un enum comme une chaîne de caractères.

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 être en mesure de transmettre la valeur de l'enum sans problème.

3 votes

La mise en œuvre la plus simple de toutes.

22voto

TURTLE Points 161

Pour passer un enum par intention, vous pouvez convertir l'enum en integer.

Ex :

public enum Num{A ,B}

Envoi (de l'énum à l'entier) :

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

Réception (entier à enum) :

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

Meilleures salutations. :)

8 votes

Ou vous pouvez l'envoyer sous forme de chaîne de caractères (pour qu'elle soit lisible) avec Num.A.name() et la récupérer en utilisant Num.ValueOf(intent.getStringExtra("TEST")).

1 votes

Je pense que la méthode de Benoit est plus sûre, puisque temp.ordinal() n'est pas préférable en pratique car la valeur de ordinal() peut changer. Voir ce post : stackoverflow.com/questions/2836256/

15voto

Jan Berkel Points 1678

Si vous en avez vraiment besoin, vous pouvez sérialiser un enum comme un String, en utilisant name() y 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 enums ont un état mutable (ce qui ne devrait pas être le cas, en réalité).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X