DialogFragment melalui perubahan konfigurasi dan kematian proses.

Nov 29 2022
Hai semuanya, Sudah lama saya menggunakan media untuk terus belajar hal-hal baru dan sekarang saatnya saya menulis artikel pertama saya. Mungkin awal dari banyak lainnya.

Halo semuanya,

Sudah lama saya menggunakan media untuk terus belajar hal-hal baru dan sekarang saatnya saya menulis artikel pertama saya. Mungkin awal dari banyak lainnya.

pengantar

Mengapa saya akan berbicara tentang DialogFragment terkait dengan perubahan konfigurasi dan kematian proses? Karena kami peduli dengan pengalaman pengguna dan kami tidak menyukai laporan kerusakan atau perilaku aneh saat terjadi kesalahan.

Artikel ini tentang DialogFragment tetapi refleksinya sama untuk semua Fragmen.

Untuk melakukan perubahan konfigurasi

Cara mudah untuk melakukan "perubahan konfigurasi" di aplikasi Anda adalah beralih antara mode terang/gelap. Singkatnya, Fragmen/Aktivitas akan dibuat ulang dengan argumen tersimpan dan storedInstanceState tetapi ViewModels akan tetap ada. Dalam kematian proses, ViewModels, Aktivitas, dan Fragmen semuanya akan dibuat ulang.

Untuk memprovokasi kematian proses

- Opsi pengembang (cara mudah): Opsi pengembang -> Jangan simpan aktivitas -> aktifkan.
- Opsi situasi nyata: jalankan banyak aplikasi serakah atau buat aplikasi Anda sendiri yang akan menghabiskan banyak memori (menggunakan loop misalnya) hingga perangkat memperkirakan bahwa aplikasi yang dibuka sebelumnya harus dibebaskan.

Setiap kali Anda kembali ke aplikasi apa pun yang telah dibebastugaskan, itu akan diluncurkan kembali dengan argumen / storedInstanceState stored. Yang ini harus menjadi satu-satunya sumber data kebenaran yang harus Anda perhatikan.

Itu selalu merupakan praktik yang baik untuk menguji setiap layar di aplikasi Anda menggunakan perubahan konfigurasi / kematian proses untuk memastikan semuanya berfungsi seperti yang diharapkan. Jika Anda mengalami crash atau perilaku aneh tentang itu, jangan abaikan karena itu 'langka' ...
Selain itu, untuk beberapa perangkat yang mencapai kurang dari 15% masa pakai baterai, perangkat akan secara otomatis beralih ke mode gelap untuk menghemat baterai yang berarti bahwa pengguna dapat melakukan beberapa tindakan acak di aplikasi Anda dan aplikasi Anda tiba-tiba dapat dibuat ulang karenanya.

Yah, saya memperkenalkan beberapa berita buruk, tolong jangan tinggalkan artikelnya :(

Jika saya meluangkan waktu untuk menjelaskan masalah ini, itu karena ada banyak artikel mengkilap di sini yang menjelaskan cara menulis beberapa kode cantik di kotlin menggunakan DSL dengan parameter fungsi yang dikirim langsung ke dialog.
Saya bukan orang baru dalam pengembangan android dan terlepas dari kenyataan bahwa Kotlin adalah bahasa yang luar biasa, saya pasti menyukainya, beberapa artikel harus mengurus perubahan konfigurasi atau kematian proses, jika tidak, mereka tidak melayani pengembang junior.

Hal-hal yang dikatakan

1°) Sebuah fragmen (dan tentu saja DialogFragment) harus selalu memiliki konstruktor kosong. Cobalah beralih ke mode gelap/terang saat sebuah fragmen memiliki konstruktor dengan parameter; itu tidak berakhir dengan baik.

Selain itu, saat menavigasi ke DialogFragment, jangan teruskan fungsi ke fungsi ini melalui fungsi yang ditentukan dalam DialogFragment Anda yang menyimpannya dalam variabel lokal karena saat mengubah konfigurasi, fragmen akan dipulihkan tetapi tanpa memanggil fungsi ini; jadi variabel lokal Anda tidak akan diinisialisasi. Hanya konstruktor kosong dan fungsi yang diganti (seperti onCreate, onCreateDialog, dll.) dari DialogFragment yang akan dipanggil. Argumen dan storedInstanceState seharusnya hanya menjadi perhatian Anda.

2°) Jangan meneruskan fungsi sebagai argumen ke fragmen Anda melalui sebuah bundel. Suatu fungsi tidak dapat dibagi/diserialkan, aplikasi akan mogok saat perubahan konfigurasi.

Meneruskan fungsi ke fragmen mana pun adalah praktik yang sangat buruk; Selain itu dapat menyebabkan kebocoran memori jadi jangan lakukan itu. Saya tahu, mudah untuk mengatakan: ' Hei, jika Anda mengklik sukses, buka layar X ' dengan lambda. Saya melakukannya juga di awal pengembangan android, tetapi percayalah, Anda tidak ingin memberikan produk yang tidak stabil kepada pengguna Anda dalam produksi.

3°) Menggunakan ViewModel bersama? Tidak ! Pertama, karena kematian proses dan kemudian karena Anda tidak ingin pusing saat men-debug mengapa itu tidak berfungsi dalam beberapa kasus. Model tampilan bersama adalah konsep yang paling rawan kesalahan. Ini bisa berguna dalam beberapa kasus tetapi paling banyak, jangan gunakan itu.

Larutan

Lakukan permintaan ke DialogFragment Anda, tunggu hasilnya, lalu lakukan sesuatu tergantung pada hasil yang diterima.

Kasus penggunaan sederhana:

Anda ingin menerima jawaban dari pengguna Anda untuk menyetujui tindakan tertentu. Tindakan 'Oke' menyiratkan bahwa Anda akan menavigasi ke layar berikutnya dengan data spesifik yang terkait dengan input pengguna.
Dalam kasus penggunaan sederhana kami, tindakan pengguna dapat berupa: 'Ok', 'Batalkan' (2 Tombol UI) dan 'Tutup' (ikon belakang UI atau tekan kembali sistem).
Namun secara sederhana, kami akan mempertimbangkan bahwa 'Tutup' ditangani sebagai tindakan 'Batalkan'. Jadi interaksi pengguna terbatas pada dua jenis interaksi: 'Ok' atau 'Batal'.

Kami berpotensi meminta beberapa kali jenis DialogFragment yang sama dalam satu atau beberapa fragmen bergantung pada banyak konteks. Itu sebabnya kami akan memberikan 'requestKey' yang, pada umumnya, unik untuk fragmen tertentu. Satu-satunya hal yang kami inginkan adalah jawaban atas apa yang kami minta: 'Ok' atau 'Batal'.

Idenya adalah memancarkan 'requestKey' ke DialogFragment dan menunggu tindakan pengguna: 'Oke' atau 'Cancel' untuk 'requestKey' ini.

Ada banyak cara untuk berkomunikasi antara fragmen dialog Anda dan fragmen yang melakukan permintaan.

Saya pribadi menggunakan:

- setFragmentResult dari DialogFragment
- setFragmentResultListener di onCreate() dari Fragmen Anda yang melakukan permintaan

Fungsi setFragmentResult memiliki dua parameter: 'requestKey: String' dan 'result: Bundle'. Fungsi ini menyebarkan data melalui parentFragmentManager yang berarti bahwa setiap fragmen yang meminta dengan requestKey yang sama rentan untuk menangkap hasilnya.

Di bawah ini adalah beberapa kode 'disederhanakan' dengan menggunakan ekstensi fungsi (dengan begitu, kami hanya fokus pada apa yang benar-benar penting dan kami dapat dengan mudah memahami siapa yang memanggil apa. Dalam praktiknya, tentu saja, salin/tempel fungsi tubuh dari fungsi fragmen/dialogfragmen yang ada):

enum class UserAction {
    OK, CANCEL
}

/*******************
 * DIALOG FRAGMENT *
 *******************/

/**
 * In your DialogFragment, the contract mentions that REQUEST_KEY_ARG_KEY is a mandatory arg.
 * You will notice that it works for Fragment too, not only DialogFragment
 * In our case, a device back press or UI back press will invoke onDismiss(), just override it and call this function
 * @param userAction 'Yes' or 'Cancel'
 */
fun DialogFragment.deliverUserAction(userAction: UserAction) {
    setFragmentResult(requireArguments().getString(REQUEST_KEY_ARG_KEY)!!,
                      bundleOf(USER_ACTION_RESULT_KEY to userAction,
                               REQUEST_DATA_BUNDLE_KEY to requireArguments().getBundle(REQUEST_DATA_BUNDLE_KEY)))
}

/**
 * Defined in public in your DialogFragment
 */
const val REQUEST_KEY_ARG_KEY = "request_key" // Mandatory in arg
const val REQUEST_DATA_BUNDLE_KEY = "request_data" // Optional in arg and result, it is just a way to forward contextual data
const val USER_ACTION_RESULT_KEY = "user_action" // Mandatory in result

/***********************************
 * FRAGMENT REQUESTING USER ACTION *
 ***********************************/

/**
 * Listen to a user action
 * Of cource, use it in real override onCreate function
 */
fun Fragment.fakeOverrideOnCreate() {
    setFragmentResultListener(REQUEST_KEY_TO_GO_TO_SCREEN_B) { _, bundle: Bundle ->
        // Called once in Lifecycle.State.ON_START
        val userAction: UserAction = bundle.getSerializable(USER_ACTION_RESULT_KEY) as UserAction // We know at this point that we will get an user action
        val requestData: Bundle? = bundle.getBundle(REQUEST_DATA_BUNDLE_KEY) // Optional because we don't necessary need data at this point for some usecase
        when (userAction) {
            UserAction.OK -> {
                TODO("Do something with ${requestData} if you need it to navigate to next screen")
            }
            UserAction.CANCEL -> TODO()
        }
    }
}

/**
 * Request a user action
 * @see fakeOverrideOnCreate directly related to it
 * @param requestKey requestKey is mandatory
 * @param requestData optional, some data you may need to forward to dialog fragment and get back in setFragmentResultListener
 */
fun Fragment.navigateToMyDialogFragmentX(requestKey: String, requestData: Bundle?) {
    bundleOf(
        REQUEST_KEY_ARG_KEY to requestKey,
        REQUEST_DATA_BUNDLE_KEY to requestData
    )
    // etc.
    // depending on if you are using Navigation component or not
}

fun Fragment.calledAnywhereInFragmentRequestingUserAction1() {
    navigateToMyDialogFragmentX(requestKey = REQUEST_KEY_TO_GO_TO_SCREEN_B,
                                requestData = bundleOf("input1" to "Here", "input2" to "we", "input3" to "go!"))
}

/**
 * Defined in private in your fragment requesting user action
 */
private const val REQUEST_KEY_TO_GO_TO_SCREEN_B = "request_key_to_go_to_screen_B"

Di bawah ini adalah beberapa peningkatan menggunakan aliran

/***************************************
 * YOURFRAGMENT REQUESTING USER ACTION *
 ***************************************/

private val _userActionResultMutableStateFlow: MutableStateFlow<Pair<String, Bundle>?> = MutableStateFlow(null)
private val userActionResultStateFlow: StateFlow<Pair<String, Bundle>?> get() = _userActionResultMutableStateFlow

/**
 * Listen to a user action
 * Of cource, use it in real override onCreate function
 */
fun Fragment.fakeOverrideOnCreate() {
    setFragmentResultListener(REQUEST_KEY_TO_GO_TO_SCREEN_B) { requestKey, bundle: Bundle ->
        _userActionResultMutableStateFlow.value = requestKey to bundle
    }
}

/**
 * Listen to a user action and consume it only when fragment is the currentBackStackEntry
 * Of cource, use it in real override onViewCreated function
 */
fun Fragment.fakeOverrideOnViewCreated() {
    viewLifecycleOwner.lifecycleScope.launch {
        combine((findNavController().currentBackStackEntryFlow), userActionResultStateFlow) { currentBackStackEntry, userActionResult ->
            if (currentBackStackEntry.destination.id in R.id.YOURFRAGMENT_DEFINED_IN_NAV_GRAPH) {
                userActionResult
            } else {
                null
            }
        }.filterNotNull().collect { (requestKey, bundle) ->
            consumeUserActionResult(requestKey, bundle)
            _userActionResultMutableStateFlow.value = null // don't forget to notify that user action has been consumed
        }
    }
}

/**
 * consume user action result
 */
fun consumeUserActionResult(requestKey: String,
                            bundle: Bundle) {
    when (requestKey) {
        REQUEST_KEY_TO_GO_TO_SCREEN_B -> {
            val userAction: UserAction = bundle.getSerializable(USER_ACTION_RESULT_KEY) as UserAction // We know at this point that we will get an user action
            val requestData: Bundle? = bundle.getBundle(REQUEST_DATA_BUNDLE_KEY) // Optional because we don't necessary need data at this point for some usecase
            when (userAction) {
                UserAction.OK -> {
                    TODO("Do something with ${requestData} if you need it to navigate to next screen")
                }
                UserAction.CANCEL -> TODO()
            }
        }
    }
}

Anda akan melihat bahwa ada banyak kode untuk tidak terlalu banyak. Selain itu, Anda dapat berpikir bahwa Anda perlu menduplikasi kode boilerplate ini pada setiap fragmen menggunakan fragmen dialog Anda. Iya dan tidak. Delegasi adalah alat yang ampuh dan memungkinkan Anda menulis kode ini sekali saja. Beri tahu saya jika Anda tertarik dengan peningkatan seperti itu untuk ditambahkan dalam artikel ini atau tidak.

Jika Anda mempelajari sesuatu atau jika Anda sudah terbiasa tetapi merasa bahwa orang lain harus mengetahuinya, jangan ragu untuk bertepuk tangan :).

Saya suka berbagi ilmu dan jika ada yang kurang paham atau ada yang salah tulis, jangan sungkan untuk berkomentar di bawah. Saya akan menjawabnya dan saya akan melakukan perubahan jika terjadi kesalahan.

Hanya untuk berbagi, beberapa orang menarik yang saya sarankan untuk Anda pelajari bersama:

Jake Wharton
- Twitter:https://twitter.com/JakeWharton
- Sedang:https://medium.com/@techyourchance
Vasiliy Zukanov
- Twitter :https://twitter.com/VasiliyZukanov
- Sedang :https://medium.com/@techyourchance
Christophe Beyls
- Twitter:https://twitter.com/BladeCoder
- Sedang:https://medium.com/@bladecoder
Gabor Varadi
- Twitter:https://twitter.com/Zhuinden
- Sedang:https://medium.com/@zhuinden

Catatan: Saya tidak tahu secara pribadi orang-orang ini, saya hanya mengikuti mereka karena mereka baik dan aktif di Twitter.