Przekazywanie wyliczenia lub obiektu przez zamiar (najlepsze rozwiązanie)

221

Mam aktywność, która po uruchomieniu potrzebuje dostępu do dwóch różnych ArrayLists. Obie listy są różnymi obiektami, które sam stworzyłem.

Zasadniczo potrzebuję sposobu, aby przekazać te obiekty do działania z zamiaru. Mogę użyć addExtras (), ale wymaga to klasy kompatybilnej z Parceable. Mogę sprawić, że moje zajęcia będą przekazywane do postaci szeregowej, ale jak rozumiem, spowalnia to program.

Jakie są moje opcje?

Czy mogę przekazać Enum?

Nawiasem mówiąc: czy istnieje sposób na przekazanie parametrów Konstruktorowi aktywności z zamiaru?

jax
źródło
Może czegoś mi brakuje, ale w jaki sposób wyliczenie jest powiązane z ArrayList?
Martin Konecny

Odpowiedzi:

558

To stare pytanie, ale wszyscy nie wspominają, że Enumy są w rzeczywistości Serializablei dlatego można je idealnie dodać do Intencji jako dodatek. Lubię to:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

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

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

Sugestia użycia zmiennych statycznych lub obejmujących całą aplikację jest naprawdę złym pomysłem. To naprawdę łączy twoje działania z systemem zarządzania stanem i jest trudne do utrzymania, debugowania i problemów.


ALTERNATYWY:

Tedzyc zauważył , że rozwiązanie dostarczone przez Oderik daje błąd. Jednak oferowana alternatywa jest nieco kłopotliwa w użyciu (nawet przy użyciu generycznych).

Jeśli naprawdę martwisz się wydajnością dodawania wyliczenia do zamiaru, proponuję zamiast tego następujące alternatywy:

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

Stosowanie:

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

OPCJA 2: (ogólna, wielokrotnego użytku i oddzielona od wyliczenia)

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

Stosowanie:

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

OPCJA 3 (z Kotlin):

Minęło trochę czasu, ale odkąd mamy Kotlina, pomyślałem, że dodam kolejną opcję dla nowego paradygmatu. Tutaj możemy skorzystać z funkcji rozszerzeń i typów zmienionych (które zachowują typ podczas kompilacji).

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

Jest kilka korzyści z robienia tego w ten sposób.

  • Nie wymagamy „narzutu” obiektu pośredniego, aby wykonać serializację, ponieważ wszystko odbywa się w miejscu, dzięki inlineczemu zastąpi wywołania kodem wewnątrz funkcji.
  • Funkcje są bardziej znane, ponieważ są podobne do tych z zestawu SDK.
  • IDE automatycznie uzupełni te funkcje, co oznacza, że ​​nie ma potrzeby wcześniejszej znajomości klasy użytkowej.

Jedną z wad jest to, że jeśli zmienimy kolejność Emums, żadne stare odniesienie nie będzie działać. Może to być problem z rzeczami takimi jak intencje w toku, ponieważ mogą przetrwać aktualizacje. Jednak przez resztę czasu powinno być w porządku.

Ważne jest, aby pamiętać, że inne rozwiązania, takie jak użycie nazwy zamiast pozycji, również zawiodą, jeśli zmienimy nazwę dowolnej z wartości. Chociaż w takich przypadkach otrzymujemy wyjątek zamiast niepoprawnej wartości Enum.

Stosowanie:

// Sender usage
intent.putExtra(AwesomeEnum.SOMETHING)
// Receiver usage
val result = intent.getEnumExtra<AwesomeEnum>()
pablisco
źródło
14
+1 za wskazanie, że „naprawdę złym pomysłem” jest rozszerzenie ich na aplikacje.
bugfixr
3
Właściwie pracowałem nad projektem, w którym po prostu nie chciałem zajmować się serializacją ani wiązaniem obiektów (wiele obiektów z dużą ilością zmiennych), a używanie statycznych zmiennych globalnych było w porządku ... dopóki kolega z zespołu nie wszedł do projekt. Koszt próby skoordynowania wykorzystania tych globałów spowodował, że poszedłem „pieprzyć to. Piszę generator kodu, żeby zrobić mi paczki”. Liczba błędów znacznie spadła
Joe Plante
2
@Coeffect Tak, jest to zrozumiała sugestia, ale w większości przypadków można ją zakwalifikować jako przedwczesną optymalizację, chyba że analizujesz tysiące wyliczeń (które z natury powinny być tylko kilkoma, ponieważ są używane do obsługi stanu) na Nexusie 4 dostajesz 1ms poprawę ( developerphil.com/parcelable-vs-serializable ), nie jestem pewien, czy jest to warte dodatkowej pracy nóg, ale znowu masz inne alternatywy, które zasugerowałem;)
pablisco
1
@rgv Pod maską Kotlin cross kompiluje enum classtypy na zwykłą Javę enum. Wydaje mi się, że łatwiejszym obejściem jest zrobienie enum classnarzędzia Serializable: enum class AwesomeEnum : Serializable { A, B, C }nie idealne, ale powinno działać.
pablisco
1
@Pierre, jak w przypadku każdej dobrej odpowiedzi, istnieje „to zależy”. Nie ma potrzeby rozszerzania Serializowalnego. Pierwsza odpowiedź jest poprawna. Te dodatkowe opcje są w przypadku, gdy masz przypadek, w którym może być szyjka butelki, na przykład, jeśli deserializujesz miliony rekordów (mam nadzieję, że nie). Użyj tego, co uważasz za stosowne ...
pablisco
114

Możesz zrobić implementację enum Parcelable, co jest dość łatwe dla 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];
        }
    };
}

Następnie możesz użyć Intent.putExtra (String, Parcelable).

AKTUALIZACJA: Proszę zwrócić uwagę na komentarz wreckgara, który enum.values()przydziela nowe tablice przy każdym wywołaniu.

AKTUALIZACJA: Android Studio zawiera szablon na żywo, ParcelableEnumktóry implementuje to rozwiązanie. (W systemie Windows użyj Ctrl+ J)

Oderik
źródło
3
Możliwe są również metody toString () i valueOf () enum zamiast rzędnych.
Natix
2
Użycie ordinal () może się zepsuć, gdy programista wstawia nowy element enum. Oczywiście zmiana nazwy członka enum spowoduje również uszkodzenie name (). Jednak programiści częściej wstawiają nowego członka zamiast zmiany nazwy, ponieważ zmiana nazwy wymaga od niego ponownego uwzględnienia całego projektu.
Cheok Yan Cheng
2
Nie zgadzam się, że dodatkowe (lub zmienione) wartości wyliczeniowe są bardziej prawdopodobne niż te o zmienionej nazwie. Korzystanie ze skomplikowanego IDE, takiego jak refaktoryzacja IntelliJ IDEA, nie jest wielkim problemem. Ale twój punkt widzenia jest nadal dobry: musisz upewnić się, że serializacja jest spójna podczas każdej współistniejącej implementacji. Dotyczy to każdego rodzaju serializacji. Zakładam, że w większości przypadków paczki są przekazywane w jednej aplikacji, w której istnieje tylko jedna implementacja, więc nie powinno to stanowić problemu.
Oderik
2
wartości () tworzy nową tablicę przy każdym wywołaniu, więc najlepiej buforować ją np. prywatna tablica statyczna
wreckgar23,
2
Fakt, że w ogólnych przypadkach (takich jak db storage) funkcja porządkowa () nie jest bezpieczna, nie dotyczy paczek z Androidem. Paczki nie są przeznaczone do przechowywania długoterminowego (trwałego). Umierają wraz z aplikacją. Kiedy dodasz / zmienisz nazwę wyliczenia, otrzymasz nowe paczki.
noamtm
24

Możesz przekazać wyliczenie jako ciąg.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Podane łańcuchy są obsługiwane, powinieneś być w stanie bez problemu przekazać wartość wyliczenia.

Cameron Lowell Palmer
źródło
3
Najprostsza z nich wszystkich.
Avi Cohen
22

Aby przekazać wyliczenie przez zamiar, możesz przekonwertować wyliczenie na liczbę całkowitą.

Dawny:

public enum Num{A ,B}

Wysyłanie (wyliczenie do liczby całkowitej):

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

Odbieranie (liczba całkowita do wyliczenia):

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

Z poważaniem. :)

ŻÓŁW
źródło
8
lub możesz wysłać go jako ciąg znaków (aby był czytelny) za pomocą Num.A.name (), a następnie odzyskać go za pomocą Num.ValueOf (intent.getStringExtra („TEST”))
Benoit Jadinon
1
Myślę, że sposób Benoit jest bardziej bezpieczny, ponieważ temp.ordinal () nie jest preferowany w praktyce, ponieważ wartość ordinal () może się zmienić. Zobacz ten post: stackoverflow.com/questions/2836256/…
Shnkc
15

Jeśli naprawdę potrzebujesz, możesz serializować wyliczenie jako ciąg, używając name()i valueOf(String), w następujący sposób:

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

To oczywiście nie działa, jeśli twoje wyliczenia mają stan zmienny (czego tak naprawdę nie powinny).

Jan Berkel
źródło
4

Może być możliwe, aby Twoje Enum zaimplementowało Serializowalny, a następnie możesz przekazać je poprzez Intent, ponieważ istnieje metoda przekazywania go jako szeregowalnego. Rada używania int zamiast enum jest fałszywa. Wyliczenia służą do ułatwienia odczytu i konserwacji kodu. Byłby duży krok wstecz w ciemne wieki, aby nie móc używać Enums.

Damien Cooke
źródło
2
Każdy typ Enum domyślnie rozszerza nadklasę Enum, która już implementuje Serializable.
Arcao
2

na temat postu Oderika:

Możesz zrobić implementację enum Parcelable, co jest dość łatwe dla enum:

public enum MyEnum implementuje Parcelable {...} Możesz używać Intent.putExtra (String, Parcelable).

Jeśli zdefiniujesz zmienną MyEnum myEnum, a następnie zrób intent.putExtra („Parcelable1”, myEnum), otrzymasz komunikat „Metoda putExtra (String, Parcelable) jest niejednoznaczna dla typu błędu Intent”. ponieważ istnieje również metoda Intent.putExtra (String, Parcelable), a sam oryginalny typ Enum implementuje interfejs Serializable, więc kompilator nie wie, która metoda (intent.putExtra (String, Parcelable / or Serializable)).

Zaproponuj usunięcie interfejsu Parcelable z MyEnum i przeniesienie kodu podstawowego do implementacji klasy opakowywania „Parcelable”, takiej jak ta (Father2 jest Parcelable i zawiera pole 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());
}

}

wtedy możemy zrobić:

AnotherEnum anotherEnum = AnotherEnum.Z;
intent.putExtra("Serializable2", AnotherEnum.X);   
intent.putExtra("Parcelable2", new Father2(AnotherEnum.X, 7));
tedzyc
źródło
5
Możesz wyraźnie wybrać poprawny podpis, „rzutując” argument, na przykładintent.putExtra("myEnum", (Parcelable) enumValue);
Oderik
Używanie porządkowej jest idealne!
slott
To naprawdę skomplikowany sposób powiedzenia bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWiStErRob
2

możesz użyć konstruktora enum dla enum, aby mieć prymitywny typ danych.

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

możesz użyć, aby przekazać int jako dodatki, a następnie wyciągnąć go z wyliczenia, używając jego wartości.

niczm25
źródło
2

Większość odpowiedzi, które używają tutaj koncepcji Parcelable, znajduje się w kodzie Java. Łatwiej jest to zrobić w Kotlinie.

Po prostu adnotuj swoją klasę enum za pomocą @Parcelize i zaimplementuj interfejs Parcelable.

@Parcelize
enum class ViewTypes : Parcelable {
TITLE, PRICES, COLORS, SIZES
}
Ramakrishna Joshi
źródło
1

Lubię proste.

  • Aktywność Fred ma dwa tryby - HAPPYi SAD.
  • Stwórz statyczny, IntentFactoryktóry stworzy Intentdla Ciebie. Podaj to, Modeco chcesz.
  • IntentFactoryUżywa nazwyMode klasy jako nazwa Extra.
  • W IntentFactoryprzekształca Modesię do Stringkorzystanianame()
  • Po wejściu w życie onCreateinformacje te przekonwertować z powrotem na Mode.
  • Można użyć ordinal()i Mode.values()jak dobrze. Lubię łańcuchy, ponieważ widzę je w debuggerze.

    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
źródło
ciekawe .. kto nazywa IntentFactory? czy możesz wyjaśnić, w jaki sposób inna aktywność zadzwoniłaby do Freda i w jaki sposób Fred może zapewnić, że Tryb zostanie przekazany?
erik
0

Myślę, że najlepszym rozwiązaniem będzie przekonwertowanie tych list na paczkowate, takie jak ciąg znaków (lub mapa?), Aby przenieść je do działania. Następnie działanie będzie musiało przekonwertować go z powrotem na tablicę.

Wdrażanie niestandardowych paczek to ból w szyi IMHO, więc w miarę możliwości unikałbym tego.

Brad Hein
źródło
0

Rozważ następujące wyliczenie:

public static  enum MyEnum {
    ValueA,
    ValueB
}

Do zaliczenia:

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

Aby odzyskać z intencji / pakietu / argumentów:

 MyEnum myEnum = (MyEnum) intent.getSerializableExtra("ENUM_CONST");
kaganiec
źródło
0

Jeśli chcesz tylko wysłać wyliczenie, możesz zrobić coś takiego:

Najpierw zadeklaruj wyliczenie zawierające pewną wartość (którą można przekazać celowo):

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

Następnie:

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

A kiedy chcesz to zdobyć:

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

Bułka z masłem!

MHN
źródło
0

Użyj funkcji rozszerzenia 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] }

Daje to elastyczność przekazywania wielokrotności tego samego typu wyliczenia lub domyślnego używania nazwy klasy.

// 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
źródło
-2

Nie używaj enumów. Powód # 78, aby nie używać wyliczeń. :) Używaj liczb całkowitych, które można łatwo zdalnie przekazać za pomocą pakietu i paczki.

hackbod
źródło
8
@hackbod - jakie są pozostałe 77 powodów? ;) Poważnie - wydaje się, że wyliczanie przynosi wiele korzyści i nie jest ich trudno „zdystansować” - czy jest szansa na rozwinięcie swoich argumentów przeciwko nim?
ostergaard
3
@hackbod Proszę opracować. Jeśli nie należy używać wyliczeń, usuń je z interfejsu API.
dcow
2
Wyliczenia są częścią specyfikacji języka Java, więc są trochę trudne do usunięcia i nadal mają zgodną implementację Java :)
tad
1
co mEnum.ordinal()? zwraca pozycję elementu
Saif Hamed
3
Wartość Enum.ordinal()jest ustalana w czasie kompilacji. Jedyny czas, w którym komentarz ma zastosowanie, to przekazywanie danych między aplikacjami z różnymi wersjami wyliczenia lub między aktualizacjami aplikacji, które zmieniają kolejność elementów w wyliczeniu. Tego rodzaju rzeczy są niebezpieczne, ilekroć używasz czegoś, co nie jest prymitywne. Przekazywanie zamiarów między czynnościami w ramach jednej aplikacji Enum.ordinal()powinno być całkowicie bezpieczne.
Ian McLaird