Terima hasil dari DialogFragment

232

Saya menggunakan DialogFragments untuk sejumlah hal: memilih item dari daftar, memasukkan teks.

Apa cara terbaik untuk mengembalikan nilai (yaitu string atau item dari daftar) kembali ke aktivitas / fragmen panggilan?

Saat ini saya membuat implementasi aktivitas panggilan DismissListenerdan memberikan DialogFragment referensi ke aktivitas tersebut. Dialog kemudian memanggil OnDimissmetode dalam aktivitas dan aktivitas mengambil hasil dari objek DialogFragment. Sangat berantakan dan tidak berfungsi pada perubahan konfigurasi (perubahan orientasi) karena DialogFragment kehilangan referensi ke aktivitas.

Terima kasih atas bantuannya.

James Cross
sumber
10
DialogFragments masih berupa fragmen saja. Pendekatan Anda sebenarnya adalah cara yang disarankan bagi fragmen untuk digunakan untuk berbicara kembali dengan aktivitas utama. developer.android.com/guide/topics/fundamentals/…
codinguser
1
Terima kasih untuk itu. Saya sangat dekat (seperti yang Anda katakan). Bit yang ditautkan oleh dokumen yang membantu saya menggunakan onAttach () dan memberikan aktivitas kepada pendengar.
James Cross
2
@codinguser, @Styx - "memberikan DialogFragment referensi ke aktivitas" - detail ini sedikit berisiko, karena keduanya Activitydan DialogFragmentmungkin dibuat ulang. Menggunakan yang Activityditeruskan ke onAttach(Activity activity)adalah cara yang tepat dan direkomendasikan.
sstn

Jawaban:

246

Gunakan myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)dari tempat di mana Anda menampilkan dialog, dan kemudian ketika dialog Anda selesai, dari situ Anda bisa menelepon getTargetFragment().onActivityResult(getTargetRequestCode(), ...), dan mengimplementasikannya onActivityResult()dalam fragmen yang berisi.

Sepertinya penyalahgunaan onActivityResult(), terutama karena tidak melibatkan kegiatan sama sekali. Tapi saya sudah melihatnya direkomendasikan oleh orang-orang google resmi, dan mungkin bahkan dalam demo api. Saya pikir itu untuk apa g/setTargetFragment()ditambahkan.

Timmmm
sumber
2
setTargetFragment menyebutkan bahwa kode permintaan untuk digunakan di onActivityResult jadi saya rasa tidak apa-apa untuk menggunakan pendekatan ini.
Giorgi
87
Bagaimana jika targetnya adalah suatu kegiatan?
Fernando Gallego
9
Jika target adalah aktivitas, saya akan mendeklarasikan antarmuka dengan metode seperti "void onActivityResult2 (int requestCode, int resultCode, Intent data)" dan mengimplementasikannya dengan sebuah Activity. Di DialogFragment cukup getActivity dan periksa antarmuka ini dan panggil dengan tepat.
Ruslan Yanchyshyn
4
Itu bukan solusi yang baik. Ini tidak akan berfungsi setelah menyimpan dan mengembalikan keadaan fragmen dialog. LocalBroadcastManager adalah solusi terbaik dalam hal ini.
Nik
4
@ Nik Itu tidak benar. Itu solusi terbaik. Tidak ada masalah saat menyimpan dan memulihkan negara. Jika Anda pernah memiliki masalah, Anda menggunakan manajer fragmen yang salah. Target fragmen / pemanggil harus menggunakan getChildFragmentManager () untuk menampilkan dialog.
Jan
140

Seperti yang Anda lihat di sini ada cara yang sangat sederhana untuk melakukannya.

Di DialogFragmentadd Anda tambahkan pendengar antarmuka seperti:

public interface EditNameDialogListener {
    void onFinishEditDialog(String inputText);
}

Kemudian, tambahkan referensi ke pendengar itu:

private EditNameDialogListener listener;

Ini akan digunakan untuk "mengaktifkan" metode pendengar, dan juga untuk memeriksa apakah Activity / Fragment induk mengimplementasikan antarmuka ini (lihat di bawah).

Di Activity/FragmentActivity / Fragmentyang "disebut" ituDialogFragment hanya mengimplementasikan antarmuka ini.

Dalam DialogFragmentsemua yang Anda butuhkan untuk menambahkan pada titik di mana Anda ingin mengabaikan DialogFragmentdan mengembalikan hasilnya adalah ini:

listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();

Dimana mEditText.getText().toString() apa yang akan diteruskan kembali ke pemanggilan Activity.

Perhatikan bahwa jika Anda ingin mengembalikan sesuatu yang lain, cukup ubah argumen yang diambil pendengar.

Terakhir, Anda harus memeriksa apakah antarmuka benar-benar diimplementasikan oleh aktivitas / fragmen induk:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    // Verify that the host activity implements the callback interface
    try {
        // Instantiate the EditNameDialogListener so we can send events to the host
        listener = (EditNameDialogListener) context;
    } catch (ClassCastException e) {
        // The activity doesn't implement the interface, throw exception
        throw new ClassCastException(context.toString()
                + " must implement EditNameDialogListener");
    }
}

Teknik ini sangat fleksibel dan memungkinkan menelepon kembali dengan hasil bahkan jika Anda belum mau mengabaikan dialog.

Assaf Gamliel
sumber
13
Ini bekerja sangat baik dengan Activity'dan FragmentActivity' tetapi jika penelepon itu Fragment?
Brais Gabin
1
Saya tidak yakin saya sepenuhnya mengerti Anda. Tetapi akan bekerja sama jika penelepon adalah a Fragment.
Assaf Gamliel
2
Jika penelepon adalah seorang Fragmentmaka Anda dapat melakukan beberapa hal: 1. Lewati fragmen sebagai referensi (Mungkin bukan ide yang baik karena Anda dapat menyebabkan kebocoran memori). 2. Gunakan FragmentManagerdan panggil findFragmentByIdatau findFragmentByTagakan mendapatkan fragmen yang ada di aktivitas Anda. Saya harap ini membantu. Semoga hari mu menyenangkan!
Assaf Gamliel
5
Masalah dengan pendekatan ini adalah bahwa fragmen tidak sangat baik dalam mempertahankan objek karena mereka dimaksudkan untuk diciptakan kembali, misalnya mencoba mengubah orientasi, OS akan membuat ulang fragmen tetapi instance pendengar tidak akan tersedia lagi
Necronet
5
@LOG_TAG lihat jawaban @ Timmmm. setTargetFragment()dan getTargetFragment()ajaib.
Brais Gabin
48

Ada cara yang lebih sederhana untuk menerima hasil dari DialogFragment.

Pertama, di Activity, Fragment, atau FragmentActivity Anda perlu menambahkan informasi berikut:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Stuff to do, dependent on requestCode and resultCode
    if(requestCode == 1) { // 1 is an arbitrary number, can be any int
         // This is the return result of your DialogFragment
         if(resultCode == 1) { // 1 is an arbitrary number, can be any int
              // Now do what you need to do after the dialog dismisses.
         }
     }
}

Pada requestCodedasarnya ini adalah label int Anda untuk DialogFragment yang Anda panggil, saya akan menunjukkan cara kerjanya dalam sedetik. ResultCode adalah kode yang Anda kirim kembali dari DialogFragment yang memberi tahu Aktivitas, Fragmen, atau FragmentActivity Anda saat ini tentang apa yang terjadi.

Sepotong kode berikutnya untuk masuk adalah panggilan ke DialogFragment. Contohnya ada di sini:

DialogFragment dialogFrag = new MyDialogFragment();
// This is the requestCode that you are sending.
dialogFrag.setTargetFragment(this, 1);     
// This is the tag, "dialog" being sent.
dialogFrag.show(getFragmentManager(), "dialog");

Dengan tiga baris ini Anda mendeklarasikan DialogFragment Anda, menetapkan requestCode (yang akan memanggil onActivityResult (...) setelah Dialog diberhentikan, dan Anda kemudian menampilkan dialog. Sesederhana itu.

Sekarang, di DialogFragment Anda, Anda hanya perlu menambahkan satu baris langsung sebelum dismiss()sehingga Anda mengirim resultCode kembali ke onActivityResult ().

getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();

Itu dia. Catatan, resultCode didefinisikan seperti int resultCodeyang saya atur resultCode = 1;dalam hal ini.

Itu saja, Anda sekarang dapat mengirim hasil DialogFragment Anda kembali ke Aktivitas panggilan Anda, Fragmen, atau FragmentActivity.

Juga, sepertinya informasi ini telah diposting sebelumnya, tetapi tidak ada contoh yang cukup diberikan jadi saya pikir saya akan memberikan lebih banyak detail.

EDIT 06.24.2016 Saya minta maaf atas kode yang menyesatkan di atas. Tetapi Anda pasti tidak dapat menerima hasilnya kembali ke aktivitas dengan melihat sebagai baris:

dialogFrag.setTargetFragment(this, 1);

menetapkan target Fragmentdan tidak Activity. Jadi untuk melakukan ini, Anda perlu menggunakan implement InterfaceCommunicator.

Di DialogFragmentset Anda, variabel global

public InterfaceCommunicator interfaceCommunicator;

Buat fungsi publik untuk menanganinya

public interface InterfaceCommunicator {
    void sendRequestCode(int code);
}

Kemudian ketika Anda siap untuk mengirim kode kembali ke Activityketika DialogFragmentdilakukan berjalan, Anda cukup menambahkan baris sebelum Anda dismiss();Anda DialogFragment:

interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.

Dalam aktivitas Anda sekarang Anda harus melakukan dua hal, yang pertama adalah menghapus satu baris kode yang tidak lagi berlaku:

dialogFrag.setTargetFragment(this, 1);  

Kemudian implementasikan antarmuka dan Anda semua selesai. Anda dapat melakukannya dengan menambahkan baris berikut ke implementsklausa di bagian paling atas kelas Anda:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

Dan kemudian @Overridefungsi dalam aktivitas tersebut,

@Override
public void sendRequestCode(int code) {
    // your code here
}

Anda menggunakan metode antarmuka ini sama seperti onActivityResult()metode Anda. Kecuali metode antarmuka untuk DialogFragmentsdan yang lainnya adalah untuk Fragments.

Brandon
sumber
4
Pendekatan ini tidak akan berfungsi jika target adalah Aktivitas karena Anda tidak dapat memanggil onActivityResult (dari DialogFragment Anda) karena tingkat akses yang dilindungi.
Ruslan Yanchyshyn
2
Itu tidak benar. Saya menggunakan kode persis ini dalam proyek saya. Di situlah saya menariknya dan berfungsi dengan baik. Harap diingat jika Anda mengalami masalah tingkat akses yang dilindungi ini, Anda dapat mengubah tingkat akses Anda untuk metode dan kelas apa pun dari terlindungi menjadi pribadi atau publik jika perlu.
Brandon
6
Hai, Anda berkata Anda dapat menelepon dialogFrag.setTargetFragment(this, 1)dari suatu Kegiatan, tetapi metode ini menerima Fragmen sebagai argumen pertama, jadi ini tidak dapat dicor. Apakah saya benar ?
MondKin
1
Saya akan mengirimkan beberapa tanggapan untuk Anda semua dalam beberapa untuk menjelaskan hal-hal kegiatan.
Brandon
1
@Swift @lcompare Anda mungkin perlu mengganti onAttach (Konteks konteks) di DialogFragment Anda. Seperti itu:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx
20

Yah mungkin sudah terlambat untuk menjawab tetapi di sini adalah apa yang saya lakukan untuk mendapatkan kembali dari DialogFragment. sangat mirip dengan jawaban @ brandon. Di sini saya menelepon DialogFragmentdari sebuah fragmen, cukup tempatkan kode ini di mana Anda memanggil dialog Anda.

FragmentManager fragmentManager = getFragmentManager();
            categoryDialog.setTargetFragment(this,1);
            categoryDialog.show(fragmentManager, "dialog");

di mana categoryDialogsaya DialogFragmentyang ingin saya panggil dan setelah ini dalam implementasi Anda dialogfragmenttempat kode ini di mana Anda mengatur data Anda dalam niat. Nilai resultCode1 adalah Anda dapat mengaturnya atau menggunakan sistem Didefinisikan.

            Intent intent = new Intent();
            intent.putExtra("listdata", stringData);
            getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
            getDialog().dismiss();

sekarang saatnya untuk kembali ke fragmen panggilan dan mengimplementasikan metode ini. memeriksa validitas data atau hasil yang sukses jika Anda ingin dengan resultCodedan requestCodedalam kondisi jika.

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);        
        //do what ever you want here, and get the result from intent like below
        String myData = data.getStringExtra("listdata");
Toast.makeText(getActivity(),data.getStringExtra("listdata"),Toast.LENGTH_SHORT).show();
    }
vikas kumar
sumber
10

Pendekatan berbeda, untuk memungkinkan Fragmen berkomunikasi hingga Aktivitasnya :

1) Tentukan antarmuka publik dalam fragmen dan buat variabel untuknya

public OnFragmentInteractionListener mCallback;

public interface OnFragmentInteractionListener {
    void onFragmentInteraction(int id);
}

2) Keluarkan aktivitas ke variabel mCallback dalam fragmen

try {
    mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
    Log.d(TAG, e.getMessage());
}

3) Laksanakan pendengar dalam aktivitas Anda

public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener  {
     //your code here
}

4) Mengesampingkan OnFragmentInteraction dalam aktivitas

@Override
public void onFragmentInteraction(int id) {
    Log.d(TAG, "received from fragment: " + id);
}

Info lebih lanjut tentang itu: https://developer.android.com/training/basics/fragments/communicating.html

Bandingkan
sumber
Terima kasih telah merangkumnya dengan baik. Hanya satu catatan untuk yang lain, tutorial Android Devs menyarankan untuk mengganti public void onAttachfragmen dan melakukan casting aktivitas di sana
Big_Chair
8

Satu cara mudah yang saya temukan adalah yang berikut: Terapkan ini adalah dialogFragment Anda,

  CallingActivity callingActivity = (CallingActivity) getActivity();
  callingActivity.onUserSelectValue("insert selected value here");
  dismiss();

Dan kemudian dalam aktivitas yang disebut Dialog Fragment membuat fungsi yang sesuai seperti:

 public void onUserSelectValue(String selectedValue) {

        // TODO add your implementation.
      Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
    }

The Toast adalah untuk menunjukkan bahwa ia berfungsi. Bekerja untukku.

ZeWolfe15
sumber
Saya tidak yakin apakah ini cara yang benar untuk melakukannya, tetapi tentu saja akan berhasil :)
soshial
Penggunaan yang Interfacelebih baik daripada hard-coupling dengan kelas beton.
waqaslam
6

Saya sangat terkejut melihat bahwa tidak ada seorang pun telah menyarankan menggunakan siaran lokal untuk DialogFragmentuntuk Activitykomunikasi! Saya merasa itu jauh lebih sederhana dan lebih bersih daripada saran lainnya. Pada dasarnya, Anda mendaftar untuk Activitymendengarkan siaran dan Anda mengirim siaran lokal dari mesin DialogFragmentvirtual Anda . Sederhana. Untuk panduan langkah demi langkah tentang cara mengatur semuanya, lihat di sini .

Adil Hussain
sumber
2
Saya suka solusi itu, apakah ini dianggap sebagai praktik yang baik atau terbaik di Android?
Benjamin Scharbau
1
Saya sangat menyukai tutorialnya, terima kasih telah mempostingnya. Saya ingin menambahkan bahwa tergantung pada apa yang Anda coba capai, metode mana pun mungkin lebih bermanfaat daripada yang lain. Saya akan menyarankan rute siaran lokal jika Anda memiliki beberapa input / hasil yang dikirim kembali ke aktivitas dari dialog. Saya akan merekomendasikan menggunakan rute onActivityResult jika output Anda sangat mendasar / sederhana. Jadi, untuk menjawab pertanyaan praktik terbaik, itu tergantung pada apa yang Anda coba capai!
Brandon
1
@ AdilHussain Anda benar. Saya membuat asumsi bahwa orang menggunakan Fragmen dalam Aktivitas mereka. Opsi setTargetFragment sangat bagus jika Anda berkomunikasi dengan Fragmen dan DialogFragment. Tetapi Anda perlu menggunakan metode Siaran saat itu adalah suatu Aktivitas yang memanggil DialogFragment.
Brandon
3
Demi cinta Foo, jangan gunakan Siaran !! Ini membuka aplikasi Anda ke masalah keamanan. Saya juga menemukan bahwa aplikasi android terburuk saya harus bekerja pada siaran penyalahgunaan. Bisakah Anda memikirkan cara yang lebih baik untuk membuat kode yang sama sekali tidak dapat digunakan? Sekarang saya harus membasmi penerima siaran, bukan baris kode CLEAR? Supaya jelas ada kegunaan untuk broadcoasts, tetapi tidak dalam konteks ini! TIDAK PERNAH dalam konteks ini! Hanya ceroboh. Lokal atau tidak. Yang Anda butuhkan hanyalah panggilan balik.
StarWind0
1
Selain Guava EventBus, opsi lain adalah GreenRobot EventBus . Saya belum pernah menggunakan Guava EventBus tetapi telah menggunakan GreenRobot EventBus dan telah memiliki pengalaman yang baik dengannya. Bagus dan mudah digunakan. Untuk contoh kecil tentang cara membuat arsitektur aplikasi Android untuk menggunakan GreenRobot EventBus, lihat di sini .
Adil Hussain
3

Atau bagikan ViewModel seperti yang diperlihatkan di sini:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

https://developer.android.com/topic/libraries/architecture/viewmodel#sharing_data_between_fragments

Malachiasz
sumber
2

Dalam kasus saya, saya perlu meneruskan argumen ke targetFragment. Tapi saya mendapat pengecualian "Fragmen sudah aktif". Jadi saya mendeklarasikan sebuah Antarmuka di DialogFragment saya yang diimplementasikan parentFragment. Ketika parentFragment memulai DialogFragment, itu menetapkan dirinya sebagai TargetFragment. Kemudian di DialogFragment saya menelepon

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);
Lanitka
sumber
2

Di Kotlin

    // My DialogFragment
class FiltroDialogFragment : DialogFragment(), View.OnClickListener {
    
    var listener: InterfaceCommunicator? = null

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        listener = context as InterfaceCommunicator
    }

    interface InterfaceCommunicator {
        fun sendRequest(value: String)
    }   

    override fun onClick(v: View) {
        when (v.id) {
            R.id.buttonOk -> {    
        //You can change value             
                listener?.sendRequest('send data')
                dismiss()
            }
            
        }
    }
}

// Aktivitas Saya

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

    override fun sendRequest(value: String) {
    // :)
    Toast.makeText(this, value, Toast.LENGTH_LONG).show()
    }
}
 

Saya harap ini berfungsi, jika Anda dapat meningkatkan, silakan edit. Bahasa Inggris saya tidak begitu bagus

Irvin Joao
sumber
Dalam kasus saya, dialog dibuat dari fragmen bukan aktivitas sehingga solusi ini tidak berfungsi. Tapi saya suka smiley yang Anda masukkan :)
Ssenyonjo
0

jika Anda ingin mengirim argumen dan menerima hasil dari fragmen kedua, Anda dapat menggunakan Fragment.setArguments untuk menyelesaikan tugas ini

static class FirstFragment extends Fragment {
    final Handler mUIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 101: // receive the result from SecondFragment
                Object result = msg.obj;
                // do something according to the result
                break;
            }
        };
    };

    void onStartSecondFragments() {
        Message msg = Message.obtain(mUIHandler, 101, 102, 103, new Object()); // replace Object with a Parcelable if you want to across Save/Restore
                                                                               // instance
        putParcelable(new SecondFragment(), msg).show(getFragmentManager().beginTransaction(), null);
    }
}

static class SecondFragment extends DialogFragment {
    Message mMsg; // arguments from the caller/FirstFragment

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);
        mMsg = getParcelable(this);
    }

    void onClickOK() {
        mMsg.obj = new Object(); // send the result to the caller/FirstFragment
        mMsg.sendToTarget();
    }
}

static <T extends Fragment> T putParcelable(T f, Parcelable arg) {
    if (f.getArguments() == null) {
        f.setArguments(new Bundle());
    }
    f.getArguments().putParcelable("extra_args", arg);
    return f;
}
static <T extends Parcelable> T getParcelable(Fragment f) {
    return f.getArguments().getParcelable("extra_args");
}
Yessy
sumber
-3

Hanya untuk memilikinya sebagai salah satu opsi (karena belum ada yang menyebutkannya) - Anda dapat menggunakan bus acara seperti Otto. Jadi dalam dialog yang Anda lakukan:

bus.post(new AnswerAvailableEvent(42));

Dan mintalah penelepon Anda (Aktivitas atau Fragmen) untuk berlangganan:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
Dennis K
sumber