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. Dengan begitu, aplikasi apa pun yang menggunakan fragmen juga menggunakan FragmentManager pada tingkat tertentu, jadi penting untuk memahami arti dan cara kerjanya.

Topik ini mencakup cara mengakses FragmentManager, peran FragmentManager dalam kaitannya dengan aktivitas dan fragmen, mengelola data sebelumnya dengan FragmentManager, serta menyediakan data dan dependensi pada fragmen Anda.

Mengakses FragmentManager

Mengakses dalam aktivitas

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

Mengakses dalam Fragmen

Fragmen juga 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().

Mari kita lihat beberapa contoh untuk mengetahui hubungan antara fragmen, host, 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, dengan 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 dapat terdiri atas satu atau sejumlah kecil aktivitas dalam project aplikasi, dengan setiap aktivitas mewakili sekelompok layar terkait. Aktivitas dapat memberikan titik untuk menempatkan navigasi tingkat atas dan tempat untuk mencakup ViewModels dan status tampilan lainnya di antara fragmen. Setiap destinasi individu di aplikasi Anda harus diwakili oleh fragmen.

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

Kasus penggunaan lainnya untuk fragmen turunan dapat mencakup hal berikut:

  • Slide layar, dengan 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 menekan tombol Kembali di perangkat mereka, atau saat Anda memanggil FragmentManager.popBackStack(), transaksi fragmen paling atas akan dihapus dari tumpukan. Dengan kata lain, transaksi dibalik. 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, perhatikan bahwa transaksi dapat menyertakan sejumlah operasi, seperti menambahkan beberapa fragmen, mengganti fragmen dalam beberapa penampung, dan sebagainya. Saat data sebelumnya muncul, semua operasi ini akan dikembalikan sebagai tindakan atom tunggal. Jika Anda telah melakukan transaksi tambahan sebelum panggilan popBackStack(), dan jika Anda tidak menggunakan addToBackStack() untuk transaksi tersebut, operasi ini tidak akan dibalik. 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 informasi selengkapnya, lihat Menyediakan dependensi.

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. Pengguna nantinya dapat membalikkan transaksi dan mengembalikan fragmen sebelumnya dengan menekan 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, maka fragmen yang dihapus akan dihancurkan saat transaksi dilakukan, dan pengguna tidak dapat menavigasi kembali ke fragmen tersebut. Jika Anda memanggil addToBackStack() saat menghapus fragmen, maka fragmen tersebut hanya STOPPED dan RESUMED setelahnya saat pengguna menavigasi kembali. Perhatikan bahwa tampilannya dihancurkan dalam kasus ini. Untuk 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 diizinkan untuk mengontrol data sebelumnya pada fragmen di waktu tertentu. Jika aplikasi Anda menampilkan beberapa fragmen yang setara di layar secara bersamaan, atau jika aplikasi Anda menggunakan fragmen turunan, maka satu FragmentManager harus 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 harus memiliki kontrol utama.

Pertimbangkan struktur navigasi sebagai serangkaian lapisan, dengan aktivitas sebagai lapisan terluar, yang menyelimuti setiap lapisan fragmen turunan di bawahnya. Setiap lapisan harus 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.

Perhatikan bahwa saat dua atau beberapa fragmen ditampilkan secara bersamaan, hanya satu yang dapat menjadi 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 data lainnya.

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, anggap saja Anda telah menambahkan fragmen ke data sebelumnya dengan menyatukan FragmentTransaction menggunakan addToBackStack():

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 saveState():

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 akan menangani semuanya untuk Anda, FragmentManager membuat ulang instance fragmen, melampirkannya ke host, lalu 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, Anda harus membuat subclass FragmentFactory kustom, lalu mengganti FragmentFactory.instantiate. Kemudian, Anda dapat mengganti factory default FragmentManager dengan factory kustom Anda, yang kemudian digunakan untuk membuat instance fragmen Anda.

Misalkan Anda memiliki DessertsFragment yang bertanggung jawab untuk menampilkan makanan penutup populer di kota asal Anda. Anggaplah DessertsFragment memiliki dependensi pada class DessertsRepository yang menyediakan informasi yang dibutuhkan 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);
    }
}

Perhatikan bahwa penetapan 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 arsitektur aktivitas tunggal, Anda harus menguji fragmen 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 yang ditunjukkan dalam contoh berikut:

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

Untuk informasi detail tentang proses pengujian ini dan contoh lengkapnya, lihat Menguji Fragmen aplikasi Anda.