Compartir un Pokémon
Al terminar: Generar una URL que funcione fuera de la app y ofrecerla con el diálogo nativo de cada sistema.
Una ficha que no se puede enseñar a nadie está a medias. Al terminar esta lección, cada Pokémon tiene un botón de compartir arriba a la derecha y sale del móvil como un enlace que abre la app de quien lo reciba —o una página decente, si no la tiene—.
La parte interesante no es el botón. Es que las dos plataformas resuelven lo mismo con formas opuestas: en iOS compartir es una vista, en Android es un Intent y un selector. Y en medio aparece un detalle de interop que cuesta encontrar la primera vez.
Paso 1 · Cuál de los dos enlaces se comparte
Se comparte el https, y no el pokedex://. La razón es concreta: un esquema propio se queda en texto plano en la mayoría de apps de mensajería, así que el que lo recibe no tiene nada que tocar (no se convierte en enlace pulsable).
Con el https pasan las dos cosas buenas a la vez. Quien tiene la app podría abrirla (si el dominio estuviera verificado, como viste en Deep links en Android) y quien no la tiene cae en el navegador, en la página del curso que le explica qué era eso.
Paso 2 · Android: construir el Intent y pedir el selector
El archivo. PokemonDetailScreen.kt es la ficha que copiaste en Navegación en Android con Navigation 3. La versión nueva trae, además del botón, los import que necesita y mueve el número de la esquina (a la izquierda del botón nuevo). Es casi toda presentación, así que se copia: abre el fichero con Navigate → File… (Cmd+Shift+O, vista Project), pulsa Copiar en el recuadro de abajo, selecciona todo (Cmd+A) y pega encima (Cmd+V).
Lo que cambia respecto al fichero que tenías, exactamente tres cosas: cinco import nuevos (Intent, Share, LocalContext, Destination y toShareUrl); el botón de compartir, nuevo, justo debajo del de volver; y el modifier del texto «N.º», que pasa de padding(20.dp) a padding(top = 26.dp, end = 68.dp).
Lo que añade. Dentro de Ficha, junto al botón de volver, está el de compartir. Este es el trozo, con cada línea explicada:
// El Context hace falta para lanzar una pantalla de otra app.
val contexto = LocalContext.current
FilledTonalIconButton(
onClick = {
// El enlace https de este Pokémon: `Destination` → texto.
val enlace = Destination.Detalle(detalle.id).toShareUrl()
// Nombre + enlace: un enlace pelado no dice qué es.
val texto = "${detalle.name.replaceFirstChar { it.uppercase() }} · $enlace" // la primera letra en mayúscula
contexto.startActivity(
// El selector del sistema: la app no decide por el usuario; elige él.
Intent.createChooser(
Intent(Intent.ACTION_SEND).apply {
type = "text/plain" // tipo MIME: solo texto
putExtra(Intent.EXTRA_TEXT, texto) // lo que se comparte
},
null, // título: que ponga el suyo el sistema
),
)
},
// Arriba a la derecha, bajo la barra de estado.
modifier = Modifier.statusBarsPadding().padding(12.dp).align(Alignment.TopEnd),
) {
Icon(Icons.Default.Share, contentDescription = "Compartir")
}El .align(Alignment.TopEnd) del modificador solo existe dentro de un Box (es un modificador de BoxScope): por eso el botón va en el mismo Box que el de volver y el número, y no en una Column. Lo demás ya lo hiciste en Marcador (text/plain, createChooser, EXTRA_TEXT); lo propio de aquí es que el texto lleva el nombre y el enlace, y que el botón se coloca con .align.
Paso 3 · iOS: ShareLink es una vista
ShareLink es lo nuevo de SwiftUI en esta lección. Un ejemplo ajeno al proyecto, con una receta, para verlo sin Pokémon por medio:
import SwiftUI
struct RecetaView: View {
let nombre = "Tortilla de patatas"
// El enlace que se comparte. `URL(string:)` da un opcional; el `!` lo abre
// porque el texto lo escribimos nosotros y sabemos que es una URL válida.
let enlace = URL(string: "https://recetas.example/tortilla")!
var body: some View {
NavigationStack {
Text(nombre)
.navigationTitle("Receta")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
// Una vista: pinta el icono de compartir y, al tocarlo,
// abre la hoja del sistema con el enlace. No hay acción que escribir.
ShareLink(
item: enlace, // lo que se comparte
subject: Text(nombre), // asunto (lo usa el correo)
message: Text("Mira esta receta") // mensaje (donde se use)
)
}
}
}
}
}
#Preview { RecetaView() }Es un ejemplo para leer, no para añadir a la Pokédex. Si quieres verlo, pégalo en un archivo nuevo de tu carpeta ejercicios (o léelo sin más): en la vista previa verías la barra con «Receta» y, a la derecha, el icono de compartir (el cuadrado con la flecha); en la vista previa interactiva, al tocarlo se abre la hoja de compartir.
El archivo. PokemonDetailView.swift es la ficha de iOS de Navegación en iOS con NavigationStack. Igual que en Android, se sustituye por la versión con el botón: ábrelo en Xcode (iosApp → iosApp → Detail; en disco, iosApp/iosApp/Detail/PokemonDetailView.swift), pulsa Copiar en el recuadro de abajo, selecciona todo (Cmd+A) y pega encima (Cmd+V).
Lo que cambia respecto al fichero que tenías: se añade, al final de la Ficha, el bloque .toolbar { … } de abajo (después de .navigationBarTitleDisplayMode(.inline)), y el número de la ficha no se mueve: sigue abajo a la derecha, como estaba.
Lo que añade. No hay acción que escribir: se le da el enlace y SwiftUI monta la hoja del sistema. Va en la barra de navegación, en la ficha:
.toolbar {
// Arriba a la derecha de la barra de navegación.
ToolbarItem(placement: .topBarTrailing) {
// `ShareLink` es una vista, no una acción: no hay Intent ni selector.
ShareLink(
// El enlace https, convertido en URL (el `!` es seguro: lo construimos nosotros).
item: URL(string: DestinationKt.toShareUrl(DestinationDetalle(pokemonId: detalle.id)))!, // (el paso 4 explica por qué no se escribe con punto)
subject: Text(detalle.name.capitalized), // asunto, para el correo; `capitalized` pone la primera letra en mayúscula
message: Text("Mira este Pokémon") // mensaje, donde se use
)
}
}subject y message no siempre se usan: el correo aprovecha los dos, un chat no usa ninguno. Rellenarlos no cuesta nada y mejora el sitio donde sí sirven.
Paso 4 · La llamada que no se escribe con punto
Paso 5 · Probarlo
En Android, ejecuta la app (botón Run), abre una ficha y toca el botón de compartir. El texto que se comparte, si abres la ficha de Pikachu, es este:
Pikachu · https://sebastiannarvaez11.github.io/pokedex-kmp/pokemon/25Se pega en cualquier chat y es pulsable. Al abrirlo pasa lo que viste en Deep links en Android: sin verificación de dominio, el enlace abre el navegador en la página del curso que invita a abrir la app, en vez de abrir la app. La cadena funciona entera; lo único que falta es un dominio propio. En iOS, ejecuta (Cmd+R), abre una ficha y toca el botón Compartir… de la barra de navegación: se abre la hoja de iOS, con el enlace https y el mensaje «Mira este Pokémon» (el texto «Pikachu · https://…» es solo de Android).
Si no tienes una app de mensajería en el emulador o el simulador, no importa: en la hoja de compartir elige copiar (en Android, Copy to clipboard o el icono de copiar junto al texto; en iOS, Copiar) y pega en Notas, o en la barra de direcciones del navegador. En Android lo que ves pegado es lo que recibiría otra persona; en iOS, lo que se pega depende de la app donde lo hagas.
Punto de control
- En la ficha de Android hay un botón de compartir arriba a la derecha, y el número se aparta a su izquierda, sin quedar debajo.
- Al tocarlo en Android se abre la hoja del sistema con «Pikachu · https://…/pokemon/25».
- En la ficha de iOS el número sigue abajo a la derecha; el botón está en la barra de navegación y abre la hoja de iOS.
- El texto compartido lleva el enlace
https, nunca elpokedex://. - Pegado en el navegador, el enlace abre la página del curso que invita a abrir la app (si la web del curso está al día).
- Sin tests nuevos: siguen 76 / 40 / 43 (JVM, Android y iOS).
- Cmd+B compila sin errores: Swift ve
DestinationKt.toShareUrlyDestinationDetalle.
Comprueba lo que entendiste
5 preguntas · necesitas 4 para completar la lección
1.¿Por qué se comparte el enlace
httpsy no elpokedex://?2.En Swift, ¿cómo se llama a la función de extensión
Destination.toShareUrl()de Kotlin?3.¿Qué aporta
Intent.createChooserfrente a lanzar elACTION_SENDa secas?4.En iOS,
ShareLinkes…5.¿Por qué el texto compartido en Android lleva el nombre del Pokémon además del enlace?