رفرفة في الأمثلة. روابط عميقة في تطبيقات الرفرفة

يكتسب إطار عمل Google الجديد متعدد المنصات ، Flutter ، بثبات للمشجعين. المزيد والمزيد من الناس مهتمون بهذه التكنولوجيا وتجربتها في كل من مشاريع الحيوانات الأليفة والتجارية. تظهر المزيد من المقالات والأمثلة في RuNet ، لكنني لاحظت منذ فترة أنه على عكس المقالات المتوسطة ، تستعرض المقالات المخصصة للتكنولوجيا ككل ومزاياها أو مستجداتها المقدمة في الإصدار الأخير على Habré. هناك عدد قليل من النصوص على حالات محددة. لذلك ، قررت أنه كان من الضروري تصحيح الوضع الحالي. لن أبدأ بالحالة الأكثر شيوعًا ، ولكن غالبًا ما يتم استخدامها - الروابط العميقة.
صورة

في الآونة الأخيرة ، كان لدي مهمة إطلاق تطبيق Flutter باستخدام روابط عميقة. كان علي البحث في الوثائق والتجربة للحصول على فكرة مناسبة عن كيفية العمل معهم في Flutter. في هذه المقالة ، قمت بتجميع النتائج ، بحيث كان من السهل فهم أولئك الذين يواجهون نفس المهمة.

الروابط العميقة هي عناوين URL التي تمنح المستخدمين القدرة على الانتقال إلى محتوى معين داخل تطبيق محمول على نظام iOS أو Android. هذا يعني أنه يجب علينا تتبع كيفية فتح التطبيق: بالطريقة القياسية أو باستخدام رابط ، بالإضافة إلى ذلك ، قد يكون التطبيق مفتوحًا بالفعل عند اكتمال النقل. لذلك ، نحن بحاجة إلى تتبع نسب النقر إلى الظهور وفي خلفية تطبيق قيد التشغيل. دعونا نرى أفضل طريقة للقيام بذلك في رفرفة.

أول شيء هو التكوين


لاستخدام Deep Links في التنمية المحلية ، تحتاج إلى إعداد التكوين المناسب في المشروع. بالنسبة لتطبيق Flutter ، يتم ذلك بنفس الطريقة تمامًا كما هو الحال في التطبيق الأصلي.

دائرة الرقابة الداخلية


هناك طريقتان لإنشاء مثل هذه الروابط في نظام Apple البيئي: "مخططات عنوان URL المخصص" و "الروابط العالمية".

  • أنظمة URL المخصصة - تتيح لك استخدام نظام مخصص ، بغض النظر عن المضيف المحدد. هذا النهج هو أبسط طريقة ، ولكن هناك فروق دقيقة: يجب أن تكون متأكدًا من أن المخطط فريد من نوعه ، وبالإضافة إلى ذلك ، لن يعمل الرابط بدون تطبيق مثبت. إذا كنت تستخدم أنظمة عناوين URL المخصصة ، فيمكنك استخدام روابط مثل: your_scheme://any_host
  • الروابط العالمية هي نهج أكثر تعقيدًا قليلاً. إنها تسمح لك بالعمل فقط باستخدام نظام https ومع مضيف محدد ، ولكن يجب عليك تأكيد حقوق استخدام هذا المضيف ، والتي تحتاج إلى وضع الملف عليها على الخادم - apple-app-site-اقتران. تمنحك الروابط العالمية فرصة تشغيل التطبيق على عنوان URL: https://your_host ، وإذا لم يكن هناك تطبيق مثبت ، فسوف يعرض عليك تثبيته من المتجر أو فتح الرابط في متصفح.

على سبيل المثال ، أستخدم نهج مخططات عنوان URL المخصص ، لأنه أبسط. أضف القطعة التالية إلى ملف 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 البيئي أيضًا طريقتان للربط مع نفس الخصائص تقريبًا:

  • تتيح لك الروابط العميقة - (تمامًا مثل مخططات URL المخصصة في نظام التشغيل iOS) - استخدام نظام مخصص بغض النظر عن المضيف المحدد.
  • ارتباطات التطبيقات - تسمح لك بالعمل فقط باستخدام نظام https ومع مضيف محدد (تمامًا مثل Universal Links في iOS) ، وتحتاج أيضًا إلى تأكيد حقوق استخدام هذا المضيف عن طريق وضع ملف JSON على خادم Digital Asset Links.

بالنسبة إلى نظام Android ، قررت أيضًا عدم تعقيده واستخدام Deep Links. أضف هذا إلى 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> 

وبالتالي ، قمنا بتكوين التطبيقات لكلا poc لأنظمة poc قادرين على معالجة poc://deeplink.flutter.dev URL فيها poc://deeplink.flutter.dev

قنوات منصة الطبخ


لذلك ، التكوين الأصلي لكل منصة جاهز. ولكن بالإضافة إلى التهيئة ، تحتاج إلى إعداد قنوات النظام الأساسي ، حيث يتفاعل الجزء الأصلي مع Flutter. ومرة أخرى ، أنت بحاجة إلى إعداد التنفيذ لكل من Android و iOS.
لنبدأ مع Android. تحتاج فقط إلى عدم القيام بأي شيء - فقط قم بمعالجة النية الواردة في طريقة onCreate ، وإنشاء MethodChannel وتمرير URI إليها إذا كان التطبيق يعمل من خلال 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(); } } 

في نظام التشغيل iOS ، سيكون كل شيء مختلفًا بعض الشيء ، ولكن بشكل عام نفس الشيء: تمرير URI إلى التطبيق من خلال MethodChannel. قررت تنفيذه على Swift ، نظرًا لأن الأشياء Objecttive-C ليست جيدة جدًا بالنسبة لي)). التالي هو تعديل 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) } 

لذلك سوف نتعامل مع إطلاق التطبيق من خلال Deep Link. ولكن ماذا لو كان الرابط يتبع عندما كان التطبيق قيد التشغيل بالفعل؟ من الضروري أن تأخذ في الاعتبار هذه اللحظة.

في Android ، لهذا سنتجاوز طريقة onNewIntent وسنعالج كل نية واردة. إذا كانت هذه نقرة على رابط ، فسوف نلقي حدثًا في EventChannel الذي تم إنشاؤه لهذا من خلال 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); } ; } }; } } 

دعونا نفعل الشيء نفسه في الجزء دائرة الرقابة الداخلية. في Swift ، نحتاج إلى إنشاء FlutterStreamHandler ومعالجة أي رابط نتلقاه أثناء وجود التطبيق في الخلفية. حان الوقت لتغيير AppDelegate.swift قليلاً مرة أخرى

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

عندما نجمع بين الجزأين: الجزء الخاص بتشغيل التطبيق والجزء الخاص بالتطبيق في الخلفية - سنتحكم في كل نقرات المستخدمين على Deep Links.

معالجة الروابط العميقة في الرفرفة


جزء المنصة جاهز لذلك ، فقد حان الوقت للانتقال إلى جزء الرفرفة. كما تعلم ، يمكنك إنشاء تطبيقات على الرفرفة باستخدام مناهج معمارية مختلفة. تم بالفعل كتابة العديد من المقالات حول هذا الموضوع ( على سبيل المثال ، هذا واحد ) ، ولكن شخصيا يبدو لي أن BLoC النقي هو النهج الأنسب. لذلك ، سأقوم بإعداد BLoC منفصلة والتي ستتعامل مع هذه الروابط. نتيجة لذلك ، نحصل على رمز غير مرتبط مطلقًا بـ UI ويمكنه التعامل مع روابط الاستقبال حيثما كان ذلك مناسبًا.

 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}'."; } } } 

خاصة بالنسبة لأولئك الذين لم تكن لديهم خبرة سابقة مع BLoC و StreamBuilders ، سأقوم بإعداد عنصر واجهة تعامل مستخدم يعمل مع BLoC هذا. يستند عنصر واجهة المستخدم إلى StreamBuilder ، الذي يعيد إنشاء واجهة المستخدم اعتمادًا على الأحداث التي تم تلقيها من الدفق.

 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! هذا كل شيء. الآن كل شيء يعمل!

للاختبار ، قم بتشغيل التطبيق بثلاث طرق مختلفة. يدويًا ومن خلال Deep Links ، أولاً باستخدام poc://deeplink.flutter.dev URI poc://deeplink.flutter.dev ، ثم مع poc://deeplink.flutter.dev/parameter . فيما يلي لقطات لما حدث:

صورة

هناك طرق أخرى للعمل مع Deep Links. على سبيل المثال ، يمكنك استخدام Firebase Dynamic Links لهذا الغرض. هناك مقال ممتاز حول كيفية استخدامها مع رفرفة. هناك أيضًا مكتبة "الروابط الأحادية" الجاهزة للاتصال بعمق الروابط - يمكنك استخدامها. وإذا كنت لا ترغب في الاعتماد على مكتبات الطرف الثالث ، فيمكنك دائمًا تنفيذ مكتبتك. آمل أن مقالتي سوف تساعدك في هذا!

شفرة المصدر


يمكن الاطلاع على الكود المصدري للمثال أعلاه هنا .

بعض المعلومات المفيدة


إذا قرأت المقالة حتى هذه النقطة ، فمن المرجح أنك مهتم بتطوير الرفرفة). أريد أن أتحدث عن العديد من الموارد التي قد تكون مفيدة لك. منذ وقت ليس ببعيد ، تم إنشاء زوج من مواد البودكاست الناطقة باللغة الروسية والتي ترتبط مباشرة بتطوير Flutter. أوصي بالاشتراك معهم: Flutter Dev Podcast ( قناة التلغراف ) ، حيث نناقش القضايا الملحة المتعلقة بتطوير Flutter ، ومحادثات People Mobile ( قناة telegram ) ، حيث نناقش مشاكل تطوير المحمول من حيث المبدأ ، من وجهات نظر مختلفة. محادثات الهاتف المحمول الرائدة هي iOS و Android و ReactNative ورفرفة.

صورة

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


All Articles