Melewati enum atau objek melalui niat (solusi terbaik)

221

Saya memiliki aktivitas yang ketika dimulai membutuhkan akses ke dua ArrayLists yang berbeda. Kedua Daftar adalah Objek berbeda yang telah saya buat sendiri.

Pada dasarnya saya butuh cara untuk meneruskan objek-objek ini ke aktivitas dari Intent. Saya dapat menggunakan addExtras () tetapi ini membutuhkan kelas Parceable yang kompatibel. Saya bisa membuat kelas saya menjadi serializable tetapi karena saya mengerti ini memperlambat program.

Apa saja pilihan saya?

Bisakah saya lulus Enum?

Sebagai tambahan: apakah ada cara untuk mengirimkan parameter ke Activity Constructor dari Intent?

Jax
sumber
Mungkin saya kehilangan sesuatu, tetapi bagaimana enum terkait dengan ArrayList?
Martin Konecny

Jawaban:

558

Ini adalah pertanyaan lama, tetapi semua orang gagal menyebutkan bahwa Enums sebenarnya Serializabledan karenanya dapat dengan sempurna ditambahkan ke Intent sebagai tambahan. Seperti ini:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

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

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

Saran untuk menggunakan variabel statis atau aplikasi-lebar adalah ide yang sangat buruk. Ini benar-benar pasangan kegiatan Anda ke sistem pengelolaan negara, dan sulit untuk mempertahankan, men-debug dan terikat masalah.


ALTERNATIF:

Poin yang baik dicatat oleh tedzyc tentang fakta bahwa solusi diberikan oleh Oderik memberi Anda kesalahan. Namun, alternatif yang ditawarkan agak rumit untuk digunakan (bahkan menggunakan obat generik).

Jika Anda benar-benar khawatir tentang kinerja menambahkan enum ke Intent, saya mengusulkan alternatif ini sebagai gantinya:

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

Pemakaian:

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

PILIHAN 2: (generik, dapat digunakan kembali, dan dipisahkan dari 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);
    }
}

Pemakaian:

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

OPSI 3 (dengan Kotlin):

Sudah lama, tapi karena sekarang kita memiliki Kotlin, saya pikir saya akan menambahkan opsi lain untuk paradigma baru. Di sini kita dapat menggunakan fungsi ekstensi dan tipe reified (yang mempertahankan tipe ketika kompilasi).

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

Ada beberapa manfaat melakukannya dengan cara ini.

  • Kami tidak memerlukan "overhead" dari objek perantara untuk melakukan serialisasi karena semuanya dilakukan di tempat berkat inline yang akan menggantikan panggilan dengan kode di dalam fungsi.
  • Fungsinya lebih akrab karena mirip dengan yang SDK.
  • IDE akan melengkapi fungsi-fungsi ini secara otomatis yang berarti tidak perlu memiliki pengetahuan sebelumnya tentang kelas utilitas.

Salah satu kelemahannya adalah bahwa, jika kita mengubah urutan Emum, maka referensi lama mana pun tidak akan berfungsi. Ini bisa menjadi masalah dengan hal-hal seperti Maksud di dalam maksud yang tertunda karena mereka dapat bertahan dari pembaruan. Namun, untuk sisa waktu, itu harus ok.

Penting untuk dicatat bahwa solusi lain, seperti menggunakan nama alih-alih posisi, juga akan gagal jika kita mengganti nama salah satu nilai. Meskipun, dalam kasus tersebut, kami mendapatkan pengecualian alih-alih nilai Enum yang salah.

Pemakaian:

// Sender usage
intent.putExtra(AwesomeEnum.SOMETHING)
// Receiver usage
val result = intent.getEnumExtra<AwesomeEnum>()
pablisco
sumber
14
+1 untuk menunjukkan bahwa itu adalah "ide buruk yang sebenarnya" untuk menjadikannya aplikasi yang luas.
bugfixr
3
Saya benar-benar bekerja pada sebuah proyek di mana saya hanya tidak ingin berurusan dengan objek serialisasi atau bundling (banyak objek dengan banyak variabel di dalamnya), dan menggunakan variabel global statis baik-baik saja ... sampai seorang rekan tim naik ke proyek. Biaya mencoba mengoordinasikan penggunaan global itu membuat saya "mengacaukannya. Saya menulis generator kode untuk membuatkan saya beberapa Parcelables". Jumlah bug menurun secara signifikan
Joe Plante
2
@Coffect Ya, ini adalah saran yang bisa dimengerti, tetapi dalam kebanyakan kasus ini dapat dikualifikasikan sebagai optimasi prematur kecuali jika Anda mengurai ribuan enum (yang pada dasarnya hanya sedikit karena mengingat bahwa mereka digunakan untuk menangani keadaan) Pada Nexus 4 Anda mendapatkan peningkatan 1ms ( developerphil.com/parcelable-vs-serializable ) tidak yakin itu sepadan dengan pekerjaan kaki ekstra, tapi sekali lagi Anda memiliki alternatif lain yang saya sarankan;)
pablisco
1
@ rgv Di bawah kap Kotlin mengkompilasi enum classtipe ke Java biasa enum. Saya kira solusi yang lebih mudah adalah dengan membuat enum classimplement Serializable: enum class AwesomeEnum : Serializable { A, B, C }Tidak ideal, tetapi harus bekerja.
pablisco
1
@Pierre seperti dengan jawaban yang bagus, ada "itu tergantung". Tidak perlu memperpanjang Serialisable. Jawaban awal valid. Opsi-opsi tambahan jika Anda memiliki kasus di mana mungkin ada leher botol, seperti jika Anda deserialising jutaan catatan (harap tidak). Gunakan apa yang Anda inginkan ...
pablisco
114

Anda dapat membuat enum mengimplementasikan Parcelable yang cukup mudah untuk 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];
        }
    };
}

Anda kemudian dapat menggunakan Intent.putExtra (String, Parcelable).

UPDATE: Harap perhatikan komentar wreckgar itu enum.values() mengalokasikan array baru di setiap panggilan.

PEMBARUAN: Android Studio menampilkan templat langsung ParcelableEnumyang mengimplementasikan solusi ini. (Di Windows, gunakan Ctrl+ J)

Oderik
sumber
3
Mungkin juga metode toString () dan valueOf () dari enum alih-alih tata cara.
Natix
2
Menggunakan ordinal () mungkin rusak, saat pengembang memasukkan anggota enum baru. Tentu saja, mengganti nama anggota enum akan merusak nama () juga. Tetapi, pengembang lebih cenderung untuk memasukkan anggota baru, daripada mengganti nama, karena penggantian nama mengharuskannya untuk memasukkan kembali faktor keseluruhan proyek.
Cheok Yan Cheng
2
Saya tidak setuju bahwa nilai enum tambahan (atau dipesan ulang) lebih mungkin daripada yang diganti namanya. Menggunakan IDE canggih seperti IntelliJ IDEA refactoring bukan masalah besar. Tetapi poin Anda masih bagus: Anda harus memastikan bahwa serialisasi konsisten di seluruh implementasi coexistend. Itu berlaku untuk segala jenis serialisasi. Saya kira dalam banyak kasus, paket-paket diedarkan di sekitar satu aplikasi di mana hanya satu implementasi yang ada, sehingga seharusnya tidak menjadi masalah.
Oderik
2
values ​​() membuat larik baru pada setiap panggilan, jadi yang terbaik untuk cache misalnya. array statis pribadi
wreckgar23
2
Fakta bahwa dalam kasus umum (seperti penyimpanan db), ordinal () tidak aman tidak relevan untuk paket Android. Paket tidak untuk penyimpanan jangka panjang (persisten). Mereka mati dengan aplikasi. Jadi, ketika Anda menambahkan / mengganti nama enum, Anda akan mendapatkan paket baru.
noamtm
24

Anda dapat melewatkan enum sebagai string.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

String yang diberikan didukung Anda harus dapat melewati nilai enum sekitar tanpa masalah.

Cameron Lowell Palmer
sumber
3
Implementasi yang paling sederhana dari semuanya.
Avi Cohen
22

Untuk melewatkan enum dengan niat, Anda dapat mengubah enum menjadi integer.

Ex:

public enum Num{A ,B}

Mengirim (enum ke integer):

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

Menerima (integer ke enum):

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

Salam Hormat. :)

PENYU
sumber
8
atau Anda dapat mengirimnya sebagai string (sehingga dapat dibaca) dengan Num.A.name () dan kemudian mendapatkannya kembali menggunakan Num.ValueOf (intent.getStringExtra ("TEST"))
Benoit Jadinon
1
Saya pikir cara Benoit lebih aman, karena temp.ordinal () tidak disukai dalam praktik karena nilai ordinal () dapat berubah. Lihat posting ini: stackoverflow.com/questions/2836256/…
Shnkc
15

Jika Anda benar-benar perlu, Anda bisa membuat serial enum sebagai String, menggunakan name()dan valueOf(String), sebagai berikut:

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

Ini jelas tidak berfungsi jika enum Anda memiliki keadaan yang bisa berubah (yang seharusnya tidak, benar-benar).

Jan Berkel
sumber
4

Dimungkinkan untuk membuat Enum Anda mengimplementasikan Serializable maka Anda dapat meneruskannya melalui Intent, karena ada metode untuk meneruskannya sebagai serializable. Saran untuk menggunakan int bukan enum adalah palsu. Enum digunakan untuk membuat kode Anda lebih mudah dibaca dan lebih mudah dipelihara. Ini akan menjadi langkah besar mundur ke zaman kegelapan untuk tidak dapat menggunakan Enums.

Damien Cooke
sumber
2
Setiap jenis Enum meluas superclass Enum secara default, yang sudah mengimplementasikan Serializable.
Arcao
2

tentang pos Oderik:

Anda dapat membuat enum mengimplementasikan Parcelable yang cukup mudah untuk enum:

public enum MyEnum mengimplementasikan Parcelable {...} Anda dapat menggunakan Intent.putExtra (String, Parcelable).

Jika Anda mendefinisikan variabel MyEnum, myEnum, maka lakukan intent.putExtra ("Parcelable1", myEnum), Anda akan mendapatkan "Metode putExtra (String, Parcelable) ambigu untuk pesan kesalahan tipe Intent". karena ada juga metode Intent.putExtra (String, Parcelable), dan tipe 'Enum' asli itu sendiri mengimplementasikan antarmuka Serializable, jadi kompiler tidak tahu memilih metode mana (intent.putExtra (String, Parcelable / atau Serializable)).

Sarankan agar menghapus antarmuka Parcelable dari MyEnum, dan pindahkan kode inti ke implementasi wrapcel 'Parcelable class, seperti ini (Father2 adalah Parcelable dan berisi bidang 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());
}

}

maka kita bisa melakukan:

AnotherEnum anotherEnum = AnotherEnum.Z;
intent.putExtra("Serializable2", AnotherEnum.X);   
intent.putExtra("Parcelable2", new Father2(AnotherEnum.X, 7));
tedzyc
sumber
5
Anda dapat secara eksplisit memilih tanda tangan yang benar dengan "melemparkan" argumen, misalnyaintent.putExtra("myEnum", (Parcelable) enumValue);
Oderik
Menggunakan ordinal sempurna!
slot
Ini cara yang sangat rumit untuk mengatakan bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWiStErRob
2

Anda dapat menggunakan enum konstruktor agar enum memiliki tipe data 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);
    }
}

Anda dapat menggunakan pass int sebagai ekstra lalu tarik dari enum menggunakan nilainya.

niczm25
sumber
2

Sebagian besar jawaban yang menggunakan konsep Parcelable di sini adalah dalam kode Java. Lebih mudah melakukannya di Kotlin.

Beri catatan pada kelas enum Anda dengan @Parcelize dan terapkan antarmuka Parcelable.

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

Saya suka yang sederhana.

  • Aktivitas Fred memiliki dua mode - HAPPYdanSAD .
  • Buat statis IntentFactoryyang membuat Anda Intentuntuk Anda. Lewati ituMode Anda inginkan.
  • The IntentFactorymenggunakan namaMode kelas sebagai nama ekstra.
  • The IntentFactorybertobat yang Modeke Stringmenggunakanname()
  • Setelah masuk onCreategunakan info ini untuk mengkonversi kembali ke a Mode.
  • Anda bisa menggunakan ordinal()dan Mode.values()juga. Saya suka string karena saya bisa melihatnya di 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;
        }
    }
JohnnyLambada
sumber
ingin tahu .. siapa yang memanggil IntentFactory? dapatkah Anda menguraikan bagaimana suatu aktivitas yang berbeda akan memanggil Fred dan bagaimana Fred dapat memastikan bahwa Mode tersebut dilewatkan?
erik
0

Saya pikir taruhan terbaik Anda adalah mengubah daftar itu menjadi sesuatu yang tak terpisahkan seperti string (atau peta?) Untuk mendapatkannya ke dalam Kegiatan. Maka Kegiatan harus mengubahnya kembali menjadi sebuah array.

Menerapkan parcelables khusus adalah rasa sakit di IMHO IMHO jadi saya akan menghindarinya jika memungkinkan.

Brad Hein
sumber
0

Pertimbangkan Mengikuti enum ::

public static  enum MyEnum {
    ValueA,
    ValueB
}

Untuk Passing ::

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

Untuk mengambil kembali dari maksud / bundel / argumen ::

 MyEnum myEnum = (MyEnum) intent.getSerializableExtra("ENUM_CONST");
menghafal
sumber
0

Jika Anda hanya ingin mengirim enum, Anda dapat melakukan sesuatu seperti:

Pertama mendeklarasikan enum yang mengandung beberapa nilai (yang dapat dilewatkan melalui niat):

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

Kemudian:

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

Dan ketika Anda ingin mendapatkannya:

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

Sepotong kue!

MHN
sumber
0

Gunakan Fungsi Ekstensi 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] }

Ini memberi Anda fleksibilitas untuk melewati beberapa jenis enum yang sama, atau default untuk menggunakan nama kelas.

// 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
sumber
-2

Jangan gunakan enum. Alasan # 78 untuk tidak menggunakan enum. :) Gunakan bilangan bulat, yang dapat dengan mudah dikirim ulang melalui Bundle dan Parcelable.

hackbod
sumber
8
@hackbod - apa alasan 77 lainnya? ;) Serius meskipun - tampaknya ada banyak manfaat untuk enum dan mereka tidak benar-benar sulit untuk 'jauh' - setiap kesempatan Anda dapat memperluas alasan Anda melawan mereka?
ostergaard
3
@hackbod Tolong jelaskan. Jika enum tidak boleh digunakan maka hapus dari API.
dcow
2
Enum adalah bagian dari spesifikasi bahasa Java, jadi mereka agak sulit untuk dihapus dan masih memiliki implementasi Java yang sesuai :)
tad
1
bagaimana mEnum.ordinal()? itu mengembalikan posisi elemen
Saif Hamed
3
Nilai Enum.ordinal()tetap pada waktu kompilasi. Satu-satunya waktu yang berlaku komentar akan melewati data antara aplikasi dengan versi enum yang berbeda, atau lintas pembaruan aplikasi yang mengubah urutan elemen dalam enum. Hal semacam itu berbahaya setiap kali Anda menggunakan sesuatu yang bukan primitif. Untuk melewati maksud antara aktivitas dalam satu aplikasi, Enum.ordinal()harus sepenuhnya aman.
Ian McLaird