Passagem de enum ou objeto através de uma intenção (a melhor solução)

221

Eu tenho uma atividade que, quando iniciada, precisa acessar duas ArrayLists diferentes. Ambas as listas são objetos diferentes que eu mesmo criei.

Basicamente, preciso de uma maneira de passar esses objetos para a atividade a partir de um Intent. Eu posso usar addExtras (), mas isso requer uma classe compatível com Parceable. Eu poderia fazer minhas aulas serem passadas serializáveis, mas, pelo que entendi, isso atrasa o programa.

Quais são as minhas opções?

Posso passar um Enum?

Como um aparte: existe uma maneira de passar parâmetros para um Construtor de Atividades a partir de uma Intenção?

jax
fonte
Talvez esteja faltando alguma coisa, mas como uma enumeração está relacionada a um ArrayList?
Martin Konecny

Respostas:

558

Esta é uma pergunta antiga, mas todo mundo deixa de mencionar que as Enums são realmente Serializablee, portanto, podem ser adicionadas perfeitamente a uma Intenção como um extra. Como isso:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

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

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

A sugestão de usar variáveis ​​estáticas ou de todo o aplicativo é uma péssima idéia. Isso realmente associa suas atividades a um sistema de gerenciamento de estado e é difícil manter, depurar e vincular problemas.


ALTERNATIVAS:

Um bom ponto foi observado por tedzyc sobre o fato de que a solução fornecida pela Oderik gera um erro. No entanto, a alternativa oferecida é um pouco complicada de usar (mesmo usando genéricos).

Se você está realmente preocupado com o desempenho de adicionar a enum a uma intenção, proponho essas alternativas:

OPÇÃO 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);

OPÇÃO 2: (genérica, reutilizável e dissociada da enumeração)

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

OPÇÃO 3 (com Kotlin):

Já faz um tempo, mas como agora temos o Kotlin, pensei em adicionar outra opção para o novo paradigma. Aqui, podemos usar funções de extensão e tipos reificados (que mantêm o tipo ao 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] }

Existem alguns benefícios em fazê-lo dessa maneira.

  • Não exigimos que a "sobrecarga" de um objeto intermediário faça a serialização, pois tudo é feito no local, graças ao inlinequal substituirá as chamadas pelo código dentro da função.
  • As funções são mais familiares, pois são semelhantes às do SDK.
  • O IDE completará automaticamente essas funções, o que significa que não há necessidade de ter conhecimento prévio da classe de utilitário.

Uma das desvantagens é que, se mudarmos a ordem dos Emums, qualquer referência antiga não funcionará. Isso pode ser um problema com coisas como Intents dentro de intenções pendentes, pois elas podem sobreviver a atualizações. No entanto, pelo resto do tempo, deve estar ok.

É importante observar que outras soluções, como usar o nome em vez da posição, também falharão se renomearmos qualquer um dos valores. Embora, nesses casos, recebamos uma exceção em vez do valor incorreto do Enum.

Uso:

// Sender usage
intent.putExtra(AwesomeEnum.SOMETHING)
// Receiver usage
val result = intent.getEnumExtra<AwesomeEnum>()
pablisco
fonte
14
+1 por destacar que é uma "péssima idéia" torná-los amplos para o aplicativo.
bugfixr
3
Na verdade, eu trabalhei em um projeto em que não queria lidar com serialização ou agrupamento de objetos (muitos objetos com muitas variáveis), e o uso de variáveis ​​globais estáticas era bom ... até que um colega de equipe entrasse no projeto. O custo de tentar coordenar o uso desses globais me fez "estragar tudo. Estou escrevendo um gerador de código para fazer alguns Parcelables". O número de erros caiu significativamente
Joe Plante
2
@Coeffect Sim, é uma sugestão compreensível, mas na maioria dos casos isso pode ser qualificado como otimização prematura, a menos que você esteja analisando milhares de enumerações (que por natureza devem ser apenas algumas, já que são usadas para lidar com o estado) em um Nexus 4 você começa 1ms melhoria ( developerphil.com/parcelable-vs-serializable ) não tenho certeza vale a pena o trabalho de perna extra, mas mais uma vez você tem as outras alternativas que sugeri;)
pablisco
1
@rgv Sob o capô, o Kotlin cross compila enum classtipos em um Java simples enum. Acho que a solução mais fácil é tornar o enum classimplemento Serializable: enum class AwesomeEnum : Serializable { A, B, C }não é o ideal, mas deve funcionar.
Pablisco 22/0318
1
@ Pierre, como em qualquer boa resposta, existe um "depende". Não há necessidade de estender o Serialisable. A resposta inicial é válida. Essas opções extras são para o caso de você ter um gargalo, como se estivesse desserializando milhões de registros (espero que não). Use o que achar melhor ...
pablisco
114

Você pode fazer seu enum implementar Parcelable, o que é bastante fácil para enumerações:

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

Você pode usar Intent.putExtra (String, Parcelable).

ATUALIZAÇÃO: Observe o comentário de wreckgar que enum.values()aloca uma nova matriz a cada chamada.

ATUALIZAÇÃO: o Android Studio possui um modelo ativo ParcelableEnumque implementa esta solução. (No Windows, use Ctrl+ J)

Oderik
fonte
3
Também é possível os métodos toString () e valueOf () da enumeração, em vez dos ordinais.
Natix
2
O uso de ordinal () pode ser interrompido quando o desenvolvedor inserir um novo membro de enum. Obviamente, renomear membro enum também quebrará name (). Porém, é mais provável que os desenvolvedores insiram um novo membro, em vez de renomear, pois a renomeação exige que ele refaça o fator inteiro do projeto.
Cheok Yan Cheng
2
Não concordo que valores de enum adicionais (ou reordenados) sejam mais prováveis ​​que valores renomeados. Usar um IDE sofisticado como a refatoração IntelliJ IDEA não é grande coisa. Mas o seu argumento ainda é bom: você deve garantir que a serialização seja consistente ao longo de qualquer implementação coexistente. Isso é verdade para qualquer tipo de serialização. Presumo que, na maioria dos casos, os pacotes são passados ​​em um aplicativo em que apenas uma implementação existe, portanto isso não deve ser um problema.
Oderik
2
values ​​() cria uma nova matriz em cada chamada, então é melhor armazená-la em cache, por exemplo. uma matriz estática privada
wreckgar23
2
O fato de que, em casos gerais (como armazenamento em db), ordinal () não é seguro, não é relevante para as parcelas do Android. As encomendas não são para armazenamento a longo prazo (persistente). Eles morrem com o aplicativo. Portanto, quando você adiciona / renomeia uma enumeração, obtém novas parcelas.
noamtm
24

Você pode passar um enum como uma string.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Dadas seqüências de caracteres são suportadas, você deve ser capaz de passar o valor do enum sem nenhum problema.

Cameron Lowell Palmer
fonte
3
A implementação mais simples de todas.
Avi Cohen
22

Para passar uma enum por intenção, você pode converter enum em número inteiro.

Ex:

public enum Num{A ,B}

Enviando (enum para inteiro):

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

Recebendo (inteiro para enum):

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

Cumprimentos. :)

TARTARUGA
fonte
8
ou você pode enviá-lo como uma string (por isso é legível) com Num.A.name () e, em seguida, recuperá-lo usando Num.ValueOf (intent.getStringExtra ( "Test"))
Benoit Jadinon
1
Eu acho que o caminho de Benoit é mais seguro, já que temp.ordinal () não é preferido na prática porque o valor ordinal () pode mudar. Veja esta postagem: stackoverflow.com/questions/2836256/…
Shnkc 12/14/14/13:
15

Se você realmente precisar, pode serializar uma enumeração como uma String, usando name()e valueOf(String), da seguinte maneira:

 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, isso não funciona se seus enums tiverem um estado mutável (o que não deveriam, na verdade).

Jan Berkel
fonte
4

Pode ser possível fazer seu Enum implementar Serializable, então você pode passá-lo através do Intent, pois existe um método para transmiti-lo como serializável. O conselho para usar int em vez de enum é falso. As enums são usadas para facilitar a leitura e a manutenção do código. Seria um grande passo para trás na idade das trevas para não poder usar Enums.

Damien Cooke
fonte
2
Qualquer tipo de Enum estende a superclasse de Enum por padrão, que já implementa Serializable.
Arcao 04/04
2

sobre a publicação de Oderik:

Você pode fazer seu enum implementar Parcelable, o que é bastante fácil para enumerações:

enumeração pública MyEnum implementa Parcelable {...} Você pode usar Intent.putExtra (String, Parcelable).

Se você definir uma variável MyEnum myEnum, faça intent.putExtra ("Parcelable1", myEnum), você receberá uma mensagem de erro "O método putExtra (String, Parcelable) é ambíguo para o tipo Intent". porque também existe um método Intent.putExtra (String, Parcelable) e o próprio tipo 'Enum' implementa a interface Serializable, portanto, o compilador não sabe escolher qual método (intent.putExtra (String, Parcelable / ou Serializable)).

Sugira que remova a interface Parcelable do MyEnum e mova o código principal para a implementação Parcelable da classe wrap, como esta (o Father2 é um Parcelable e contém um 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());
}

}

então nós podemos fazer:

AnotherEnum anotherEnum = AnotherEnum.Z;
intent.putExtra("Serializable2", AnotherEnum.X);   
intent.putExtra("Parcelable2", new Father2(AnotherEnum.X, 7));
tedzyc
fonte
5
Você pode escolher explicitamente a assinatura correta por "Casting" o argumento, por exemplointent.putExtra("myEnum", (Parcelable) enumValue);
Oderik
Usar ordinal é perfeito!
slott
Esta é uma maneira realmente complicada de dizer bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWIStErRob 13/04/19
2

você pode usar o construtor enum para que o enum tenha um tipo de dados 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);
    }
}

você pode usar para passar int como extras e retirá-lo do enum usando seu valor.

niczm25
fonte
2

A maioria das respostas que estão usando o conceito Parcelable aqui está em código Java. É mais fácil fazê-lo no Kotlin.

Apenas anote sua classe enum com @Parcelize e implemente a interface Parcelable.

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

Eu gosto de simples

  • A atividade de Fred tem dois modos - HAPPYe SAD.
  • Crie uma estática IntentFactoryque crie o seu Intentpara você. Passe o Modeque quiser.
  • O IntentFactoryusa o nome da Modeclasse como o nome do extra.
  • O IntentFactoryconverte Modepara um Stringusandoname()
  • Após a entrada em onCreateuso, essas informações serão convertidas novamente em a Mode.
  • Você poderia usar ordinal()e Mode.values()também. Eu gosto de strings porque posso vê-las no 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
fonte
curioso .. quem chama o IntentFactory? você pode explicar como uma atividade diferente chamaria Fred e como Fred pode garantir que o Modo seja aprovado?
21917 erik
0

Acho que sua melhor aposta será converter essas listas em algo que pode ser parcelado, como uma string (ou mapa?) Para levá-la à Atividade. Então a Atividade precisará convertê-lo novamente em uma matriz.

Implementar encomendas personalizadas é uma dor no pescoço IMHO, então eu evitaria se possível.

Brad Hein
fonte
0

Considere a seguir enum ::

public static  enum MyEnum {
    ValueA,
    ValueB
}

Para passar ::

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

Para recuperar de volta a intenção / pacote configurável / argumentos:

 MyEnum myEnum = (MyEnum) intent.getSerializableExtra("ENUM_CONST");
focinho
fonte
0

Se você apenas deseja enviar uma enumeração, pode fazer algo como:

Primeiro, declare uma enum contendo algum valor (que pode ser passado através da intenção):

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

Então:

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

E quando você quiser obtê-lo:

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

Pedaco de bolo!

MHN
fonte
0

Use as funções de extensão 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] }

Isso oferece a flexibilidade de passar vários do mesmo tipo de enumeração ou usar como padrão o nome da 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>()
Gibolt
fonte
-2

Não use enums. Razão # 78 para não usar enumerações. :) Use números inteiros, que podem ser facilmente remotos através de Bundle e Parcelable.

hackbod
fonte
8
@hackbod - quais são as outras 77 razões? ;) Porém, falando sério - parece haver muitos benefícios a enum e não são exatamente difíceis de 'remotamente' - há alguma chance de você expandir suas razões contra eles?
Ostergaard
3
@hackbod Por favor, elabore. Se as enumerações não devem ser usadas, remova-as da API.
dcow
2
Enums são parte da especificação da linguagem Java, por isso eles são um pouco difícil de remover e ainda ter uma implementação Java compatível :)
tad
1
que tal mEnum.ordinal()? é retornar a posição do elemento
Saif Hamed
3
O valor de Enum.ordinal()é fixo no tempo de compilação. O único momento em que esse comentário se aplica seria a transferência de dados entre aplicativos com versões diferentes da enumeração ou através de atualizações de aplicativos que alteram a ordem dos elementos na enumeração. Esse tipo de coisa é perigosa sempre que você usa algo que não é primitivo. Para passar intenções entre atividades em um único aplicativo, Enum.ordinal()deve ser totalmente seguro.
Ian McLaird