El nuevo marco multiplataforma de Google, Flutter, está ganando adeptos constantemente. Cada vez más personas están interesadas en esta tecnología y la prueban tanto en proyectos comerciales como de mascotas. Cada vez aparecen más artículos y ejemplos en RuNet, pero hace algún tiempo noté que, a diferencia de Medium, los artículos de revisión dedicados a la tecnología en su conjunto y sus ventajas o novedades presentadas en la última versión predominan en Habré. Hay pocos textos sobre casos específicos. Por lo tanto, decidí que era necesario corregir la situación actual. No comenzaré con el caso más común, pero con la frecuencia suficiente: enlaces profundos.

Recientemente, tuve la tarea de lanzar una aplicación Flutter usando enlaces profundos. Tuve que rebuscar en la documentación y experimentar para tener una idea adecuada de cómo trabajar con ellos en Flutter. En este artículo, he agregado los resultados, para que aquellos que se enfrentan a la misma tarea, sea más fácil de entender.
Los enlaces profundos son URL que brindan a los usuarios la capacidad de navegar a contenido específico dentro de una aplicación móvil en iOS o Android. Esto significa que debemos rastrear cómo se abrió la aplicación: de la manera estándar o mediante un enlace, y además, la aplicación ya puede estar abierta cuando se completó la transición. Por lo tanto, debemos realizar un seguimiento de los clics y en el fondo de una aplicación en ejecución. Veamos cómo hacer esto mejor en Flutter.
Lo primero es la configuración
Para utilizar Deep Links en el desarrollo nativo, debe preparar la configuración adecuada en el proyecto. Para una aplicación Flutter, esto se hace exactamente de la misma manera que en el nativo.
iOS
Hay dos formas de crear dichos enlaces en el ecosistema de Apple: "Esquemas de URL personalizados" y "Enlaces universales".
- Esquemas de URL personalizados : le permiten utilizar un esquema personalizado, independientemente del host que se especifique. Este enfoque es el más simple, pero hay matices: debe asegurarse de que el esquema sea único y, además, el enlace no funcionará sin una aplicación instalada. Si usa esquemas de URL personalizados, puede usar enlaces como:
your_scheme://any_host
- Universal Links es un enfoque un poco más complejo. Le permiten trabajar solo con el esquema https y con un host específico, pero debe confirmar los derechos para usar este host, para lo cual debe colocar el archivo en el servidor - apple-app-site-association. Universal Links le brinda la oportunidad de ejecutar la aplicación en la URL:
https://your_host
, y si no hay una aplicación instalada, ofrecerá instalarla desde la tienda o abrir el enlace en un navegador.
Por ejemplo, uso el enfoque de esquemas de URL personalizados, ya que es más simple. Agregue la siguiente pieza al archivo Info.plist:
<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLName</key> <string>deeplink.flutter.dev</string> <key>CFBundleURLSchemes</key> <array> <string>poc</string> </array> </dict> </array>
Android
El ecosistema de Android también tiene dos formas de vincularse con aproximadamente las mismas propiedades:
- Enlaces profundos : (al igual que los esquemas de URL personalizados en iOS) le permiten usar un esquema personalizado sin importar qué host se especifique.
- Enlaces de aplicaciones : le permite trabajar solo con el esquema https y con un host específico (al igual que Universal Links en iOS), y también debe confirmar los derechos para usar este host colocando un archivo JSON en el servidor de enlaces de activos digitales.
Para Android, también decidí no complicarlo y utilicé Deep Links. Agregue esto a AndroidManifest.xml:
<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="poc" android:host="deeplink.flutter.dev" /> </intent-filter>
Por lo tanto, configuramos aplicaciones para ambas plataformas para esquemas de
poc
y podremos procesar el
poc://deeplink.flutter.dev
URL en ellos
poc://deeplink.flutter.dev
Canales de plataforma de cocina
Entonces, la configuración nativa para cada plataforma está lista. Pero además de la configuración, debe preparar canales de plataforma, gracias a los cuales la parte nativa interactuará con Flutter. Y nuevamente, debe preparar su implementación tanto para Android como para iOS.
Comencemos con Android. Simplemente no necesita hacer nada: simplemente procese la Intención entrante en el método onCreate, cree un MethodChannel y páselo el URI si la aplicación se ejecuta a través de Deep Link.
private static final String CHANNEL = "poc.deeplink.flutter.dev/channel"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); Intent intent = getIntent(); Uri data = intent.getData(); new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler( new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { if (call.method.equals("initialLink")) { if (startString != null) { result.success(startString); } } } }); if (data != null) { startString = data.toString(); } }
En iOS, todo será un poco diferente, aunque en general será lo mismo: pasar el URI a la aplicación a través de MethodChannel. Decidí implementarlo en Swift, ya que con Objecttive-C las cosas no son muy buenas para mí)). El siguiente es el AppDelegate.swift modificado
@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { private var methodChannel: FlutterMethodChannel? override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) -> Bool { let controller = window.rootViewController as! FlutterViewController methodChannel = FlutterMethodChannel(name: "poc.deeplink.flutter.dev/channel", binaryMessenger: controller) methodChannel?.setMethodCallHandler({ (call: FlutterMethodCall, result: FlutterResult) in guard call.method == "initialLink" else { result(FlutterMethodNotImplemented) return } }) GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) }
Entonces manejaremos el lanzamiento de la aplicación a través de Deep Link. Pero, ¿qué pasa si el enlace siguió cuando la aplicación ya se estaba ejecutando? Es necesario tener en cuenta este momento.
En Android, para esto anularemos el método onNewIntent y procesaremos cada intento entrante. Si se trata de un clic en el enlace, lanzaremos un evento en el EventChannel creado para esto a través de un BroadcastReceiver especialmente creado.
private static final String EVENTS = "poc.deeplink.flutter.dev/events"; private BroadcastReceiver linksReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); new EventChannel(getFlutterView(), EVENTS).setStreamHandler( new EventChannel.StreamHandler() { @Override public void onListen(Object args, final EventChannel.EventSink events) { linksReceiver = createChangeReceiver(events); } @Override public void onCancel(Object args) { linksReceiver = null; } } ); } @Override public void onNewIntent(Intent intent){ super.onNewIntent(intent); if(intent.getAction() == android.content.Intent.ACTION_VIEW && linksReceiver != null) { linksReceiver.onReceive(this.getApplicationContext(), intent); } } private BroadcastReceiver createChangeReceiver(final EventChannel.EventSink events) { return new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) {
Hagamos lo mismo en la parte de iOS. En Swift, necesitamos crear un FlutterStreamHandler y procesar cualquier enlace que recibamos mientras la aplicación esté en segundo plano. Es hora de cambiar AppDelegate.swift un poco más
@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { private var eventChannel: FlutterEventChannel? private let linkStreamHandler = LinkStreamHandler() override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) -> Bool { let controller = window.rootViewController as! FlutterViewController eventChannel = FlutterEventChannel(name: "poc.deeplink.flutter.dev/events", binaryMessenger: controller) GeneratedPluginRegistrant.register(with: self) eventChannel?.setStreamHandler(linkStreamHandler) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } override func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { eventChannel?.setStreamHandler(linkStreamHandler) return linkStreamHandler.handleLink(url.absoluteString) } } class LinkStreamHandler:NSObject, FlutterStreamHandler { var eventSink: FlutterEventSink?
Cuando combinamos ambas partes: la parte para iniciar la aplicación y la parte para la aplicación en segundo plano, controlaremos todos los clics de los usuarios en Deep Links.
Procesamiento de enlaces profundos en Flutter
La parte de la plataforma está lista para esto, es hora de pasar a la parte de Flutter. Como probablemente sepa, puede crear aplicaciones en aleteo utilizando diferentes enfoques arquitectónicos. Ya se han escrito muchos artículos sobre este tema (
por ejemplo, este ), pero personalmente me parece que BLoC puro es el enfoque más adecuado. Por lo tanto, prepararé un BLoC separado que manejará estos enlaces. Como resultado, obtenemos un código que no está vinculado a la interfaz de usuario y puede recibir enlaces de recepción donde sea conveniente.
class DeepLinkBloc extends Bloc {
Especialmente para aquellos que no tenían experiencia previa con BLoC y StreamBuilders, prepararé un widget de ejemplo que funcionará con este BLoC. El widget se basa en StreamBuilder, que reconstruye la interfaz de usuario en función de los eventos recibidos de la transmisión.
class PocWidget extends StatelessWidget { @override Widget build(BuildContext context) { DeepLinkBloc _bloc = Provider.of<DeepLinkBloc>(context); return StreamBuilder<String>( stream: _bloc.state, builder: (context, snapshot) { if (!snapshot.hasData) { return Container( child: Center( child: Text('No deep link was used '))); } else { return Container( child: Center( child: Padding( padding: EdgeInsets.all(20.0), child: Text('Redirected: ${snapshot.data}')))); } }, ); } }
Tadam! Eso es todo ¡Ahora todo funciona!
Para probar, ejecute la aplicación de tres maneras diferentes. Manualmente y mediante enlaces profundos, primero con el URI
poc://deeplink.flutter.dev
, y luego con
poc://deeplink.flutter.dev/parameter
. Aquí hay capturas de pantalla de lo que sucedió:

Hay otras formas de trabajar con enlaces profundos. Por ejemplo, puede usar
Firebase Dynamic Links para esto. Hay
un excelente artículo sobre cómo usarlos con Flutter. También hay una
biblioteca 'uni-links' ya preparada para conectar Deep Links: puede usarla. Y si no desea depender de bibliotecas de terceros, siempre puede implementar la suya. ¡Espero que mi artículo te ayude con esto!
Código fuente
El código fuente para el ejemplo anterior se puede
ver aquí .
Alguna información útil
Si leyó el artículo hasta este punto, lo más probable es que esté interesado en el desarrollo de Flutter). Quiero hablar sobre varios recursos que pueden serle útiles. No hace mucho tiempo, se creó un par de podcasts de habla rusa que están directamente relacionados con el desarrollo de Flutter. Recomiendo suscribirse a ellos:
Flutter Dev Podcast (
canal de telegramas ), donde discutimos los temas candentes del desarrollo de Flutter, y
Mobile People Talks (
canal de telegramas ), donde discutimos los problemas de desarrollo móvil en principio, desde diferentes puntos de vista. Las principales conversaciones sobre personas móviles son iOS, Android, ReactNative y Flutter.
