Apakah ada metode yang berfungsi seperti memulai fragmen untuk hasil?

95

Saat ini saya memiliki fragmen dalam overlay. Ini untuk masuk ke layanan. Di aplikasi ponsel, setiap langkah yang ingin saya tampilkan di hamparan adalah layar dan aktivitasnya sendiri. Ada 3 bagian proses masuk dan masing-masing memiliki aktivitasnya sendiri yang dipanggil dengan startActivityForResult ().

Sekarang saya ingin melakukan hal yang sama menggunakan fragmen dan overlay. Overlay akan menampilkan fragmen yang sesuai dengan setiap aktivitas. Masalahnya adalah fragmen ini dihosting dalam aktivitas di API Honeycomb. Saya bisa mendapatkan fragmen pertama berfungsi, tetapi kemudian saya perlu startActivityForResult (), yang tidak mungkin dilakukan. Apakah ada sesuatu di sepanjang baris startFragmentForResult () di mana saya bisa memulai sebuah fragmen baru dan setelah selesai apakah itu mengembalikan hasil ke fragmen sebelumnya?

CACuzcatlan
sumber

Jawaban:

58

Semua Fragmen ada di dalam Aktivitas. Memulai Fragmen untuk hasil tidak masuk akal, karena Aktivitas yang menampungnya selalu memiliki akses ke sana, dan sebaliknya. Jika Fragmen perlu meneruskan hasil, Fragmen dapat mengakses Aktivitasnya dan menyetel hasilnya serta menyelesaikannya. Dalam kasus menukar Fragmen dalam satu Aktivitas, Aktivitas masih dapat diakses oleh kedua Fragmen, dan semua pesan Anda yang lewat dapat melalui Aktivitas.

Ingatlah bahwa Anda selalu memiliki komunikasi antara Fragmen dan Aktivitasnya. Memulai dan mengakhiri dengan hasil adalah mekanisme komunikasi antara Aktivitas - Aktivitas kemudian dapat mendelegasikan informasi yang diperlukan ke Fragmen mereka.

LeffelMania
sumber
11
Selain itu, saat memuat satu fragmen dari yang lain, Anda dapat menyetel fragmen target dan memanggil kembali ke metode fragmen induk onActivityResultjika diinginkan.
PJL
4
Jawaban Anda tidak lengkap, fragmen menggunakan siklus hidup seperti aktivitas. Jadi, kita tidak bisa hanya melewatkan argumen melalui metode tetapi kita harus menggunakan bundel untuk meneruskan nilai. Bahkan jika fragmen menetapkan nilai di suatu tempat, kita perlu tahu kapan dia berakhir. Haruskah fragmen sebelumnya mendapatkan nilai saat dimulai / dilanjutkan? Ini adalah ide. Tetapi tidak ada cara yang tepat untuk menyimpan nilai, fragmen dapat dipanggil oleh beberapa fragmen / aktivitas lainnya.
Loenix
1
baik, masuk akal untuk memanggil 'start fragment for result' karena ini akan memberikan lebih banyak fleksibilitas -> mengapa seorang anak harus tahu tentang induknya? tidak akan jauh lebih bersih bagi fragmen untuk hanya melakukan pekerjaannya dan mengembalikan hasil terlepas dari aktivitas mana yang dia masuki. Terutama dalam konteks aplikasi aktivitas tunggal.
daneejela
Bagaimana dengan potensi kebocoran memori?
daneejela
60

Jika Anda mau, ada beberapa metode komunikasi antar Fragmen,

setTargetFragment(Fragment fragment, int requestCode)
getTargetFragment()
getTargetRequestCode()

Anda dapat menelepon balik menggunakan ini.

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}
nagoya0
sumber
Saya belum mengkonfirmasinya, tapi mungkin berhasil. jadi, Anda harus berhati-hati terhadap kebocoran memori yang disebabkan oleh referensi melingkar oleh penyalahgunaan setTargetFragment().
nagoya0
3
Solusi terbaik sejauh ini! Berfungsi dengan baik saat memiliki satu Aktivitas dan tumpukan fragmen di mana mencoba menemukan fragmen yang tepat untuk diberitahukan akan menjadi mimpi buruk. Terima kasih
Damien Praca
1
@ userSeven7s ini tidak akan bekerja untuk replace case tapi harus bekerja untuk hide case
Muhammad Babar
1
@ nagoya0 melakukannya lebih baik jika kita menggunakan WeakReference untuk target fragmen
quangson91
Meskipun ini berfungsi, setTargetFragmentsaat ini tidak digunakan lagi. Lihat setResultListenerdi stackoverflow.com/a/61881149/2914140 di sini.
CoolMind
11

Kita cukup berbagi ViewModel yang sama antar fragmen

SharedViewModel

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel

class SharedViewModel : ViewModel() {

    val stringData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

}

FirstFragment

import android.arch.lifecycle.Observer
import android.os.Bundle
import android.arch.lifecycle.ViewModelProviders
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class FirstFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        sharedViewModel.stringData.observe(this, Observer { dateString ->
            // get the changed String
        })

    }

}

SecondFragment

import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGrou

class SecondFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        changeString()
    }

    private fun changeString() {
        sharedViewModel.stringData.value = "Test"
    }

}
Levon Petrosyan
sumber
3
Hai Levon, saya pikir kode di atas tidak akan berbagi contoh model tampilan yang sama. Anda meneruskan (fragmen) ini untuk ViewModelProviders.of (this) .get (SharedViewModel :: class.java). Ini akan membuat dua contoh terpisah untuk fragmen. Anda harus meneruskan aktivitas ViewModelProviders.of (activity) .get (SharedViewModel :: class.java)
Shailendra Patil
@ShailendraPatil Tangkapan bagus, saya akan memperbaikinya sekarang.
Levon Petrosyan
6

Baru-baru ini, Google baru saja menambahkan kemampuan baru FragmentManageryang membuatnya FragmentManagerdapat bertindak sebagai penyimpanan pusat untuk hasil fragmen. Kami dapat mengirimkan data bolak-balik antar Fragmen dengan mudah.

Fragmen awal.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact
    setResultListener("requestKey") { key, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported
        val result = bundle.getString("bundleKey")
        // Do something with the result...
    }
}

Fragmen yang kami inginkan hasilnya kembali.

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setResult("requestKey", bundleOf("bundleKey" to result))
}

Cuplikan tersebut diambil dari dokumen resmi Google. https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

Pada data jawaban ini tertulis, fitur ini masih dalam alphastatus. Anda dapat mencobanya menggunakan ketergantungan ini.

androidx.fragment:fragment:1.3.0-alpha05
Boonya Kitpitak
sumber
4

2 sen saya.

Saya mengganti beberapa fragmen dengan menukar fragmen lama dengan yang baru menggunakan hide and show / add (existing / new). Jadi jawaban ini untuk developer yang menggunakan fragmen seperti yang saya lakukan.

Kemudian saya menggunakan onHiddenChangedmetode ini untuk mengetahui bahwa fragmen lama dialihkan ke belakang dari yang baru. Lihat kode di bawah ini.

Sebelum meninggalkan fragmen baru, saya menetapkan hasil dalam parameter global untuk ditanyai oleh fragmen lama. Ini adalah solusi yang sangat naif.

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden) return;
    Result result = Result.getAndReset();
    if (result == Result.Refresh) {
        refresh();
    }
}

public enum Result {
    Refresh;

    private static Result RESULT;

    public static void set(Result result) {
        if (RESULT == Refresh) {
            // Refresh already requested - no point in setting anything else;
            return;
        }
        RESULT = result;
    }

    public static Result getAndReset() {
        Result result = RESULT;
        RESULT = null;
        return result;
    }
}
AlikElzin-kilaka
sumber
Apa getAndReset()metodenya?
EpicPandaForce
Bukankah juga onResume()dipanggil pada fragmen pertama saat yang kedua ditutup?
PJ_Finnegan
2

Dalam fragmen Anda, Anda bisa memanggil getActivity (). Ini akan memberi Anda akses ke aktivitas yang membuat fragmen. Dari sana Anda dapat memanggil metode kustomisasi Anda untuk menyetel nilai atau meneruskan nilai.

Menyimpulkan Jain
sumber
1

Ada pustaka Android - FlowR yang memungkinkan Anda memulai fragmen untuk mendapatkan hasil.

Memulai fragmen untuk hasil.

Flowr.open(RequestFragment.class)
    .displayFragmentForResults(getFragmentId(), REQUEST_CODE);

Penanganan menghasilkan fragmen pemanggil.

@Override
protected void onFragmentResults(int requestCode, int resultCode, Bundle data) {
    super.onFragmentResults(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            demoTextView.setText("Result OK");
        } else {
            demoTextView.setText("Result CANCELED");
        }
    }
}

Menetapkan hasil di Fragmen.

Flowr.closeWithResults(getResultsResponse(resultCode, resultData));
Ragunath Jawahar
sumber
1

Solusi yang menggunakan antarmuka (dan Kotlin). Ide intinya adalah mendefinisikan antarmuka callback, mengimplementasikannya dalam aktivitas Anda, lalu memanggilnya dari fragmen Anda.

Pertama, buat antarmuka ActionHandler:

interface ActionHandler {
    fun handleAction(actionCode: String, result: Int)
}

Selanjutnya, panggil ini dari anak Anda (dalam hal ini, fragmen Anda):

companion object {
    const val FRAGMENT_A_CLOSED = "com.example.fragment_a_closed"
}

fun closeFragment() {
    try {
        (activity as ActionHandler).handleAction(FRAGMENT_A_CLOSED, 1234)
    } catch (e: ClassCastException) {
        Timber.e("Calling activity can't get callback!")
    }
    dismiss()
}

Terakhir, terapkan ini pada orang tua Anda untuk menerima callback (dalam hal ini, Aktivitas Anda):

class MainActivity: ActionHandler { 
    override fun handleAction(actionCode: String, result: Int) {
        when {
            actionCode == FragmentA.FRAGMENT_A_CLOSED -> {
                doSomething(result)
            }
            actionCode == FragmentB.FRAGMENT_B_CLOSED -> {
                doSomethingElse(result)
            }
            actionCode == FragmentC.FRAGMENT_C_CLOSED -> {
                doAnotherThing(result)
            }
        }
    }
Jake Lee
sumber
0

Cara termudah untuk meneruskan data kembali adalah dengan setArgument (). Misalnya, Anda memiliki fragment1 yang memanggil fragment2 yang memanggil fragment3, fragment1 -> framgnet2 -> fargment3

Dalam fragment1

public void navigateToFragment2() {
    if (fragmentManager == null) return;

    Fragment2 fragment = Fragment2.newInstance();
    String tag = "Fragment 2 here";
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .add(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commitAllowingStateLoss();
}

Dalam fragment2 kami memanggil fragment3 seperti biasa

private void navigateToFragment3() {
    if (fragmentManager == null) return;
    Fragment3 fragment = new Fragment3();
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .replace(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commit();
}

Ketika kami menyelesaikan tugas kami di fragment3 sekarang kami memanggil seperti ini:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
if (fragmentManager == null) return;
fragmentManager.popBackStack();
Bundle bundle = new Bundle();
bundle.putString("bundle_filter", "data");
fragmentManager.findFragmentByTag("Fragment 2 here").setArguments(bundle);

Sekarang di fragment2 kita dapat dengan mudah memanggil argumen

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}
Kirk_hehe
sumber
Berhati-hatilah dengan yang satu ini, ini bisa berfungsi dalam beberapa kasus, tetapi jika fragmen Anda memiliki argumen asli ('fragmen 2' dalam contoh ini), ini akan menimpa argumen asli yang digunakan untuk membuat fragmen, yang dapat menyebabkan keadaan tidak konsisten jika fragmen tersebut dihancurkan dan dibuat ulang.
Juan
0

Hal lain yang dapat Anda lakukan bergantung pada arsitektur Anda adalah menggunakan ViewModel bersama di antara fragmen. Jadi dalam kasus saya FragmentA adalah formulir, dan FragmentB adalah tampilan pemilihan item di mana pengguna dapat mencari dan memilih item, menyimpannya di ViewModel. Lalu ketika saya kembali ke FragmentA, informasinya sudah disimpan!

Arjun
sumber