Comunicación bidireccional entre Fragment y Activity en Android

Cuando uno empieza a usar Fragments en Android uno de los problemas que se suele encontrar es cómo comunicar un evento ocurrido en el Fragment al Activity y viceversa. Es una de las dudas que más a menudo me encuentro en Stackoverflow tanto en español como en inglés.

Resolver esta necesidad es fácil y puede hacerse de múltiples maneras, unas más eficientes que otras. Muchas veces lo que se hace es pasar una referencia del Activity al Fragment y realizar las llamadas necesarias a partir de esa referencia pero esto tiene un problema y es que suele producir fugas de memoria ya que no es recomendable mantener referencias a objetos que albergan el contexto de la aplicación, otra de ellas es utilizar los observables de la librería RxJava, muy útil y eficiente pero dependemos de una librería externa que utilizada para este único acometido puede ser exagerado, la que vamos a comentar hoy aquí utiliza una interfaz que hará de intermediario entre el Activity y el Fragment, de forma nativa, sin librerías externas.

El Activity se comunicará con el Fragment que contiene a través de una referencia al mismo, el Fragment se comunicará con el Activity a través del interfaz implementado por el Activity y que le pasará en algún momento, ya sea en la propia llamada al método, al crear el Fragment o al adjuntarlo, en este caso lo haremos al adjuntarlo.

Comunicación entre Activity y Fragment

El proyecto que he creado de ejemplo está publicado en mi Github para ello he utilizado Kotlin como lenguaje pero esto mismo puede realizarse perfectamente con Java simplemente adaptando la sintáxis. Lo que hace es generar un número aleatorio que se muestra en el Fragment a partir de un límite inferior y otro superior introducidos utilizando dos EditText ubicados en el Activity, uno para cada límite del rango, al generar el número aleatorio el Fragment además de mostrarlo en un TextView lo comunicará al Activity. Para actualizar el número se utiliza como botón el Floating Action Button de la esquina inferior derecha.

El código aquí expuesto no se corresponde 100% al publicado en el repositorio de Github ya que en él se utiliza un controlador para gestionar toda la funcionalidad tanto del Activity como del Fragment, pero la idea es la misma.

La parte de comunicación Activity -> Fragment es super sencilla, al inicializar nuestro Fragment guardamos la referencia y ya podremos, a través de ella, comunicarnos con él.

...
    val mainFrag: MainFragment = MainFragment()
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        supportFragmentManager.beginTransaction().add(contenedor.id, mainFrag).commit()
        fab.setOnClickListener { view ->
            MainActivityController.actualizarNumeroAleatorio(mainFrag, etMin, etMax)
        }
        ...
    }
...

Como vemos no hace falta más que inicializar el Fragment, en este caso se le ha llamado MainFragment y está contenido dentro del Activity principal, una vez inicializado, conservamos su referencia en la variable mainFrag para poder utilizarla en cualquier momento dentro del propio Activity.

Para realizar la comunicación inversa como hemos dicho antes vamos a utilizar un interfaz, ya que pasar la referencia del Activity al Fragment sería un error al producir filtrados de memoria, nunca deberíamos mantener la referencia a un objeto que almacene el contexto de la aplicación ya que nos conducirá a errores inesperados.

Lo primero es crear el interfaz ya sea en una clase aparte o en el propio Fragment.

    interface OnNumeroAleatorio {
        fun actualizado(numero: Int)
    }

Una vez creada la interfaz vamos a establecer para que todo Activity que utilice este Fragment tenga la obligación de implementarla y la recogemos automáticamente, en caso de no implementarla se lanzará una excepción.

    var onNumeroAleatorio: OnNumeroAleatorio? = null
    override fun onAttach(context: Context?) {
        super.onAttach(context)
        if (context is OnNumeroAleatorio) {
            onNumeroAleatorio = context
        } else {
            throw RuntimeException(context!!.toString() + " debe implementar OnNumeroAleatorio")
        }
    }

De esta forma al adjuntar el Fragment se comprobará si el Activity tiene la interfaz implementada, en caso de no tenerla se produce error, si la tiene se recoge y se conserva para utilizarla después.

class MainActivity : AppCompatActivity(), MainFragment.OnNumeroAleatorio {
    override fun actualizado(numero: Int) {
        Snackbar.make(fab, getString(R.string.numero_aleatorio_actualizado, numero.toString()), Snackbar.LENGTH_LONG).show()
    }
}

Como ya tenemos en nuestro Fragment la referencia a la interfaz implementada por el Activity y el método sobrescrito ya podemos comunicarnos con el Activity.

    fun actualizarNumero(min: Int, max: Int) {
        val numeroAleatorio: Int = Aleatorio.generar(min, max)
        tvNumeroAleatorio.text = "$numeroAleatorio"
        onNumeroAleatorio?.actualizado(numeroAleatorio)
    }

Este método del Fragment se encarga de generar el número aleatorio, mostrarlo en el TextView y comunicar al Activity que el número ha sido actualizado.

Ahora sólo nos faltaría eliminar la referencia a la interfaz al excluir el Fragment

    override fun onDetach() {
        super.onDetach()
        onNumeroAleatorio = null
    }

Así podemos establecer de forma nativa una comunicación bidireccional entre un Activity y uno o varios Fragment sin necesidad de librerías externas y sin problemas de fugas de memoria. Recomiendo el uso de RxJava si se va a aprovechar o es necesaria gran parte de su funcionalidad, en caso contrario el uso de interfaces es más que suficiente.

Espero sea de utilidad, no dudes en dejar abajo cualquier duda o comentario

Relacionado:

 

By | 2017-11-26T14:38:29+00:00 noviembre 26th, 2017|Android, Android, Desarrollo, Informática|0 Comments

About the Author:

Desarrollador de software de profesión, apasionado de la informática y todo lo relacionado con la tecnología

Leave A Comment