[Kotlin Coroutine] Cuando DialogFragment se encuentra con Coroutine

Inserte la descripción de la imagen aquí
Hay muchas formas de implementar el diálogo de Android, la recomendación actual es evitar la desaparición de la rotación de pantalla en DialogFragmentcomparación con el uso directo AlertDialog. Pero su API basada en devolución de llamada no es fácil de usar. Afortunadamente, existen excelentes herramientas como RxJava, Coroutine, etc., podemos hacer algunos cambios en ellas.

Transformación basada en Coroutine + RxJava


build.gradle

dependencies {
    
    
    // 省略

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$latest_version"
    implementation 'io.reactivex.rxjava2:rxjava:$latest_version'
    implementation 'io.reactivex.rxjava2:rxkotlin:$latest_version'
    implementation 'io.reactivex.rxjava2:rxandroid:$latest_version'

    // 省略
}

kotlin {
    
    
    experimental {
    
    
        coroutines "enable"
    }
}

Heredar DialogFragment

class AlertDialogFragment : DialogFragment() {
    
    

    private val subject = SingleSubject.create<Int>()

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    
    
        val listener = {
    
     _: DialogInterface, which: Int ->
            subject.onSuccess(which)
        }

        return AlertDialog.Builder(activity)
                .setTitle("Title")
                .setMessage("Message")
                .setPositiveButton("Ok", listener)
                .setNegativeButton("Cancel", listener)
                .create()
    }

    suspend fun showAsSuspendable(fm: FragmentManager, tag: String? = null) = suspendCoroutine<Int> {
    
     cont ->
        show(fm, tag)
        subject.subscribe {
    
     it -> cont.resume(it) }
    }
}

utilizar

button.setOnClickListener {
    
    
    launch(UI) {
    
    
        val result = AlertDialogFragment().showAsSuspendable(supportFragmentManager)
        Log.d("AlertDialogFragment", "$result Clicked")
    }
}

Problema de rotación de pantalla


Cuando la pantalla gira, devolverá que el Listener configurado anteriormente no es válido. Siempre que comprenda el ciclo de vida de Fragmento y Actividad, sabrá la causa del problema:

  1. Cuando se gira la pantalla, se volverá a crear la actividad.
  2. onSaveInstanceState()El estado de DialogFragment se guardará en la Actividad antes de la muerte FragmentManagerState;
  3. Actividad reconstruida a onCreate()voluntad según savedInstanceStateadministrar FragmentManagerStateuna instancia de forma automática DialogFragment y show()fuera

En resumen, el proceso es el siguiente:

旋转屏幕-->-Activity.onSaveInstanceState()-->-Activity.onCreate()-->- DialogFragment.show()

La transformación de la corrutina permite que el DialogFragmentresultado se lea sincrónicamente, pero esencialmente convierte el código detrás de la suspensión en una devolución de llamada durante el período de compilación, lo que puede entenderse como establecer un Listener. El problema está llegando. Debido a la reconstrucción del Fragmento causada por las pantallas horizontal y vertical, el Oyente se pierde. En este momento, al hacer clic en el botón ya no se puede mostrar el registro esperado:

  Log.d("AlertDialogFragment", "$result Clicked")

Para esta situación, generalmente hay dos soluciones:

  1. Reinicie el oyente. Especialmente para el caso en el que el Oyente contiene una referencia a la Actividad del host (clase interna anónima o implementada por la Actividad), el cierre expira debido a la reconstrucción de la Actividad y el Oyente debe actualizarse
  2. Pass Listener argumentso savedInstanceStaterestaurar después de guardar

SubjectImplementamos el segundo método implementando un serializable

SerializableSingleSubject

Serializado SingleSubject, puede guardar su Subscriberestado interno y restaurarlo para usarlo

/**
 * implements Serializable并增加serialVersionUID
 */
public final class SerializableSingleSubject<T> extends Single<T> implements SingleObserver<T>, Serializable {
    
    
    private static final long serialVersionUID = 1L;

    final AtomicReference<SerializableSingleSubject.SingleDisposable<T>[]> observers;

    @SuppressWarnings("rawtypes")
    static final SerializableSingleSubject.SingleDisposable[] EMPTY = new SerializableSingleSubject.SingleDisposable[0];

    @SuppressWarnings("rawtypes")
    static final SerializableSingleSubject.SingleDisposable[] TERMINATED = new SerializableSingleSubject.SingleDisposable[0];

    final AtomicBoolean once;
    T value;
    Throwable error;

    // 省略

AlertDialogFragment

Basado en SerialzableSingleSubject, vuelva a implementar AlertDialogFragment:

class AlertDialogFragment : DialogFragment() {
    
    

    private var subject = SerializableSingleSubject.create<Int>()

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        savedInstanceState?.let {
    
    
            subject = it["subject"] as SerializableSingleSubject<Int>
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    
    
        val listener = {
    
     _: DialogInterface, which: Int ->
            subject.onSuccess(which)
        }

        return AlertDialog.Builder(activity)
                .setTitle("Title")
                .setMessage("Message")
                .setPositiveButton("Ok", listener)
                .setNegativeButton("Cancel", listener)
                .create()
    }

    override fun onSaveInstanceState(outState: Bundle?) {
    
    
        super.onSaveInstanceState(outState)
        outState?.putSerializable("subject", subject);
    }

    suspend fun showAsSuspendable(fm: FragmentManager, tag: String? = null) = suspendCoroutine<Int> {
    
     cont ->
        show(fm, tag)
        subject.subscribe {
    
     it -> cont.resume(it) }
    }
}

Después de la reconstrucción, se savedInstanceStaterestaura el Subject/ anterior Subscriberpara garantizar que el clic sea válido.


Versión RxJava


Por supuesto, también puedes salir de la corrutina y solo usar RxJava

fun showAsSingle(fm: FragmentManager, tag: String? = null): Single<Int> {
    
    
    show(fm, tag)
    return subject.hide()
}

Cuando se usa, subscribe()la llamada de la función suspendida se reemplaza por

button.setOnClickListener {
    
    
    AlertDialogFragment().showAsSingle(supportFragmentManager).subscribe {
    
     result ->
        Log.d("AlertDialogFragment", "$result Clicked")
    }
}

Supongo que te gusta

Origin blog.csdn.net/vitaviva/article/details/107744388
Recomendado
Clasificación