حل المشكلات المشتركة بين الأنظمة الأساسية عند العمل باستخدام Flutter
نشرت: 2022-03-10لقد رأيت الكثير من الالتباس عبر الإنترنت فيما يتعلق بتطوير الويب باستخدام Flutter ، وغالبًا ما يكون ذلك محزنًا لأسباب خاطئة.
على وجه التحديد ، أحيانًا ما يخلط الناس بينه وبين أطر العمل عبر الأنظمة الأساسية للجوال (وسطح المكتب) الأقدم المستندة إلى الويب ، والتي كانت في الأساس مجرد صفحات ويب تعمل داخل متصفحات تعمل داخل تطبيق مجمّع.
كان هذا حقًا عبر الأنظمة الأساسية بمعنى أن الواجهات كانت هي نفسها على أي حال لأنه كان لديك فقط إمكانية الوصول إلى الواجهات التي يمكن الوصول إليها عادةً على الويب.
لكن Flutter ليس كذلك: فهو يعمل أصلاً على كل منصة ، وهذا يعني أن كل تطبيق يعمل تمامًا كما لو كان مكتوبًا بلغة Java / Kotlin أو Objective-C / Swift على Android و iOS ، إلى حد كبير. أنت بحاجة إلى معرفة ذلك لأن هذا يعني أنك بحاجة إلى الاهتمام بالاختلافات العديدة بين هذه الأنظمة الأساسية المتنوعة للغاية.
في هذه المقالة ، سنرى بعض هذه الاختلافات وكيفية التغلب عليها. وبشكل أكثر تحديدًا ، سنتحدث عن اختلافات التخزين وواجهة المستخدم ، والتي غالبًا ما تسبب ارتباكًا للمطورين عند كتابة كود Flutter الذي يريدون أن يكون عبر الأنظمة الأساسية.
مثال 1: التخزين
كتبت مؤخرًا على مدونتي حول الحاجة إلى نهج مختلف لتخزين JWTs في تطبيقات الويب عند مقارنتها بتطبيقات الأجهزة المحمولة.
ويرجع ذلك إلى الطبيعة المختلفة لخيارات تخزين الأنظمة الأساسية ، والحاجة إلى معرفة كل منها وأدوات التطوير الأصلية الخاصة بها.
الويب
عندما تكتب تطبيق ويب ، فإن خيارات التخزين المتوفرة لديك هي:
- تنزيل / تحميل الملفات إلى / من القرص ، الأمر الذي يتطلب تفاعل المستخدم وبالتالي فهو مناسب فقط للملفات التي من المفترض أن يقرأها المستخدم أو ينشئها ؛
- باستخدام ملفات تعريف الارتباط ، التي قد تكون أو لا يمكن الوصول إليها من JS (اعتمادًا على ما إذا كانت
httpOnly
أم لا) ويتم إرسالها تلقائيًا مع الطلبات إلى مجال معين وحفظها عندما تأتي كجزء من استجابة ؛ - باستخدام JS
localStorage
وsessionStorage
، ويمكن الوصول إليه بواسطة أي JS على موقع الويب ، ولكن فقط من JS الذي يعد جزءًا من صفحات هذا الموقع.
متحرك
يختلف الوضع عندما يتعلق الأمر بتطبيقات الأجهزة المحمولة تمامًا. خيارات التخزين هي كما يلي:
- مستندات التطبيق المحلية أو ذاكرة التخزين المؤقت ، التي يمكن الوصول إليها بواسطة هذا التطبيق ؛
- مسارات التخزين المحلية الأخرى للملفات التي ينشئها المستخدم / يمكن قراءتها ؛
-
NSUserDefaults
وSharedPreferences
على التوالي على iOS و Android لتخزين قيمة المفتاح ؛ -
Keychain
على iOS وKeyStore
على Android للتخزين الآمن لأي بيانات ومفاتيح تشفير على التوالي.
إذا كنت لا تعرف ذلك ، فستحدث فوضى في عمليات التنفيذ الخاصة بك لأنك بحاجة إلى معرفة حل التخزين الذي تستخدمه بالفعل وما هي المزايا والعيوب.
حلول عبر الأنظمة الأساسية: نهج أولي
يستخدم استخدام حزمة localStorage
shared_preferences
التخزين المحلي على الويب و SharedPreferences
على Android و NSUserDefaults
على iOS. هذه لها آثار مختلفة تمامًا على تطبيقك ، خاصةً إذا كنت تقوم بتخزين معلومات حساسة مثل الرموز المميزة للجلسة: يمكن للعميل قراءة localStorage
، لذا فهذه مشكلة إذا كنت عرضة لـ XSS. على الرغم من أن تطبيقات الأجهزة المحمولة ليست معرضة حقًا لـ XSS ، فإن SharedPreferences
و NSUserDefaults
ليست طرق تخزين آمنة لأنه يمكن اختراقها من جانب العميل نظرًا لأنها ليست تخزينًا آمنًا وليست مشفرة. هذا لأنها مخصصة لتفضيلات المستخدم ، كما هو مذكور هنا في حالة iOS وهنا في وثائق Android عند الحديث عن مكتبة الأمان المصممة لتوفير أغلفة لـ SharedPreferences
خصيصًا لتشفير البيانات قبل تخزينها.
تخزين آمن على الهاتف المحمول
حلول التخزين الآمنة الوحيدة على الهاتف المحمول هي Keychain
و KeyStore
على iOS و Android على التوالي ، بينما لا يوجد تخزين آمن على الويب .
إن Keychain
و KeyStore
مختلفان تمامًا في طبيعتهما ، على الرغم من: Keychain
هو حل تخزين بيانات اعتماد عام ، بينما يتم استخدام KeyStore
لتخزين (ويمكن إنشاء) مفاتيح التشفير ، إما مفاتيح متماثلة أو مفاتيح عامة / خاصة.
هذا يعني أنه إذا احتجت ، على سبيل المثال ، إلى تخزين رمز جلسة مميز ، فيمكنك على نظام التشغيل iOS السماح لنظام التشغيل بإدارة جزء التشفير وإرسال الرمز المميز الخاص بك إلى Keychain
، بينما في نظام Android يعتبر تجربة يدوية أكثر قليلاً لأنك تحتاج لإنشاء مفتاح (ليس رمزًا ثابتًا ، هذا سيء) ، استخدمه لتشفير الرمز المميز ، وتخزين الرمز المميز المشفر في SharedPreferences
وتخزين المفتاح في KeyStore
.
هناك طرق مختلفة لذلك ، كما هو الحال مع معظم الأشياء في الأمان ، ولكن ربما يكون أبسطها هو استخدام التشفير المتماثل ، حيث لا توجد حاجة لتشفير المفتاح العام لأن تطبيقك يقوم بتشفير وفك تشفير الرمز المميز.
من الواضح أنك لست بحاجة إلى كتابة رمز خاص بالنظام الأساسي للجوال يقوم بكل ذلك ، حيث يوجد مكون إضافي Flutter يقوم بكل ذلك ، على سبيل المثال.
نقص التخزين الآمن على الويب
كان هذا ، في الواقع ، هو السبب الذي أجبرني على كتابة هذا المنشور. لقد كتبت عن استخدام تلك الحزمة لتخزين JWT على تطبيقات الهاتف المحمول وأراد الناس إصدار الويب من ذلك ولكن ، كما قلت ، لا يوجد تخزين آمن على الويب . لا وجود لها.
هل هذا يعني أن JWT الخاصة بك يجب أن تكون في العراء؟
لا إطلاقا. يمكنك استخدام httpOnly
ملفات تعريف الارتباط ، أليس كذلك؟ تلك التي لا يمكن الوصول إليها بواسطة JS ويتم إرسالها إلى الخادم الخاص بك فقط. تكمن المشكلة في ذلك في أنه يتم إرسالها دائمًا إلى الخادم الخاص بك ، حتى إذا نقر أحد المستخدمين لديك على عنوان URL لطلب GET على موقع ويب شخص آخر وأن طلب GET هذا له آثار جانبية لن تحبها أنت أو مستخدمك. يعمل هذا في الواقع مع أنواع الطلبات الأخرى أيضًا ، إنه أكثر تعقيدًا. إنه يسمى تزييف طلب عبر الموقع وأنت لا تريد ذلك. إنه من بين تهديدات أمان الويب المذكورة في مستندات MDN من Mozilla ، حيث يمكنك العثور على شرح أكثر اكتمالاً.
هناك طرق وقائية. الأكثر شيوعًا هو وجود رمزين ، في الواقع: أحدهما يصل إلى العميل كملف تعريف ارتباط httpOnly
، والآخر كجزء من الاستجابة. يجب تخزين الأخير في localStorage
وليس في ملفات تعريف الارتباط لأننا لا نريد إرساله تلقائيًا إلى الخادم.
حل كلاهما
ماذا لو كان لديك تطبيق جوال وتطبيق ويب؟
يمكن التعامل مع ذلك بإحدى طريقتين:
- استخدم نفس نقطة النهاية الخلفية ، ولكن احصل يدويًا على ملفات تعريف الارتباط وأرسلها باستخدام رؤوس HTTP المتعلقة بملفات تعريف الارتباط ؛
- قم بإنشاء نقطة نهاية خلفية منفصلة بخلاف الويب والتي تنشئ رمزًا مميزًا مختلفًا عن أي رمز مميز يستخدمه تطبيق الويب ثم السماح بتفويض JWT العادي إذا كان العميل قادرًا على توفير رمز مميز للجوال فقط.
تشغيل كود مختلف على منصات مختلفة
الآن ، دعنا نرى كيف يمكننا تشغيل تعليمات برمجية مختلفة على منصات مختلفة حتى نتمكن من تعويض الاختلافات.
إنشاء البرنامج الإضافي Flutter
لحل مشكلة التخزين على وجه الخصوص ، إحدى الطرق التي يمكنك من خلالها القيام بذلك هي باستخدام حزمة مكون إضافي: توفر المكونات الإضافية واجهة Dart مشتركة ويمكنها تشغيل تعليمات برمجية مختلفة على أنظمة أساسية مختلفة ، بما في ذلك كود Kotlin / Java أو كود Swift / Objective-C الخاص بالنظام الأساسي . يعد تطوير الحزم والمكونات الإضافية أمرًا معقدًا إلى حد ما ، ولكن يتم شرحه في العديد من الأماكن على الويب وفي أماكن أخرى (على سبيل المثال في كتب Flutter) ، بما في ذلك وثائق Flutter الرسمية.

بالنسبة لأنظمة الأجهزة المحمولة ، على سبيل المثال ، يوجد بالفعل مكون إضافي للتخزين الآمن ، وهو flutter_secure_storage
، حيث يمكنك العثور على مثال للاستخدام هنا ، ولكن هذا لا يعمل على الويب ، على سبيل المثال.
من ناحية أخرى ، للتخزين البسيط لقيمة المفتاح الذي يعمل أيضًا على الويب ، هناك حزمة مكونة إضافية من الطرف الأول طورتها Google عبر الأنظمة الأساسية تسمى shared_preferences
، والتي تحتوي على مكون خاص بالويب يسمى shared_preferences_web
والذي يستخدم NSUserDefaults
أو SharedPreferences
أو localStorage
حسب المنصة.
TargetPlatform على Flutter
بعد استيراد package:flutter/foundation.dart
، يمكنك مقارنة Theme.of(context).platform
بالقيم:
-
TargetPlatform.android
-
TargetPlatform.iOS
-
TargetPlatform.linux
-
TargetPlatform.windows
-
TargetPlatform.macOS
-
TargetPlatform.fuchsia
واكتب وظائفك بحيث يقومون بما يناسب كل منصة تريد دعمها. سيكون هذا مفيدًا بشكل خاص للمثال التالي لاختلاف النظام الأساسي ، وهو الاختلافات في كيفية عرض عناصر واجهة المستخدم على الأنظمة الأساسية المختلفة.
بالنسبة لحالة الاستخدام هذه ، على وجه الخصوص ، هناك أيضًا مكون إضافي شائع بشكل معقول flutter_platform_widgets
، والذي يبسط تطوير أدوات مدركة للنظام الأساسي.
مثال 2: الاختلافات في كيفية عرض نفس الأداة
لا يمكنك فقط كتابة رمز عبر الأنظمة الأساسية والتظاهر بأن المتصفح والهاتف والكمبيوتر والساعة الذكية هي نفس الشيء - إلا إذا كنت تريد أن يكون تطبيق Android و iOS الخاص بك هو WebView وتطبيق سطح المكتب الخاص بك ليتم بناؤه باستخدام Electron . هناك العديد من الأسباب لعدم القيام بذلك ، وليس الهدف من هذه القطعة إقناعك باستخدام أطر عمل مثل Flutter بدلاً من ذلك التي تحافظ على تطبيقك أصليًا ، مع كل مزايا الأداء وتجربة المستخدم التي تأتي معه ، مع السماح لك كتابة التعليمات البرمجية التي ستكون هي نفسها لجميع الأنظمة الأساسية في معظم الأوقات.
يتطلب ذلك عناية واهتمامًا ، وعلى الأقل معرفة أساسية بالمنصات التي تريد دعمها ، وواجهات برمجة التطبيقات الأصلية الفعلية ، وكل ذلك. يحتاج مستخدمو React Native إلى إيلاء المزيد من الاهتمام لذلك لأن إطار العمل هذا يستخدم عناصر واجهة مستخدم نظام التشغيل المضمنة ، لذلك تحتاج في الواقع إلى إيلاء المزيد من الاهتمام لكيفية ظهور التطبيق من خلال اختباره على نطاق واسع على كلا النظامين الأساسيين ، دون أن تكون قادرًا على التبديل بينهما عنصر واجهة مستخدم iOS والمواد أثناء التنقل كما هو ممكن مع Flutter.
ما يتغير بدون طلبك
هناك بعض جوانب واجهة المستخدم لتطبيقك والتي يتم تغييرها تلقائيًا عند التبديل بين الأنظمة الأساسية. يذكر هذا القسم أيضًا التغييرات بين Flutter و React Native في هذا الصدد.
بين Android و iOS (Flutter)
Flutter قادر على عرض عناصر واجهة مستخدم المواد على iOS (وعناصر واجهة مستخدم Cupertino (مثل iOS) على Android) ، ولكن ما لا يفعله هو إظهار نفس الشيء تمامًا على Android و iOS: تتكيف السمات المادية بشكل خاص مع اصطلاحات كل نظام أساسي .
على سبيل المثال ، تختلف الرسوم المتحركة والانتقالات والخطوط الافتراضية في التنقل ، ولكنها لا تؤثر على تطبيقك كثيرًا.
ما قد يؤثر على بعض اختياراتك عندما يتعلق الأمر بالجمال أو تجربة المستخدم هو حقيقة أن بعض العناصر الثابتة تتغير أيضًا. على وجه التحديد ، تتغير بعض الرموز بين النظامين الأساسيين ، وتوجد عناوين شريط التطبيقات في المنتصف على iOS وعلى اليسار على Android (على يسار المساحة المتاحة في حالة وجود زر رجوع أو زر لفتح درج (موضح هنا في إرشادات التصميم متعدد الأبعاد والمعروف أيضًا باسم قائمة الهامبرغر). هذا ما يبدو عليه تطبيق Material مع درج على Android:

وما يشبه التطبيق المادي ، البسيط للغاية ، على نظام التشغيل iOS:

بين الهاتف المحمول والويب ومع ظهور شقوق الشاشة (رفرفة)
يوجد على الويب موقف مختلف قليلاً ، كما هو مذكور أيضًا في مقالة Smashing هذه حول تطوير الويب المتجاوب مع Flutter: على وجه الخصوص ، بالإضافة إلى الاضطرار إلى التحسين للشاشات الأكبر حجمًا ومراعاة الطريقة التي يتوقع الأشخاص التنقل خلالها عبر موقعك - وهو المحور الرئيسي لهذه المقالة - يجب أن تقلق بشأن حقيقة أنه يتم أحيانًا وضع الأدوات خارج نافذة المتصفح. أيضًا ، تحتوي بعض الهواتف على شقوق في الجزء العلوي من شاشتها أو عوائق أخرى أمام العرض الصحيح لتطبيقك بسبب نوع من العوائق.
يمكن تجنب هاتين المشكلتين عن طريق تغليف عناصر واجهة المستخدم الخاصة بك في عنصر واجهة مستخدم SafeArea
، وهو نوع خاص من عناصر واجهة المستخدم الخاصة بالحشو والتي تتأكد من أن عناصر واجهة المستخدم الخاصة بك تقع في مكان حيث يمكن عرضها فعليًا دون أي شيء يعيق قدرة المستخدمين على رؤيتها ، سواء كان ذلك بسبب قيود على الأجهزة أو البرامج.
في React Native
يتطلب React Native مزيدًا من الاهتمام ومعرفة أعمق بكثير بكل منصة ، بالإضافة إلى مطالبتك بتشغيل iOS Simulator وكذلك محاكي Android على الأقل حتى تتمكن من اختبار تطبيقك على كلا النظامين الأساسيين: إنه ليس كذلك نفس الشيء ويقوم بتحويل عناصر واجهة مستخدم JavaScript الخاصة به إلى عناصر واجهة مستخدم خاصة بالمنصة. بعبارة أخرى ، ستبدو تطبيقات React Native الخاصة بك دائمًا مثل iOS - مع عناصر Cupertino UI كما يطلق عليها أحيانًا - وستبدو تطبيقات Android دائمًا مثل تطبيقات Material Design Android العادية لأنها تستخدم عناصر واجهة المستخدم للنظام الأساسي.
يتمثل الاختلاف هنا في أن Flutter تعرض أدواتها باستخدام محرك العرض منخفض المستوى الخاص بها ، مما يعني أنه يمكنك اختبار كلا إصداري التطبيق على نظام أساسي واحد.
التغلب على هذه المشكلة
ما لم تكن تبحث عن شيء محدد للغاية ، من المفترض أن يبدو تطبيقك مختلفًا على أنظمة أساسية مختلفة وإلا لن يكون بعض المستخدمين سعداء.
تمامًا مثلما لا يجب عليك ببساطة شحن تطبيق جوال إلى الويب (كما كتبت في منشور Smashing المذكور أعلاه) ، يجب ألا تشحن تطبيقًا مليئًا بأدوات كوبرتينو لمستخدمي Android ، على سبيل المثال ، لأنه سيكون مربكًا لـ الجزء الاكبر. من ناحية أخرى ، فإن الحصول على فرصة لتشغيل تطبيق يحتوي على عناصر واجهة مستخدم مخصصة لمنصة أخرى يتيح لك اختبار التطبيق وعرضه على الأشخاص في كلا الإصدارين دون الحاجة إلى استخدام جهازين لذلك بالضرورة.
الجانب الآخر: استخدام الأدوات الخاطئة للأسباب الصحيحة
ولكن هذا يعني أيضًا أنه يمكنك القيام بمعظم تطوير Flutter على محطة عمل Linux أو Windows دون التضحية بتجربة مستخدمي iOS لديك ، ثم إنشاء التطبيق للنظام الأساسي الآخر ولا داعي للقلق بشأن اختباره بدقة.
الخطوات التالية
تعد أطر العمل عبر الأنظمة الأساسية رائعة ، لكنها تنقل المسؤولية إليك ، كمطور ، لفهم كيفية عمل كل نظام أساسي وكيفية التأكد من أن تطبيقك يتكيف وأنه ممتع للاستخدام لمستخدميك. قد تكون الأشياء الصغيرة الأخرى التي يجب مراعاتها ، على سبيل المثال ، استخدام أوصاف مختلفة لما قد يكون في جوهره نفس الشيء إذا كانت هناك اصطلاحات مختلفة على منصات مختلفة.
من الرائع ألا تضطر إلى إنشاء تطبيقين (أو أكثر) بشكل منفصل باستخدام لغات مختلفة ، ولكن لا يزال يتعين عليك أن تضع في اعتبارك أنك ، في الأساس ، تبني أكثر من تطبيق واحد وهذا يتطلب التفكير في كل تطبيق من التطبيقات التي تقوم بإنشائها .
مزيد من الموارد
- موقع ويب Flutter Gallery وتطبيق Android ، يعرضان استخدام أدوات Flutter النموذجية للأنظمة الأساسية المختلفة ولا أدريتها في النظام الأساسي
- وثائق Flutter API على TargetPlatform
- وثائق Flutter على إنشاء الحزم والإضافات
- وثائق Flutter على تعديلات النظام الأساسي
- توثيق MDN على ملفات تعريف الارتباط