Kotlin Avanzado
> Sesión 04
> Ejemplo 1
-
Emitir una señal para un receiver específico
-
Emitir una señal implícita desde una aplicación
-
Escuchar señales explícitas y explícitas
-
Implementar callbacks al recibir emisiones
Un Broadcast Receiver es una clase que recibe una emisión hecha por una aplicación o por parte del sistema (por ejemplo, un aviso de batería baja) a partir de un Intent, estas emisiones las podemos interpretar como eventos. Para poder utilizarlos, hay que crear una clase que herede de este elemento y registrarlo en un componente de la app para que comience a recibir los mensajes.
Nuestro primer receiver escuchará un evento detonado desde nuestra misma aplicaciǿn.
Para esto, creamos un botón para detonar la emisión.
<androidx.constraintlayout.widget.ConstraintLayout ...>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enviar emisión"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Ahora, definiremos nuestro receiver; para esto, crearemos una subclase de BroadcastReceiver que implemente el método onReceive(), dicho método se detona después de recibir una señal correspondiente y aquí es donde reaccionaremos a dicho evento. En nuestro ejemplo, obtendremos del evento los parámetros NAME y EMAIL mediante un bundle, como si de una comunicación entre activities se tratase. La información será mostrada en nuestro siempre confiable Toast.
class ReceiverOne : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val bundle = intent.extras
val name = bundle?.getString("NAME")
val email = bundle?.getString("EMAIL")
Toast.makeText(context,"$name $email",Toast.LENGTH_SHORT).show()
}
}
En nuestro archivo Manifest haremos la declaración estática de nuestro receiver creando nuestro tag receiver . Este recibirá como atributo el nombre de la clase relacionada a este y contendrá un intent filter, que determinaa qué tipo de intent responderá nuestro componente, y se definirá a través de una llave que permita identificar el evento cuando sea emitido que en este caso, será "org.bedu.actions.SALUDO". Consulta Este enlace para visualizar todos sus atributos.
<receiver
android:name=".ReceiverOne"
android:enabled="true"
android:exported="true">
<intent-filter>
<action
android:name="org.debu.actions.SALUDO"/>
</intent-filter>
</receiver>
Finalmente, para poder recibir la señal, hemos de detonarla mediante un Intent y con apoyo del método sendBroadcast() dentro del listener de nuestro botón. Hay qué recordar que como en el receiver obtenemos info extra, tenemos qué declararla en nuestro Intent.
// Esta es nuestra información extra a enviar
val bundle = Bundle().apply {
putString("NAME","Pedro")
putString("EMAIL","[email protected]")
}
// Creamos un Intent con nuestro bundle como extra y detonamos el evento con sendBroadcast()
Intent(this,ReceiverOne::class.java).apply {
putExtras(bundle)
}.let(::sendBroadcast)
si la sintaxis anterior (específicamente en el último bloque) no te cuadra, te mostramos el código equivalente sin scope functions
val intent = Intent(this,ReceiverOne::class.java)
intent.putExtras(bundle)
sendBroadcast(intent)
La pantalla debe verse así al pulsar el botón:
Si lo requieres, puedes dar un repaso al tema de Scope Functions en la documentación oficial y para ejemplos más detallados, en este blog.
Ya escuchamos emisiones desde la propia aplicación, pero el sistema operativo también puede arrojar varias emisiones correspondientes a eventos que suceden en el sistema, algunos de esto son:
- ACTION_DATE_CHANGED (al cambiar la fecha del sistema)
- DATA_SMS_RECEIVED_ACTION (Al recibir un SMS)
- ACTION_WIFI_AWARE_STATED_CHANGED
- ACTION_BATTERY_LOW (Batería baja)
- ACTION_BATTERY_OKAY (Batería con carga considerable)
Para consultar todas las acciones disponibles para la API 30 consulta aquí, ten en cuenta que pueden haber algunas incompatibilidades si las acciones se prohibieron en esta o en anteriores APIs.
En este ejemplo utilizaremos el aviso de modo avión.
Primero crearemos nuestro receiver y luego lo registraremos (forzosamente dinámico porque la llamada será implícita).
Creamos un nuevo receiver
class AirplaneReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
}
de acuerdo a la documentación, el nombre de la acción para el modo avión es ACTION_AIRPLANE_MODE_CHANGED y nos avisa que su estado cambió, para obtener el valor del estado, hay qué extraer la propiedad state que es un boolenao, por lo que lo extraeremos e imprimiremos su estado en un Toast.
val airplaneState = intent?.let {
if(it.getBooleanExtra("state",false)) "Prendido" else "Apagado"
}
Toast.makeText(context,
"Modo avión $airplaneState",
Toast.LENGTH_SHORT)
.show()
}
Creamos un miembro en nuestro Activity para nuestro receiver:
private val airplaneReceiver = AirplaneReceiver()
Ahora lo registraremos. Como se desea que el mensaje se muestre aun cuando la app está en segundo plano debido a que abriremos nuestra menú de opciones, el registro sucederá en onCreate.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
IntentFilter().apply {
addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}.also { filter -> registerReceiver(airplaneReceiver,filter) }
}
Y le damos de baja en onDestroy
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(airplaneReceiver)
}
De esta forma, al prender o apagar el modo avión, la app debe hacer lo siguiente:
También debe funcionar con la aplicación minimizada (en segundo plano):