Pengelola fragmen

FragmentManager adalah class yang bertanggung jawab untuk melakukan tindakan pada fragmen aplikasi Anda, seperti menambahkan, menghapus, atau menggantinya, dan menambahkannya ke data sebelumnya.

Anda mungkin tidak pernah berinteraksi dengan FragmentManager secara langsung jika menggunakan library Navigasi Jetpack, karena library tersebut berfungsi dengan FragmentManager atas nama Anda. Namun, aplikasi apa pun yang menggunakan fragmen juga menggunakan FragmentManager pada tingkat tertentu, jadi penting untuk memahami arti dan cara kerjanya.

Halaman ini membahas:

  • Cara mengakses FragmentManager.
  • Peran FragmentManager dalam kaitannya dengan aktivitas dan fragmen Anda.
  • Cara mengelola data sebelumnya dengan FragmentManager.
  • Cara menyediakan data dan dependensi ke fragmen Anda.

Mengakses FragmentManager

Anda dapat mengakses FragmentManager dari aktivitas atau dari fragmen.

FragmentActivity dan subclass-nya, seperti AppCompatActivity, memiliki akses ke FragmentManager melalui metode getSupportFragmentManager().

Fragmen dapat menghosting satu atau beberapa fragmen turunan. Dalam fragmen, Anda bisa mendapatkan referensi ke FragmentManager yang mengelola turunan fragmen melalui getChildFragmentManager(). Jika perlu mengakses FragmentManager hostnya, Anda dapat menggunakan getParentFragmentManager().

Berikut adalah beberapa contoh untuk melihat hubungan antara fragmen, host-nya, dan instance FragmentManager yang terkait dengan masing-masing fragmen.

dua contoh tata letak UI yang menunjukkan hubungan
            antara fragmen dan aktivitas host-nya
Gambar 1. Dua contoh tata letak UI yang menunjukkan hubungan antara fragmen dan aktivitas host-nya.

Gambar 1 menunjukkan dua contoh, yang masing-masing memiliki host aktivitas tunggal. Aktivitas host di kedua contoh ini menampilkan navigasi level atas kepada pengguna sebagai BottomNavigationView yang bertanggung jawab untuk menukar fragmen host dengan layar yang berbeda dalam aplikasi. Setiap layar diterapkan sebagai fragmen terpisah.

Fragmen host di Contoh 1 menghosting dua fragmen turunan yang membentuk layar tampilan terpisah. Fragmen host di Contoh 2 menghosting fragmen turunan tunggal yang membentuk fragmen tampilan dari tampilan geser.

Dengan penyiapan ini, Anda dapat memikirkan setiap host sebagai FragmentManager yang terkait dengannya yang mengelola fragmen turunannya. Hal ini diilustrasikan dalam gambar 2, beserta pemetaan properti antara supportFragmentManager, parentFragmentManager, dan childFragmentManager.

setiap host memiliki FragmentManager masing-masing yang terkait dengannya yang mengelola fragmen turunannya
Gambar 2. Setiap host memiliki FragmentManager-nya sendiri yang terkait dengannya yang mengelola fragmen turunannya.

Properti FragmentManager yang sesuai untuk referensi tergantung pada lokasi callsite dalam hierarki fragmen bersama dengan pengelola fragmen yang Anda coba akses.

Setelah memiliki referensi ke FragmentManager, Anda dapat menggunakannya untuk memanipulasi fragmen yang ditampilkan kepada pengguna.

Fragmen turunan

Secara umum, aplikasi Anda terdiri atas satu atau sejumlah kecil aktivitas dalam project aplikasi, dengan setiap aktivitas mewakili sekelompok layar terkait. Aktivitas mungkin memberikan titik untuk menempatkan navigasi tingkat atas dan tempat untuk mencakup objek ViewModel dan status tampilan lainnya di antara fragmen. Fragmen mewakili setiap tujuan di aplikasi Anda.

Jika Anda ingin menampilkan beberapa fragmen sekaligus, seperti dalam tampilan terpisah atau dasbor, Anda dapat menggunakan fragmen turunan yang dikelola oleh fragmen tujuan dan pengelola fragmen turunannya.

Kasus penggunaan lainnya untuk fragmen turunan adalah sebagai berikut:

  • Slide layar, menggunakan ViewPager2 di fragmen induk untuk mengelola serangkaian tampilan fragmen turunan.
  • Sub-navigasi dalam sekumpulan layar terkait.
  • Navigasi Jetpack menggunakan fragmen turunan sebagai tujuan individu. Suatu aktivitas menghosting satu NavHostFragment induk dan mengisi ruang dengan fragmen tujuan turunan yang berbeda saat pengguna menavigasi aplikasi Anda.

Menggunakan FragmentManager

FragmentManager mengelola data sebelumnya pada fragmen. Pada runtime, FragmentManager dapat menjalankan operasi data sebelumnya seperti menambahkan atau menghapus fragmen sebagai respons terhadap interaksi pengguna. Setiap kumpulan perubahan disatukan sebagai unit tunggal yang disebut FragmentTransaction. Untuk pembahasan lebih mendalam tentang transaksi fragmen, lihat panduan transaksi fragmen.

Saat pengguna mengetuk tombol Kembali di perangkat, atau saat Anda memanggil FragmentManager.popBackStack(), transaksi fragmen teratas akan muncul dari tumpukan. Jika tidak ada lagi transaksi fragmen pada tumpukan, dan jika Anda tidak menggunakan fragmen turunan, peristiwa Kembali akan muncul dalam aktivitas. Jika Anda menggunakan fragmen turunan, lihat pertimbangan khusus untuk fragmen turunan dan yang setara.

Saat Anda memanggil addToBackStack() pada transaksi, transaksi tersebut dapat menyertakan sejumlah operasi, seperti menambahkan beberapa fragmen atau mengganti fragmen dalam beberapa penampung.

Saat data sebelumnya muncul, semua operasi ini terbalik sebagai satu tindakan atomik. Namun, jika Anda melakukan transaksi tambahan sebelum panggilan popBackStack(), dan jika Anda tidak menggunakan addToBackStack() untuk transaksi tersebut, operasi ini tidak terbalik. Oleh karena itu, dalam satu FragmentTransaction, hindari transaksi interleaving yang memengaruhi data sebelumnya dengan yang tidak.

Melakukan transaksi

Untuk menampilkan fragmen dalam penampung tata letak, gunakan FragmentManager untuk membuat FragmentTransaction. Dalam transaksi, selanjutnya Anda dapat melakukan operasi add() atau replace() pada penampung.

Misalnya, FragmentTransaction sederhana mungkin terlihat seperti ini:

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container)
   setReorderingAllowed(true)
   addToBackStack("name") // Name can be null
}

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null)
    .setReorderingAllowed(true)
    .addToBackStack("name") // Name can be null
    .commit();

Dalam contoh ini, ExampleFragment menggantikan fragmen, jika ada, yang saat ini ada di penampung tata letak yang diidentifikasi ID R.id.fragment_container. Dengan memberikan class fragmen ke metode replace(), FragmentManager dapat menangani pembuatan instance menggunakan FragmentFactory. Untuk mengetahui informasi selengkapnya, lihat bagian Menyediakan dependensi untuk fragmen.

setReorderingAllowed(true) mengoptimalkan perubahan status fragmen yang terlibat dalam transaksi sehingga animasi dan transisi berfungsi dengan benar. Untuk informasi selengkapnya tentang navigasi dengan animasi dan transisi, lihat Transaksi fragmen dan Menavigasi antara fragmen menggunakan animasi.

Memanggil addToBackStack() akan mengikat transaksi ke data sebelumnya. Nantinya pengguna dapat membalikkan transaksi dan mengembalikan fragmen sebelumnya dengan mengetuk tombol Kembali. Jika Anda menambahkan atau menghapus beberapa fragmen dalam satu transaksi, semua operasi tersebut akan diurungkan saat data sebelumnya muncul. Nama opsional yang diberikan dalam panggilan addToBackStack() memberi Anda kemampuan untuk memunculkan kembali transaksi spesifik tersebut menggunakan popBackStack().

Jika Anda tidak memanggil addToBackStack() saat melakukan transaksi yang menghapus fragmen, fragmen yang dihapus akan dihancurkan saat transaksi dilakukan, dan pengguna tidak dapat menavigasi kembali ke fragmen tersebut. Jika Anda memanggil addToBackStack() saat menghapus fragmen, fragmen tersebut hanya STOPPED dan RESUMED setelahnya saat pengguna menavigasi kembali. Tampilannya dihancurkan dalam hal ini. Untuk mengetahui informasi selengkapnya, lihat Siklus proses fragmen

Menemukan fragmen yang ada

Anda dapat memperoleh referensi ke fragmen saat ini dalam penampung tata letak dengan menggunakan findFragmentById(). Gunakan findFragmentById() untuk mencari fragmen baik dengan ID yang diberikan saat di-inflate dari XML atau menurut ID penampung saat ditambahkan di FragmentTransaction. Berikut contohnya:

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container)
   setReorderingAllowed(true)
   addToBackStack(null)
}
...
val fragment: ExampleFragment =
        supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null)
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .commit();
...
ExampleFragment fragment =
        (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);

Atau, Anda dapat menetapkan tag unik ke fragmen dan mendapatkan referensi menggunakan findFragmentByTag(). Anda dapat menetapkan tag menggunakan atribut XML android:tag pada fragmen yang ditentukan dalam tata letak, atau selama operasi add() atau replace() dalam FragmentTransaction.

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container, "tag")
   setReorderingAllowed(true)
   addToBackStack(null)
}
...
val fragment: ExampleFragment =
        supportFragmentManager.findFragmentByTag("tag") as ExampleFragment

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null, "tag")
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .commit();
...
ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");

Pertimbangan khusus untuk fragmen turunan dan yang setara

Hanya satu FragmentManager yang dapat mengontrol data sebelumnya di fragmen pada waktu tertentu. Jika aplikasi Anda menampilkan beberapa fragmen yang setara di layar secara bersamaan, atau jika aplikasi Anda menggunakan fragmen turunan, satu FragmentManager ditetapkan untuk menangani navigasi utama aplikasi.

Untuk menentukan fragmen navigasi utama di dalam transaksi fragmen, panggil metode setPrimaryNavigationFragment() pada transaksi, dengan meneruskan instance fragmen yang childFragmentManager-nya memiliki kontrol utama.

Pertimbangkan struktur navigasi sebagai serangkaian lapisan, dengan aktivitas sebagai lapisan terluar, yang menyelimuti setiap lapisan fragmen turunan di bawahnya. Setiap lapisan memiliki satu fragmen navigasi utama.

Saat peristiwa Kembali terjadi, lapisan terdalam mengontrol perilaku navigasi. Setelah lapisan terdalam tidak lagi memiliki transaksi fragmen untuk dimunculkan kembali, kontrol akan kembali ke lapisan berikutnya, dan proses ini terjadi berulang hingga Anda mencapai aktivitas tersebut.

Jika dua fragmen atau lebih ditampilkan secara bersamaan, hanya satu yang merupakan fragmen navigasi utama. Menetapkan fragmen sebagai fragmen navigasi utama akan menghapus penetapan dari fragmen sebelumnya. Menggunakan contoh sebelumnya, jika Anda menetapkan fragmen detail sebagai fragmen navigasi utama, penetapan fragmen utama akan dihapus.

Mendukung beberapa data sebelumnya

Dalam beberapa kasus, aplikasi Anda mungkin perlu mendukung beberapa data sebelumnya. Contoh yang umum adalah jika aplikasi Anda menggunakan menu navigasi bawah. FragmentManager memungkinkan Anda mendukung beberapa data sebelumnya dengan metode saveBackStack() dan restoreBackStack(). Metode ini memungkinkan Anda beralih antar-data sebelumnya dengan menyimpan satu data sebelumnya dan memulihkan yang lain.

saveBackStack() berfungsi mirip dengan memanggil popBackStack() dengan parameter name opsional: transaksi yang ditentukan dan semua transaksi setelahnya di stack akan muncul. Perbedaannya adalah saveBackStack() menyimpan status semua fragmen dalam transaksi yang muncul.

Misalnya, sebelumnya Anda telah menambahkan fragmen ke data sebelumnya dengan menyatukan FragmentTransaction menggunakan addToBackStack(), seperti yang ditunjukkan dalam contoh berikut:

Kotlin

supportFragmentManager.commit {
  replace<ExampleFragment>(R.id.fragment_container)
  setReorderingAllowed(true)
  addToBackStack("replacement")
}

Java

supportFragmentManager.beginTransaction()
  .replace(R.id.fragment_container, ExampleFragment.class, null)
  // setReorderingAllowed(true) and the optional string argument for
  // addToBackStack() are both required if you want to use saveBackStack()
  .setReorderingAllowed(true)
  .addToBackStack("replacement")
  .commit();

Dalam hal ini, Anda dapat menyimpan transaksi fragmen ini dan status ExampleFragment dengan memanggil saveBackStack():

Kotlin

supportFragmentManager.saveBackStack("replacement")

Java

supportFragmentManager.saveBackStack("replacement");

Anda dapat memanggil restoreBackStack() dengan parameter nama yang sama untuk memulihkan semua transaksi yang muncul dan semua status fragmen yang disimpan:

Kotlin

supportFragmentManager.restoreBackStack("replacement")

Java

supportFragmentManager.restoreBackStack("replacement");

Memberikan dependensi ke fragmen Anda

Saat menambahkan fragmen, Anda dapat membuat instance fragmen secara manual dan menambahkannya ke FragmentTransaction.

Kotlin

fragmentManager.commit {
    // Instantiate a new instance before adding
    val myFragment = ExampleFragment()
    add(R.id.fragment_view_container, myFragment)
    setReorderingAllowed(true)
}

Java

// Instantiate a new instance before adding
ExampleFragment myFragment = new ExampleFragment();
fragmentManager.beginTransaction()
    .add(R.id.fragment_view_container, myFragment)
    .setReorderingAllowed(true)
    .commit();

Saat Anda melakukan transaksi fragmen, instance fragmen yang Anda buat adalah instance yang digunakan. Namun, selama perubahan konfigurasi, aktivitas Anda dan semua fragmennya akan dihancurkan, lalu dibuat ulang dengan resource Android yang paling sesuai. FragmentManager menangani semua ini untuk Anda: membuat ulang instance fragmen, melampirkannya ke host, dan membuat ulang status data sebelumnya.

Secara default, FragmentManager menggunakan FragmentFactory yang disediakan oleh framework untuk membuat instance baru fragmen Anda. Factory default ini menggunakan refleksi untuk menemukan dan mengaktifkan konstruktor tanpa argumen untuk fragmen Anda. Artinya Anda tidak dapat menggunakan factory default ini untuk memberikan dependensi pada fragmen Anda. Hal ini juga berarti bahwa setiap konstruktor kustom yang Anda gunakan untuk membuat fragmen pertama kali tidak digunakan selama pembuatan ulang secara default.

Untuk memberikan dependensi pada fragmen Anda, atau untuk menggunakan konstruktor kustom, buat subclass FragmentFactory kustom, lalu ganti FragmentFactory.instantiate. Kemudian, Anda dapat mengganti factory default FragmentManager dengan factory kustom, yang kemudian digunakan untuk membuat instance fragmen.

Misalkan Anda memiliki DessertsFragment yang bertanggung jawab untuk menampilkan hidangan penutup populer di kota asal Anda, dan DessertsFragment memiliki dependensi pada class DessertsRepository yang memberinya informasi yang diperlukan untuk menampilkan UI yang benar kepada pengguna.

Anda dapat menentukan DessertsFragment untuk mewajibkan instance DessertsRepository dalam konstruktornya.

Kotlin

class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() {
    ...
}

Java

public class DessertsFragment extends Fragment {
    private DessertsRepository dessertsRepository;

    public DessertsFragment(DessertsRepository dessertsRepository) {
        super();
        this.dessertsRepository = dessertsRepository;
    }

    // Getter omitted.

    ...
}

Penerapan sederhana FragmentFactory Anda mungkin terlihat seperti berikut ini.

Kotlin

class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment =
            when (loadFragmentClass(classLoader, className)) {
                DessertsFragment::class.java -> DessertsFragment(repository)
                else -> super.instantiate(classLoader, className)
            }
}

Java

public class MyFragmentFactory extends FragmentFactory {
    private DessertsRepository repository;

    public MyFragmentFactory(DessertsRepository repository) {
        super();
        this.repository = repository;
    }

    @NonNull
    @Override
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
        Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className);
        if (fragmentClass == DessertsFragment.class) {
            return new DessertsFragment(repository);
        } else {
            return super.instantiate(classLoader, className);
        }
    }
}

Contoh ini menjadikan FragmentFactory subclass, dengan menggantikan metode instantiate() untuk memberikan logika pembuatan fragmen kustom untuk DessertsFragment. Class fragmen lainnya ditangani oleh perilaku default FragmentFactory hingga super.instantiate().

Kemudian, Anda dapat menetapkan MyFragmentFactory sebagai factory yang akan digunakan saat membuat fragmen aplikasi dengan menetapkan properti pada FragmentManager. Anda harus menetapkan properti ini sebelum super.onCreate() aktivitas untuk memastikan bahwa MyFragmentFactory digunakan saat membuat ulang fragmen.

Kotlin

class MealActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance())
        super.onCreate(savedInstanceState)
    }
}

Java

public class MealActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        DessertsRepository repository = DessertsRepository.getInstance();
        getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository));
        super.onCreate(savedInstanceState);
    }
}

Menetapkan FragmentFactory dalam aktivitas akan menggantikan pembuatan fragmen di seluruh hierarki fragmen aktivitas. Dengan kata lain, childFragmentManager setiap fragmen turunan yang Anda tambahkan menggunakan factory fragmen kustom yang ditetapkan di sini, kecuali jika diganti pada level yang lebih rendah.

Menguji dengan FragmentFactory

Dalam satu arsitektur aktivitas, uji fragmen Anda secara terpisah menggunakan class FragmentScenario. Karena Anda tidak dapat mengandalkan logika onCreate kustom dari aktivitas, Anda dapat meneruskan FragmentFactory sebagai argumen ke pengujian fragmen, seperti ditunjukkan dalam contoh berikut:

// Inside your test
val dessertRepository = mock(DessertsRepository::class.java)
launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment {
    // Test Fragment logic
}

Untuk informasi mendetail tentang proses pengujian ini dan contoh lengkapnya, lihat Menguji fragmen Anda.