Flattern in den Beispielen. Deep Links in Flatteranwendungen

Das neue plattformübergreifende Framework von Google, Flutter, gewinnt stetig Fans. Immer mehr Menschen interessieren sich für diese Technologie und probieren sie sowohl in Heimtier- als auch in kommerziellen Projekten aus. Immer mehr Artikel und Beispiele erscheinen in RuNet, aber vor einiger Zeit habe ich festgestellt, dass im Gegensatz zu Medium Artikel über die Technologie als Ganzes und ihre Vorteile oder Neuheiten in der neuesten Version bei Habré überwiegen. Es gibt nur wenige Texte zu bestimmten Fällen. Daher habe ich beschlossen, dass es notwendig ist, die aktuelle Situation zu korrigieren. Ich beginne nicht mit dem häufigsten Fall, sondern werde häufig genug verwendet - Deep Links.
Bild

Vor kurzem hatte ich die Aufgabe, eine Flutter-Anwendung über Deep Links zu starten. Ich musste die Dokumentation durchsuchen und experimentieren, um eine angemessene Vorstellung davon zu bekommen, wie ich mit ihnen in Flutter arbeiten sollte. In diesem Artikel habe ich die Ergebnisse zusammengefasst, damit diejenigen, die vor der gleichen Aufgabe stehen, leichter zu verstehen waren.

Deep Links sind URLs, mit denen Benutzer unter iOS oder Android zu bestimmten Inhalten in einer mobilen Anwendung navigieren können. Dies bedeutet, dass wir nachverfolgen müssen, wie die Anwendung geöffnet wurde: auf standardmäßige Weise oder über einen Link. Außerdem ist die Anwendung möglicherweise bereits geöffnet, als der Übergang abgeschlossen wurde. Wir müssen also die Klickraten und den Hintergrund einer laufenden Anwendung verfolgen. Mal sehen, wie das am besten in Flutter geht.

Das erste ist die Konfiguration


Um Deep Links in der nativen Entwicklung zu verwenden, müssen Sie die entsprechende Konfiguration im Projekt vorbereiten. Bei einer Flutter-Anwendung erfolgt dies genauso wie bei der nativen Anwendung.

iOS


Es gibt zwei Möglichkeiten, solche Links im Apple-Ökosystem zu erstellen: "Benutzerdefinierte URL-Schemata" und "Universelle Links".

  • Benutzerdefinierte URL-Schemata - Ermöglichen die Verwendung eines benutzerdefinierten Schemas, unabhängig davon, welcher Host angegeben ist. Dieser Ansatz ist der einfachste, aber es gibt Nuancen: Sie müssen sicher sein, dass das Schema eindeutig ist, und außerdem funktioniert der Link ohne eine installierte Anwendung nicht. Wenn Sie benutzerdefinierte URL-Schemata verwenden, können Sie Links verwenden wie: your_scheme://any_host
  • Universal Links ist ein etwas komplexerer Ansatz. Sie ermöglichen es Ihnen, nur mit dem https-Schema und mit einem bestimmten Host zu arbeiten. Sie müssen jedoch die Rechte zur Verwendung dieses Hosts bestätigen, für die Sie die Datei auf dem Server ablegen müssen - Apple-App-Site-Zuordnung. Universelle Links bieten Ihnen die Möglichkeit, die Anwendung unter der URL https://your_host keine installierte Anwendung vorhanden ist, können Sie sie aus dem Store installieren oder den Link in einem Browser öffnen.

Zum Beispiel verwende ich den Ansatz "Benutzerdefinierte URL-Schemata", da dieser einfacher ist. Fügen Sie der Datei Info.plist das folgende Teil hinzu:

 <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


Das Android-Ökosystem bietet außerdem zwei Möglichkeiten zur Verknüpfung mit ungefähr denselben Eigenschaften:

  • Deep Links - (genau wie die benutzerdefinierten URL-Schemata in iOS) ermöglichen es Ihnen, ein benutzerdefiniertes Schema zu verwenden, unabhängig davon, welcher Host angegeben ist.
  • App-Links - Ermöglichen es Ihnen, nur mit dem https-Schema und mit einem bestimmten Host zu arbeiten (genau wie Universal Links in iOS). Außerdem müssen Sie die Rechte zur Verwendung dieses Hosts bestätigen, indem Sie eine JSON-Datei auf dem Digital Asset Links-Server ablegen.

Für Android habe ich mich auch entschieden, es nicht zu komplizieren und Deep Links verwendet. Fügen Sie dies zu AndroidManifest.xml hinzu:

 <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> 

Daher haben wir Anwendungen für beide Plattformen für poc Schemata konfiguriert und können die darin enthaltene URL poc://deeplink.flutter.dev

Kochplattformkanäle


Die native Konfiguration für jede Plattform ist also fertig. Neben der Konfiguration müssen Sie jedoch Plattformkanäle vorbereiten, dank derer der native Teil mit Flutter interagiert. Auch hier müssen Sie Ihre Implementierung für Android und iOS vorbereiten.
Beginnen wir mit Android. Sie müssen nur nichts tun - verarbeiten Sie einfach die eingehende Absicht in der onCreate-Methode, erstellen Sie einen MethodChannel und übergeben Sie den URI an ihn, wenn die Anwendung über Deep Link ausgeführt wird.

 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(); } } 

In iOS wird alles etwas anders sein, obwohl im Allgemeinen dasselbe: Übergeben des URI an die Anwendung über den MethodChannel. Ich habe beschlossen, es auf Swift zu implementieren, da es mit Objecttive-C nicht sehr gut für mich ist)). Als nächstes kommt die modifizierte AppDelegate.swift

 @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) } 

Wir werden also den Start der Anwendung über Deep Link abwickeln. Was aber, wenn der Link folgte, als die Anwendung bereits ausgeführt wurde? Es ist notwendig, diesen Moment zu berücksichtigen.

In Android überschreiben wir dazu die onNewIntent-Methode und verarbeiten jede eingehende Absicht. Wenn dies ein Linkklick ist, werfen wir ein Ereignis in den dafür erstellten EventChannel über einen speziell erstellten BroadcastReceiver.

  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) { // NOTE: assuming intent.getAction() is Intent.ACTION_VIEW String dataString = intent.getDataString(); if (dataString == null) { events.error("UNAVAILABLE", "Link unavailable", null); } else { events.success(dataString); } ; } }; } } 

Machen wir dasselbe im iOS-Teil. In Swift müssen wir einen FlutterStreamHandler erstellen und alle Links verarbeiten, die wir erhalten, während sich die Anwendung im Hintergrund befindet. Es ist Zeit, AppDelegate.swift wieder ein wenig zu ändern

 @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? // links will be added to this queue until the sink is ready to process them var queuedLinks = [String]() func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { self.eventSink = events queuedLinks.forEach({ events($0) }) queuedLinks.removeAll() return nil } func onCancel(withArguments arguments: Any?) -> FlutterError? { self.eventSink = nil return nil } func handleLink(_ link: String) -> Bool { guard let eventSink = eventSink else { queuedLinks.append(link) return false } eventSink(link) return true } } 

Wenn wir beide Teile kombinieren: den Teil zum Starten der Anwendung und den Teil für die Anwendung im Hintergrund - steuern wir alle Benutzerklicks auf Deep Links.

Deep Links in Flutter verarbeiten


Der Plattformteil ist dafür bereit, es ist Zeit, zum Flatterteil überzugehen. Wie Sie wahrscheinlich wissen, können Sie Anwendungen auf Flattern mit verschiedenen Architekturansätzen erstellen. Zu diesem Thema wurden bereits viele Artikel geschrieben ( zum Beispiel dieser ), aber persönlich scheint mir reines BLoC der am besten geeignete Ansatz zu sein. Daher werde ich ein separates BLoC vorbereiten, das diese Links behandelt. Als Ergebnis erhalten wir Code, der absolut nicht an die Benutzeroberfläche gebunden ist und den Empfang von Links überall dort verarbeiten kann, wo es zweckmäßig ist.

 class DeepLinkBloc extends Bloc { //Event Channel creation static const stream = const EventChannel('poc.deeplink.flutter.dev/events'); //Method channel creation static const platform = const MethodChannel('poc.deeplink.flutter.dev/channel'); StreamController<String> _stateController = StreamController(); Stream<String> get state => _stateController.stream; Sink<String> get stateSink => _stateController.sink; //Adding the listener into contructor DeepLinkBloc() { //Checking application start by deep link startUri().then(_onRedirected); //Checking broadcast stream, if deep link was clicked in opened appication stream.receiveBroadcastStream().listen((d) => _onRedirected(d)); } _onRedirected(String uri) { // Here can be any uri analysis, checking tokens etc, if it's necessary // Throw deep link URI into the BloC's stream stateSink.add(uri); } @override void dispose() { _stateController.close(); } Future<String> startUri() async { try { return platform.invokeMethod('initialLink'); } on PlatformException catch (e) { return "Failed to Invoke: '${e.message}'."; } } } 

Insbesondere für diejenigen, die noch keine Erfahrung mit BLoC und StreamBuilders hatten, werde ich ein Beispiel-Widget vorbereiten, das mit diesem BLoC funktioniert. Das Widget basiert auf StreamBuilder, das die Benutzeroberfläche abhängig von den vom Stream empfangenen Ereignissen neu erstellt.

 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! Das ist alles. Jetzt funktioniert alles!

Führen Sie die Anwendung zum Testen auf drei verschiedene Arten aus. Manuell und über Deep Links, zuerst mit dem URI poc://deeplink.flutter.dev und dann mit poc://deeplink.flutter.dev/parameter . Hier sind Screenshots von dem, was passiert ist:

Bild

Es gibt andere Möglichkeiten, mit Deep Links zu arbeiten. Hierfür können Sie beispielsweise Firebase Dynamic Links verwenden . Es gibt einen ausgezeichneten Artikel darüber, wie man sie mit Flutter benutzt. Es gibt auch eine vorgefertigte 'Uni-Links'-Bibliothek zum Verbinden von Deep Links - Sie können sie verwenden. Und wenn Sie nicht von Bibliotheken von Drittanbietern abhängig sein möchten, können Sie jederzeit Ihre eigenen implementieren. Ich hoffe mein Artikel hilft dir dabei!

Quellcode


Der Quellcode für das obige Beispiel kann hier eingesehen werden .

Einige nützliche Informationen


Wenn Sie den Artikel bis zu diesem Punkt gelesen haben, sind Sie höchstwahrscheinlich an der Flutter-Entwicklung interessiert. Ich möchte über einige Ressourcen sprechen, die für Sie nützlich sein können. Vor nicht allzu langer Zeit wurden zwei russischsprachige Podcasts erstellt, die in direktem Zusammenhang mit der Flutter-Entwicklung stehen. Ich empfehle, sie zu abonnieren: Flutter Dev Podcast ( Telegrammkanal ), in dem wir die brennenden Probleme der Flutter-Entwicklung diskutieren, und Mobile People Talks ( Telegrammkanal ), in dem wir Probleme der mobilen Entwicklung im Prinzip aus verschiedenen Blickwinkeln diskutieren. Führende Gespräche über mobile Personen sind iOS, Android, ReactNative und Flutter.

Bild

Source: https://habr.com/ru/post/de461239/


All Articles