I’m going to show you how you can use Flutter and AWS Amplify to quickly go from nothing to a working cross-platform mobile application with authentication and backend infrastructure. What would usually take a small dev team a week or so to setup can be achieved in a fraction of the time using this toolkit.
إذا اتبعت هذا البرنامج التعليمي ، فقد يستغرق منك أقل من ساعة. حسنًا ، استغرقت مني عدة ساعات مقاتلة مع مشاكل مختلفة ، ولكن نأمل أنني قد وثقتها جيدًا بما يكفي بحيث لا ينبغي أن تواجهها.
إليك النتيجة النهائية. إذا كنت تريد “نسخة قدمتها مسبقًا” ، فاتبع الخطوات الموجودة في ملف القراءة ، ويجب أن تحصل عليها وتعمل في حوالي خمسة عشر دقيقة. وهنا رابط GitHub.
هذا البرنامج التعليمي يتكون من خمسة أجزاء:
- المتطلبات الأساسية وإعداد قاعدة التعليمات البرمجية
- إضافة التوثيق
- تحميل صورة الملف الشخصي
- تخزين تفاصيل المستخدم
- إضافة بعض الفضة التصميمية
توصية
Flutter هي منصة ناضجة جدًا تم استخدامها لعدة سنوات الآن ، مع مجتمع ناشئ والعديد من المكونات والوظائف لتحقيق معظم الأشياء.
Amplify أيضًا منصة قوية ؛ ومع ذلك ، وجدت أن وظائف API صعبة الاستخدام ولم تكن مكتبات Flutter محدثة مع أحدث الإعلانات والميزات في Amplify. على وجه الخصوص ، كان العمل مع AppSync GraphQL وDataStore (لمخزن البيانات عند الإنترنت الخافت والتزامن) هشًا نسبيًا (كما سترى لاحقًا).
معًا ، هذان الاثنان مزيج رائع لتسريع تطوير نماذج التطبيقات المحمولة ، ولكن عندما تشعر أنك تنحني Amplify على إرادتك ، لا تخشى التخلي عنها للاستعانة بشكل مباشر بخدمات AWS التي تعتبر تجريدية.
تطبيق العرض التوضيحي الذي بنيته يحمل معلومات ملف المستخدم—متطلب شائع بين التطبيقات. يمكنك إنشاء حساب وتسجيل الدخول، وتحميل صورة ملف التعريف، وتقديم بعض التفاصيل عن نفسك. سنتطرق للتفاصيل في الواجهة الكاملة—العمل مع فلاتر ودارت لرمز التطبيق إلى أبعد الحدود مثل دايناموDB لتزويدك بالأطلاع الكاملة على ما تحتاج معرفته.
الجزء الأول: المتطلبات الأساسية وتهيئة قاعدة التعليمات
يفترض هذا البرنامج التعليمي أن لديك ما يلي مهيأً بالفعل على جهاز الكمبيوتر:
Code Editor/ IDE | I use VSCode as it has a good set of Flutter and Dart plugins that speed up development, such as auto loading of dependencies, Dart linting, and intellisense. You’re free to use whatever IDE works for you though |
AWS Account | Create an AWS account if you don’t already have one. Visit AWS’ official page for steps to create an AWS account.
All of what we’ll use today is part of the free tier, so it shouldn’t cost you anything to follow this tutorial. |
AWS CLI and AWS Amplify CLI | Install AWS and Amplify CLI tooling.
Make sure you have an up-to-date version of Node and NPM (this is what the CLIs use). Visit Node.Js’ official website to download the up-to-date version. If you need to run multiple versions of Node, I recommend using NVM to manage and switch between them. To install AWS CLI, visit AWS’ official page. |
XCode (for iOS) | If you don’t have access to a Mac, you can deploy EC2 instances running MacOS in AWS these days, which you can use when you need to build iOS artifacts.
Download Xcode through the Mac App Store. Follow the rest of the steps here to set it up ready for iOS Flutter development. |
Android Studio (for Android) | Follow the steps here to be ready for Android Flutter development. |
Flutter SDK | Follow these steps to get Flutter and its dependencies up and running (if you’re on a Mac that is, other guides are available for other OSes). |
فلاتر وأمبليفي لديهما أدوات تأصيل تنشئ بنية المشروع الأولية. من المهم القيام بهذه بترتيب معين؛ وإلا فإن بنية المجلد الخاصة بك لن تتناسب مع ما تتوقعه الأدوات، مما سيسبب لك مشاكل في تصحيحها لاحقًا.
تأكد من إنشاء بنية قاعدة التعليمات باستخدام فلاتر أولاً، ثم تهيئة أمبليفي داخله.
I used the official Flutter getting started documentation to kick things off for my demo.
دعونا نرى ما إذا كان بإمكاننا جعل فلاتر تعمل. أولاً، للتأكد من تثبيته بشكل صحيح وإضافته إلى مسارك، يمكنك تشغيل flutter doctor
.
إذا كانت هذه أول تجربة لك في تطوير الهواتف المحمولة، سيكون هناك بعض الأشياء تحتاج إلى التعامل معها هنا. بالنسبة لي كانت:
- تثبيت Android Studio (وأندرويد SDK CLI).
- تثبيت XCode وكوكابود.
- الموافقة على شروط وأحكام للأدوات الخاصة بأندرويد وآي أو إس.
إنشاء بنية رمز التطبيق
عندما تكون قد حصلت على جميع المتطلبات الأساسية، يمكنك إنشاء تأصيل فلاتر. ينشئ هذا الأمر المجلد الذي سنعمل فيه، لذا استخدم هذا الأمر من داخل دليل والد:
flutter create flutterapp --platforms=android,ios
I’ve specified Android and iOS as target platforms to remove the unnecessary config for other platforms (e.g. web, Windows, Linux).
قد ترغب في تسمية الدليل العلوي الموجود الآن في حالة عدم رغبتك في تطابق اسم تطبيقك. قمت بتغييره من “flutterapp” إلى “flutter-amplify-tutorial” (اسم مستودع git الخاص بي).
في هذه المرحلة، قام Flutter بإنشاء ثلاثة وسبعين ملفًا لنا. دعونا نلقي نظرة على ما هذه:
المجلدات التي سنقضي فيها الكثير من الوقت هي ios/android
و lib/
. داخل المجلدات ios
و android
يوجد موارد مشروع يمكن فتحها باستخدام XCode و Android Studio على التوالي. تعمل هذه المشاريع كوسيلة للتواصل بين رمز Dart غير المستهدف للمنصة والمنصات المستهدفة الخاصة بك، ويمكنك استخدامها لاختبار تطبيقك ضد المنصات المقابلة. دعونا نجرب ذلك مع iOS الآن:
إعداد iOS
open -a Simulator
flutter run
على جهاز Mac الخاص بي، بإعداد XCode أدنى، قد تقدمت من لا شيء إلى تشغيل محاكي iPhone 14 Pro Max بتطبيق Flutter المبني بالكامل، وهو أمر رائع.
إذا رأيت ما يلي: فأهلاً بك، لقد نجحت في توليد البنية الأساسية.
يمكنك أيضًا فتح مشروع ios/Runner.xcodeproj
داخل XCode أيضًا، واستكشاف محتوياته وتشغيله ضد المحاكيات والأجهزة المادية كما تفعل مع أي مشروع XCode آخر.
إعداد Android
Android أقل وضوحًا، حيث يجب تكوين محاكي بشكل صريح في Android Studio قبل أن تتمكن من تشغيله. افتح مشروع android/flutterapp_android.iml
داخل Android Studio للبدء، ثم يمكنك تكوين وتشغيل محاكي لاختبار التطبيق.
اعطي Android Studio بضع دقائق لتحميل Gradle وجميع المرتبطات المطلوبة لتشغيل التطبيق – يمكنك تتبع تقدم هذا في شريط التقدم في الزاوية اليمنى السفلية.
عندما يستقر Android Studio، إذا كان لديك بالفعل جهاز محاكاة معدّل في AVD، يجب أن تتمكن من الضغط على زر التشغيل في الزاوية اليمنى من النافذة:
وبينما تشاهد، نفس التطبيق على Android:
هذا يُظهر رمز التطبيق النموذجي الذي يتم توفيره عندما تنشئ مشروع Flutter جديدًا. على مدار هذه البرنامج التعليمي، سنستبدل هذا الرمز تدريجيًا برمزنا الخاص.
هذه نقطة جيدة لإجراء عملية git commit، بما أننا حصلنا الآن على الأساسيات المؤهلة لتطوير Flutter. نحن الآن في نقطة يمكننا من خلالها البدء في التلاعب برمز Flutter ورؤية النتائج على iOS وAndroid بشكل متزامن.
يستخدم Flutter Dart كلغة وسطى بين Android وiOS، وجميع الرموز التي ستتفاعل معها توجد داخل المجلد lib/
. يجب أن يكون هناك ملف main.dart
، وهو المكان الذي سنبدأ التلاعب منه.
تكوين ونشر تطبيق جديد باستخدام Amplify
الآن بعد أن أصبحت أدوات التطبيق المحمول جاهزة للعمل، نحتاج إلى بعض البنية التحتية لدعم وظائف التطبيق.
سنستخدم خدمات AWS والعديد منها لدعم تطبيقنا، لكن جميعها ستُدار باستخدام خدمة AWS Amplify. سيتم التعامل مع معظمها بشكل شفاف بالنسبة لنا، وبدلاً من القلق بشأن أي خدمات يجب استخدامها، سنركز على الميزات التي نريد نشرها.
للبدء، داخل مجلد الكود قم بتنفيذ ما يلي:
amplify init
هذا الأمر يبدأ AWS Amplify داخل مشروعك. إذا لم تستخدمه من قبل، سيطلب منك عددًا من الأسئلة. بالنسبة للأشخاص اللاحقين الذين يعملون بشكل تعاوني على المشروع، يقوم هذا الأمر بإعداد بيئتهم المحلية بتكوين Amplify الموجود بالفعل.
سيقوم هذا بتوفير بعض موارد AWS الأولية لتخزين تكوين وحالة التطبيق Amplify الخاص بك، وتشمل ذلك خزّنة S3.
قد يبدو شريط تقدم النشر وحالته المذكورة أعلاه مألوفًا بالنسبة لبعض الأشخاص—إنه CloudFormation، ومثل AWS CDK، يستخدم Amplify CFN خلف الكواليس لتوفير جميع الموارد المطلوبة. يمكنك فتح وحدة تحكم حزم CloudFormation لرؤيتها في العمل:
أخيرًا، عندما يكتمل الـ CLI، يجب أن ترى تأكيدًا مشابهًا للأدناه، وستتمكن من رؤية التطبيق المنشأ الجديد في وحدة تحكم Amplify:
إدارة البيئات
تتضمن AWS Amplify مفهوم “البيئات”، وهي نشرات معزولة لتطبيقك وموارده. تاريخيًا، كان عليك إنشاء مفهوم البيئات داخل النظام الذي كنت تستخدمه: (على سبيل المثال، CloudFormation، CDK)، باستخدام أنواع التسميات والمعلمات. في Amplify، إنه مواطن أول أمثاله—يمكنك الحصول على عدة محاولات تسمح بأنماط، مثل توفير بيئات مشتركة، حيث تتم الترقيات من خلال التغييرات (على سبيل المثال، Dev > QA > PreProd > Prod) وتوفير بيئات لكل مطور أو فرع ميزة.
يمكن لـ Amplify أيضًا تكوين وتوفير خدمات CI/CD بالنسبة لك باستخدام اضافة دعم الاستضافة في Amplify ودمجها في تطبيقاتك لتوفير بيئة تطوير شاملة. يقوم بإعداد CodeCommit وCodeBuild وCodeDeploy للتعامل مع إدارة نظام التحكم في المصادر والبناء ونشر التطبيقات. لا يغطي هذا البرنامج التعليمي، ولكن يمكن استخدامه لأتمتة البناء والاختبار ونشر الإصدارات إلى متاجر التطبيقات.
الجزء الثاني: إضافة التوثيق
عادة، ستحتاج لمعرفة خدمة توثيق AWS Cognito والخدمات المؤيدة مثل IAM، وربطها معًا باستخدام شيء مثل CloudFormation، Terraform، أو CDK. في Amplify، الأمر بسيط كما يلي:
amplify add auth
Amplify add
يتيح لك إضافة مختلف “الميزات” إلى مشروعك. ورءوس الكثير، Amplify سينشر ويقوم بتكوين جميع الخدمات المطلوبة التي تحتاجها باستخدام CloudFormation، لكي تستطيع التركيز أكثر على ميزات تطبيقاتك وأقل على التجهيز.
عندما أقول إنه بسيط كالكتابة الثلاث كلمات السحرية أعلاه… ليس بهذه البساطة تمامًا. ستطلب منك Amplify أسئلة مختلفة لفهم كيف تريد توثيق الأشخاص وما هي السيطرات التي تريد وضعها. إذا اخترت “التكوين الافتراضي”، ستقوم Amplify بإعداد التوثيق بالإعدادات الافتراضية المعقولة لتحصل على التشغيل بسرور. سأختار “التكوين اليدوي” لإظهار مدى تخصيص Amplify.
الإعداد المذكور أعلاه يتيح لك إنشاء حسابات باستخدام رقم هاتفك فقط (بدون عنوان بريد إلكتروني مطلوب)، ويؤكد أنك المالك الحقيقي لهذا الرقم باستخدام MFA للتحقق والمحاولات الأخرى لتسجيل الدخول. أنصح بشدة باستخدام OAuth كآلية توثيق اعتيادية، لكنني لم أستخدمها هنا للتخفيف من التعقيد.
الآن، عندما تضيف ميزات، فإنها لا تُنشأ فورًا. لهذا السبب تكون الأوامر سريعة الإكمال بشكل مخيف. كل هذه الأوامر تجهز تكوين تطبيق Amplify الخاص بك (والبيئة المحلية) لنشر هذه الميزات.
لنشر الميزات (أو أي تغييرات في التكوين)، تحتاج إلى القيام بعملية “الدفع”:
amplify push
ملاحظة: هذا يختلف عن أمر amplify publish
، الذي يبني وينشر خدمتي الخلفية والأمامية. الدفع يُحدث الموارد الخلفية فقط (وهذا كل ما سنحتاجه في هذا البرنامج التعليمي لأننا سنبني تطبيقات محمولة).
عندما تضيف التوثيق (أو أي ميزة Amplify)، يضيف Amplify ملف Dart يسمى lib/amplifyconfiguration.dart
. يتم تجاهل هذا الملف في Git لأنه يحتوي على بيانات سرية متعلقة بالموارد المنشأة ويتم مزامنته تلقائيًا مع بيئة Amplify التي تعمل بها. يمكنك الحصول على مزيد من المعلومات حول هذا هنا.
في هذه المرحلة، قمنا بإعداد Amplify مع تكوين تطبيق وبيئة التطوير وتكوين Cognito لإعداد المصادقة. من الجيد الآن القيام بعمل git commit إذا كنت تتابع، حتى تتمكن من العودة إلى هذه النقطة اذا لزم الأمر. يجب أن يكون Amplify قد أنشأ بالفعل ملف .gitignore
لك، مقصوراً على جميع الملفات غير الضرورية.
الآن بعد أن أعددنا بنية المصادقة الخلفية، يمكننا البدء في بناء تطبيقنا المحمول باستخدام Flutter.
مصادقة المستخدمين في تطبيقنا
I’m following the steps outlined in the authentication for the AWS Amplify tutorial here.
هذا باستخدام شاشات ومنهجية المصادقة الافتراضية المدمجة في amplify_flutter
. أضف اعتمادات Flutter Amplify عن طريق إضافة ما يلي تحت “dependencies” داخل ملف pubspec.yaml
:
amplify_flutter: ^0.6.0
amplify_auth_cognito: ^0.6.0
إذا لم تكن تستخدم امتدادات Flutter وDart داخل VSCode (أو باستخدام VSCode) ستحتاج إلى اتباع هذا بأمر flutter pub get
. إذا كنت تستخدمها، فسيقوم VSCode تلقائيًا بتشغيل هذا عندما تحفظ ملف pubspec.yaml
.
هناك نهج سريع لدمج المصادقة باستخدام مكتبة واجهة مستخدم مصادقة مصنعة، ممتازة لتهيئة تدفق تسجيل الدخول الذي يمكن تخصيصه لاحقًا. سنستخدم ذلك في هذا البرنامج التعليمي لإظهار المجموعة الواسعة من مكتبات Amplify المتاحة، وكيف يمكنك دمجها بسرعة في تطبيقك.
الخطوات لدمج مكتبة المصادقة الافتراضية هي هنا.
يمكننا نقل الوسيط المصدري الذي يُزين الوجهة المُعدة في رمز المثال إلى الرمز المقدم في مثال بدء سريع Flutter هكذا:
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return Authenticator(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// هذا هو السمة (الثيم) لتطبيقك.
//
// جرب تشغيل تطبيقك باستخدام "flutter run". سترى
// أن التطبيق يحتوي على شريط أزرق. ثم، دون إنهاء التطبيق، جرب
// تغيير primarySwatch أدناه إلى Colors.green ثم استدع ال
// "تحديث ساخن" (اضغط "r" في وحدة التحكم حيث قمت بتشغيل "flutter run"،
// أو ببساطة حفظ تغييراتك ل"تحديث ساخن" في محرر Flutter).
// لاحظ أن العداد لم يعدل إلى الصفر؛ لم يتم إعادة تشغيل التطبيق.
// التطبيق لم يتم إعادته.
primarySwatch: Colors.blue,
useMaterial3: true),
home: const MyHomePage(title: 'Flutter Amplify Quickstart'),
builder: Authenticator.builder(),
));
}
@override
void initState() {
super.initState();
_configureAmplify();
}
void _configureAmplify() async {
try {
await Amplify.addPlugin(AmplifyAuthCognito());
await Amplify.configure(amplifyconfig);
} on Exception catch (e) {
print('Error configuring Amplify: $e');
}
}
}
ما هو الوجهة؟
إنها الوحدة الأساسية في Flutter المستخدمة لتكوين تخطيطات وعناصر الواجهة المستخدم. تقريبًا كل شيء في تخطيطات Flutter هي وجوه—أعمدة، تصفيح، دفع وتهيئة، عناصر معقدة، إلخ. المثال في وثائق بدء Flutter السريع تستخدم وجهة “Center” تليها وجهة “Text” لعرض قطعة نصية محاذاة في المركز تقول “مرحبًا بكم في العالم.”
الكود أعلاه يزين وجهة MyHomePage
بواسطة وجهة المصدر، يضيف البرنامج النصي AmplifyAuthCognito
، ويأخذ التكوين الذي أنتجته الأمر السابق amplify add auth
في lib/amplifyconfiguration.dart
للتواصل تلقائيًا بجهاز AWS Cognito User Pool الخاص بك.
بعد تشغيل Flutter، استطلاع الرأي لتجميع التوافق التوثيق، استغرقت خطوة “تشغيل تثبيت الوحدة” بالنسبة لي بعض الوقت لإكمالها. فقط احتفظ بالصبر (تقريبًا 5 دقائق).
بمجرد إجراء تلك التعديلات التوثيق وبدء التطبيق، يتم ترحيبك بشاشة مسح الورود أساسية ولكنها عاملة.
باستخدام جريدة الأخبار “إنشاء حساب”، يمكنك توفير رقم هاتفك وكلمة مرور، ثم يتم تقديم تحد لإكمال التسجيل. ثم يمكنك رؤية أن المستخدم تم إنشاؤه داخل حزمة مستخدمي Cognito:
يمكنك اختبار هذا بسهولة كافية على جهاز Android محاكي أيضًا. لا تحتاج حتى إلى مغادرة VSCode إذا كنت قد قمت بتثبيت البرامج المساعدة لFlutter وDart، لذا افتح Android Studio ليس ضروريًا. ما عليك سوى تحديد اسم الجهاز النشط الحالي (iPhone) في أسفل الزاوية اليمنى من VSCode، ثم تبديله إلى جهاز Android محاكي قمت بإنشائه مسبقًا، ثم اضغط على “F5” لبدء التصحيح. التجربة مشابهة جدًا للأيبو:
عند نشر لأول مرة بعد تنفيذ مكتبة التوثيق، واجهت الاستثناء التالي عند محاولة بناء التطبيق:
uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [:amplify_auth_cognito_android]
Flutter مفيد حقًا في هذه الحالة، حيث يقدم توصية مباشرة بعد إفراغ تتبع الأخطاء هذا:
يبدو أن SDK Flutter يتغلب بالفعل على هذا في ملف build.gradle
الخاص بنا:
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
...
defaultConfig {
// TODO: حدد معرّف التطبيق الفريد الخاص بك (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.flutterapp"
// يمكنك تحديث القيم التالية لتتناسب مع احتياجات تطبيقك.
// لمزيد من المعلومات، انظر: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
في حين أن Flutter، كحد أدنى، يتطلب استخدام API 16 (معلن في flutter.gradle
)، فإن مكتبة Amplify Auth تحتاج إلى ما لا يقل عن 21. لإصلاح هذا، قم بتغيير minSdkVersion
من “flutter.minSdkVersion” إلى “21”.
بمجرد توثيق الهوية، سيتم تقديمك بتطبيق الـ “ضربة الزر” النموذجية الموضح سابقاً. الآن، هو وقت البدء في تخصيصه لاحتياجاتنا.
الجزء الثالث: تحميل صورة الملف الشخصي
في هذا المثال، سنستخدم هذه القدرة للسماح للمستخدمين بتحميل صورة لهم لاستخدامها كأفاتريد داخل التطبيق.
تريد إضافة ميزات التخزين إلى تطبيقك؟ لا مشكلة، فقط افعل:
amplify add storage
وسيقوم Amplify بتوفير الخدمات الخلفية المطلوبة لتطبيقك لاستخدام التخزين السحابي. Amplify يدمج Flutter بسهولة مع S3 للسماح لمستخدمي تطبيقك بتخزين الكائنات. المرونة التي يوفرها S3 تسمح لك بتخزين جميع أنواع الملفات، وبالمشاركة مع Cognito و Amplify، يمكنك بسهولة توفير مناطق خاصة للمستخدمين لتخزين الصور ومقاطع الفيديو والملفات وغيرها.
يمكن حفظ الملفات بوجود حق الوصول العام أو المحمي أو الخاص:
Public | Read/Write/Delete by all users |
Protected | Creating Identify can Write and Delete, everyone else can Read |
Private | Read/Write/Delete only by Creating Identity |
بالنسبة لصورة الملف الشخصي لدينا، سنقوم بإنشائها بوجود حق الوصول المحمي بحيث يمكن للمستخدم التحديث والحذف فقط من أفاتريدهم، ولكن غيرهم في التطبيق سيتمكنون من مشاهدتها.
هنا سنبدأ في تصميم وبناء هيكل تطبيقنا. يتم تكامل Flutter بشكل وثيق مع نظام تصميم Material، الذي يستخدم بشكل كبير في تطوير تطبيقات الهاتف المحمول لتوفير مظهر وشعور متناسق. يوفر مجموعة من المكونات متوافقة مع أنظمة التشغيل المتعددة، أساليبها يمكن التغلب عليها لبناء تجربة محددة بالنسبة لعلامتك التجارية.
قالب البدء في استخدام Flutter يقوم بالفعل بتنظيم بعض الأدوات باستخدام أداة ماديالب. سبق لنا تزيين هذا مع أداة أوتينتكيكيتور. الآن، سنوسع على أداة تشغيل الموقع الأساسي لطفل ماديالب لتزويده بصورة ملف المستخدم.
تقوم بتجميع الأدوات معًا في شجرة، المعروفة بـ “هيكل الأدوات”. دائمًا ما تبدأ بأداة رئيسية. في تطبيقنا هي الأداة المضمنة للمصادقة التي تعالج التسجيل الأولي. Scaffold
هي أداة جيدة لتأسيس تخطيطاتك: غالبًا ما تستخدم كأداة مستوى عال بتطبيقات المودة ولديها العديد من المواضع البديلة، مثل زر العمل العائم، والصفحة السفلية (لتمرير التفاصيل الإضافية)، وشريط التطبيق، إلخ.
أولاً، دعنا نضيف فقط أداة الصورة التي تشير إلى عنوان URL متصل. سنستبدل هذا لاحقًا بواحد نأخذه ونرفعه إلى S3. استخدمت الموارد التالية لإضافة صورة مع إعدادية مستديرة:
- API Flutter
- Google Flutter
في مجموعة أطفال الأداة العميقة العمود، أضف الأداة المحتوى التالي:
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
Container(
width: 200,
height: 200,
decoration: const BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
fit: BoxFit.fill),
),
)
],
يمكننا الآن عرض صورة من الويب:
بعدها، سنتيح للمستخدم اختيار صورة ملف تعريفي من صورة على جهازه. بعض البحث على جوجل كشفت عن هذه المكتبة التي تبسط تفاصيل اختيار الصور:
- Google: “Pub Dev Packages Image Picker”
كل ما يتطلبه هو سطران من الكود لتحثير المستخدم على اختيار صورة:
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
بالنسبة لـ iOS، عليك إضافة مفتاح NSPhotoLibraryUsageDescription
إلى ملف تكوين Xcode <project root>/ios/Runner/Info.plist
لطلب الوصول لعرض صور المستخدم؛ وإلا ستتعرض التطبيق لفشل.
سنربط هذا بواجهة GestureDetector
، حيث عند تلقي نقرة، ستحث المستخدم على اختيار صورة لصورة ملف تعريفه:
ImageProvider? _image;
...
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
GestureDetector(
onTap: _selectNewProfilePicture,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: _image ?? _placeholderProfilePicture(), fit: BoxFit.fill),
),
),
)
...
]
void _selectNewProfilePicture() async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
var imageBytes = await image.readAsBytes();
setState(() {
_image = MemoryImage(imageBytes);
});
}
}
_placeholderProfilePicture() {
return const AssetImage("assets/profile-placeholder.png");
}
استدعي setState()
، وتحديث حقول الواجهة التي يتم تمريرها داخل اللامدا إلى Flutter، بحيث يعرف استدعاء دالة build()
، حيث يمكن استخدام الحالة المحدثة لإعادة رسم الواجهة. في حالتنا، ستتم ملء صورة الملف الشخصي، لذا سننشئ واجهة محتوى تعرض الصورة. عامل التشغيل ??
المضمن للأصلاح يوفر بديلًا لصورة ملف تعريف محلي في حالة عدم اختيار المستخدم لصورة بعد.
كما ستحتاج إلى إضافة صورة بديلة للملف الشخصي إلى مستودعك وإشارة إليها في ملف pubspec.yml
ليتم التقاطها في البناء. يمكنك استخدام الصورة من مستودعي، بينما تضيف هذا إلى ملف pubspec.yml
الخاص بك:
# القسم التالي محدد لحزم Flutter.
flutter:
...
# لإضافة الأصول إلى تطبيقك، أضف قسم الأصول، مثل هذا:
assets:
- assets/profile-placeholder.png
في هذه المرحلة، نستطيع اختيار صورة ملف تعريفي من معرض صور الجهاز، وعرضها كصورة مستديرة في التطبيق. ومع ذلك، هذه الصورة لا تُحتفظ بها في أي مكان – بمجرد إغلاق التطبيق؛ يتم فقدانها (ولا يمكن للمستخدمين الآخرين رؤية صورتك أيضًا).
ما سنفعله بعد ذلك هو ربط هذا ببعض مستودعات السحابة – AWS S3. عندما يختار المستخدم صورة من معرض صور جهازه، سنقوم بتحميلها إلى منطقته الخاصة في S3، ثم نجعل واجهة الصورة تستقبل الصورة من هناك (بدلاً من مباشرة من الجهاز)نفسه:
void _selectNewProfilePicture() async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
var imageBytes = await image.readAsBytes();
final UploadFileResult result = await Amplify.Storage.uploadFile(
local: File.fromUri(Uri.file(image.path)),
key: profilePictureKey,
onProgress: (progress) {
safePrint('Fraction completed: ${progress.getFractionCompleted()}');
},
options:
UploadFileOptions(accessLevel: StorageAccessLevel.protected));
setState(() {
_image = MemoryImage(imageBytes);
});
}
}
الآن، عندما يختار مستخدمنا صورة من جهازه، سيقوم تطبيقنا بتحميلها إلى S3 ثم عرضها على الشاشة.
بعد ذلك، سنجعل التطبيق يقوم بتنزيل صورة ملف التعريف الخاصة بالمستخدم من S3 عند بدء التشغيل:
@override
void initState() {
super.initState();
_retrieveProfilePicture();
}
void _retrieveProfilePicture() async {
final userFiles = await Amplify.Storage.list(
options: ListOptions(accessLevel: StorageAccessLevel.protected));
if (userFiles.items.any((element) => element.key == profilePictureKey)) {
final documentsDir = await getApplicationDocumentsDirectory();
final filepath = "${documentsDir.path}/ProfilePicture.jpg";
final file = File(filepath);
await Amplify.Storage.downloadFile(
key: profilePictureKey,
local: file,
options:
DownloadFileOptions(accessLevel: StorageAccessLevel.protected));
setState(() {
_image = FileImage(file);
});
} else {
setState(() {
_image = const AssetImage("assets/profile-placeholder.png");
});
}
}
بعدها، سنعيد تنظيم منطق صورة ملف التعريف ليصبح مكونًا قابلاً لإعادة الاستخدام. يمكنك مشاهدة المكون النهائي في مستودعي GitHub الذي يضم جميع المنطق أعلاه. ثم يمكنك تنظيف مكون _MyHomePageStage
ووضع الواجهة الجديدة الخاصة بك في التسلسل على النحو التالي:
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Text(
'User Profile',
style: Theme.of(context).textTheme.titleLarge,
)),
const ProfilePicture(),
TextField(...
للانتهاء من صورة ملف التعريف، سنضيف مؤشر تحميل لتوفير التغذية الراجعة للمستخدمين بأن شيئًا ما يحدث. سنستخدم حقل منطقي _isLoading
لمتابعة متى يتم تحميل الصورة، والذي سيتحكم في عرض المؤشر أو الصورة:
class _ProfilePictureState extends State<ProfilePicture> {
ImageProvider? _image;
bool _isLoading = true;
...
void _retrieveProfilePicture() async {
...
setState(() {
_image = FileImage(file);
_isLoading = false;
});
} else {
setState(() {
_image = const AssetImage("assets/profile-placeholder.png");
_isLoading = false;
});
}
}
void _selectNewProfilePicture() async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
_isLoading = true;
});
....
setState(() {
_image = MemoryImage(imageBytes);
_isLoading = false;
});
}
}
الجزء الرابع: تخزين تفاصيل المستخدم (موجز)
رائع، لدينا الآن هيكل تطبيق محمول يحتوي على مستخدمين، وتحقق الهوية، وصور الملفات الشخصية. دعونا نرى إذا كان يمكننا إنشاء واجهة برمجة تطبيقات تستخدم بيانات الاعتماد للمستخدمين لاسترجاع معلومات إضافية عنهم.
عادة ما أقول، “أنت تريد واجهة برمجة تطبيقات؟ بسيطة:”
amplify add api
هذا هو المكان الذي كان فيه معظم الجهد وإصلاح الأخطاء لأنه، بناءً على التكوين الذي تختاره، قد لا يكون مدعومًا بشكل كامل داخل بيئة Amplify وFlutter. استخدام مستودع البيانات وم��يل المسبق يمكن أن يؤدي أيضًا إلى أنماط قراءة غير فعالة، قد تصبح بسرعة مكلفة وبطيئة.
يقدم Amplify واجهة برمجة تطبيقات عالية المستوى للتفاعل مع البيانات في AppSync، لكن في هذا الدليل، سأستخدم GraphQL مع استعلامات منخفضة المستوى لأنه يوفر مرونة أكبر ويسمح باستخدام فهرس ثانوي عالمي في DynamoDB لتجنب فحص الجداول. إذا كنت تريد فهم كيف وصلت إلى هنا وما هي المصاعب المختلفة، قم بالاطلاع على “التحديات في العمل وتعديل AWS Amplify وAppsync مع Flutter.”
يحاول Amplify الافتراض في الأسئلة التي تطرح عند إنشاء واجهة برمجة التطبيقات، ولكن يمكنك تجاوز أي منها عن طريق التمرير للاعلى إلى الخيار الذي تريد تغييره. في هذا السيناريو، نريد نهاية GraphQL (للاستفادة من DataStore)، وتتم معالجة تصريح واجهة برمجة التطبيقات بواسطة مستخدم Cognito Pool، لأننا نريد تطبيق التحكم في الوصول الدقيق حتى يتمكن المستخدم فقط من تحديث أو حذف تفاصيله (لكن يمكن لغيره من المستخدمين مشاهدتها).
عندما ننشئ واجهة برمجة التطبيقات، يإنشئ Amplify نموذج GraphQL الأساسي. سنقوم بتحديث هذا وإضافة بعض قواعد الأذونات قبل دفع تغييرات واجهة برمجة التطبيقات.
قم بتعديل نموذج “ToDo” من مخطط GraphQL لخدمة معلومات المستخدم الخاصة بنا معلومات.
type UserProfile @model
@auth(rules: [
{ allow: private, operations: [read], provider: iam },
{ allow: owner, operations: [create, read, update, delete] }
])
{
userId: String! @index
name: String!
location: String
language: String
}
القاعدة الخاصة تسمح للمستخدمين المسجلين بعرض ملفات تعريف أي شخص آخر. بعدم استخدام العامة، نحن نمنع الأشخاص الذين ليسوا مسجلين من مشاهدة الملفات الشخصية. مزود IAM يمنع المستخدمين من الوصول مباشرة إلى واجهة برمجة التطبيقات GraphQL – يجب أن يستخدموا التطبيق واستخدام دور “غير مصرح به” داخل بيئة تشغيل Cognito (أي تسجيل الخروج) لمشاهدة تفاصيل المستخدم.
القاعدة “المالك” تسمح للمستخدم الذي أنشأ الملف الشخصي بإنشاء وقراءة وتحديث ملفه الخاص. في هذا المثال، لا نسمح لهم بحذف ملفاتهم الشخصية ولكن.
في هذه المرحلة، يمكننا توفير بنيتنا السحابية التي تدعم ميزة واجهة برمجة التطبيقات:
amplify push
عند تغيير نموذج GraphQL الحالي من ToDo إلى UserProfile، إذا قمت سابقًا بتنفيذ amplify push
وجددت البنية التحتية، قد تتلقى رسالة خطأ تشير إلى أن التغيير المطلوب يتطلب تدمير جدول DynamoDB الحالي. Amplify يمنعك من القيام بذلك في حالة فقدان البيانات من خلال حذف جدول ToDo الحالي. إذا واجهت هذا الخطأ، تحتاج إلى تشغيل amplify push --allow-destructive-graphql-schema-updates
.
عندما تقوم بتنفيذ amplify push
، Amplify وCloudFormation سيقومون بإعداد واجهة برمجة التطبيقات GraphQL AppSync والمحللين المتوسطين وجدول DynamoDB المؤوي كما هو موضح هنا:

بمجرد تحديد مخطط GraphQL الخاص بنا، يمكننا استخدام Amplify لإنشاء رمز Dart الذي يمثل طبقة النموذج وطبقة المستودع التي يمكنها العمل مع الواجهة البرمجية:
amplify codegen models
في هذه المرحلة، يمكننا إضافة بعض الحقول المدخلة إلى صفحتنا لملء اسم المستخدم، الموقع، واللغة الفعلية المفضلة.
هذا ما تبدو عليه تغييرات حقل النص في مكون _MyHomePageState
الخاص بنا:
class _MyHomePageState extends State<MyHomePage> {
final _nameController = TextEditingController();
final _locationController = TextEditingController();
final _languageController = TextEditingController();
@override
Widget build(BuildContext context) {
...
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Text(
'User Profile',
style: Theme.of(context).textTheme.titleLarge,
)),
const ProfilePicture(),
TextField(
decoration: const InputDecoration(labelText: "Name"),
controller: _nameController,
),
TextField(
decoration:
const InputDecoration(labelText: "Location"),
controller: _locationController,
),
TextField(
decoration: const InputDecoration(
labelText: "Favourite Language"),
controller: _languageController,
)
]
ثم ربط حقول النصوص بواجهة برمجة تطبيقات GraphQL AppSync بحيث عندما يضغط المستخدم على زر “حفظ” الإجراء العائم، يتم مزامنة التغييرات مع DynamoDB:
floatingActionButton: FloatingActionButton(
onPressed: _updateUserDetails,
tooltip: 'Save Details',
child: const Icon(Icons.save),
),
)
],
);
}
Future<void> _updateUserDetails() async {
final currentUser = await Amplify.Auth.getCurrentUser();
final updatedUserProfile = _userProfile?.copyWith(
name: _nameController.text,
location: _locationController.text,
language: _languageController.text) ??
UserProfile(
name: _nameController.text,
location: _locationController.text,
language: _languageController.text);
final request = _userProfile == null
? ModelMutations.create(updatedUserProfile)
: ModelMutations.update(updatedUserProfile);
final response = await Amplify.API.mutate(request: request).response;
final createdProfile = response.data;
if (createdProfile == null) {
safePrint('errors: ${response.errors}');
}
}
أخيرًا، عندما يفتح مستخدمونا التطبيق، نريد سحب أحدث ملف تعريف من السحابة. لتحقيق ذلك، نجعل مكالمة كجزء من تهيئة _MyHomePageState
:
@override
void initState() {
super.initState();
_getUserProfile();
}
void _getUserProfile() async {
final currentUser = await Amplify.Auth.getCurrentUser();
GraphQLRequest<PaginatedResult<UserProfile>> request = GraphQLRequest(
document:
'''query MyQuery { userProfilesByUserId(userId: "${currentUser.userId}") {
items {
name
location
language
id
owner
createdAt
updatedAt
userId
}
}}''',
modelType: const PaginatedModelType(UserProfile.classType),
decodePath: "userProfilesByUserId");
final response = await Amplify.API.query(request: request).response;
if (response.data!.items.isNotEmpty) {
_userProfile = response.data?.items[0];
setState(() {
_nameController.text = _userProfile?.name ?? "";
_locationController.text = _userProfile?.location ?? "";
_languageController.text = _userProfile?.language ?? "";
});
}
}
لدينا الآن واجهة برمجة تطبيقات يمكن تخزين البيانات فيها، محمية بواسطة Cognito ومدعومة بواسطة DynamoDB. إنه أمر جميل بالنظر إلى أنني لم أكتب أي كود للبنية التحتية.
إذًا في هذه المرحلة، لدينا وسيلة للاستعلام، عرض، وتحديث معلومات ملف تعريف المستخدم. يبدو لي وكأنه نقطة حفظ أخرى.
الجزء الخامس: إضافة بعض الفن التصميمي
أخيرًا، يبدو التطبيق النموذجي الذي طورناه ببساطة. حان الوقت لجعله أكثر حيوية.
الآن، أنا لست خبيرًا في الواجهة المستخدم، لذا استلهمت بعض الإلهام من dribbble.com وقررت الذهاب بخلفية عالية الحجم ومنطقة البطاقة البيضاء المتباينة لتفاصيل الملف الشخصي.
إضافة صورة للخلفية
أولاً، أردت إضافة صورة خلفية لجلب بعض الألوان إلى التطبيق.
I had a go at wrapping the children of my Scaffold
widget in a Container
widget, which you can then apply a decoration property to. It works and it’s the more upvoted solution, but it doesn’t fill the app bar too, which would be nice.
I ended up using this approach, which utilizes a Stack
widget to lay a full-height background image under our Scaffold
: “Background Image for Scaffold” on Stack Overflow.
يبدو الرمز الناتج هكذا:
@override
Widget build(BuildContext context) {
return Stack(
children: [
Image.asset(
"assets/background.jpg",
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
),
Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
// هنا نأخذ القيمة من كائن MyHomePage الذي تم إنشاؤه بواسطة
// طريقة App.build، ونستخدمها لتعيين عنوان الـ appbar.
title: Text(widget.title),
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
),
body: Center(
...
حسنًا، يبدو ذلك جميلًا نوعًا، لكن الخلفية تبدو مزعجة قليلاً مقارنة بالعناصر القابلة للتحرير على الشاشة:
لذا لففت حقول النص وصورة الملف الشخصي في Card
هكذا، ووضعت بعض الهوامش والتباعد حتى لا تبدو ضيقة:
Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
),
body: Center(
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 30),
child: Padding(
padding: const EdgeInsets.all(30),
child: Column(
...
هذه طريقة واحدة للقيام بذلك، على الرغم من أنني أشك في وجود نهج أكثر أصولية يستخدم نظام تصميم المودرن. ربما لموضوع آخر.
تغيير شعار التطبيق والعنوان في القائمة
إذا كنت ترغب في تغيير شعار تطبيقك، يجب أن تقدم عددًا من المتغيرات لشعارك، كلها بدرجات متفاوتة من القوام لنظامي iOS وAndroid بشكل منفصل. كلاهما لديه متطلبات منفصلة كذلك (بعضها قد تتجاهلها لتجنب رفض تطبيقك)، لذا يصبح هذا بسرعة مهمة مملة.
ولكن لحسن الحظ، هناك حزمة Dart تقوم بكل العمل الشاق. بالنظر إلى صورة المصدر لشعار تطبيقك، يمكنها توليد جميع التباينات المطلوبة لكلا النظامين.
لهذا التطبيق التجريبي، انتهبت شعار تطبيق عشوائي من Google Images:
مصدر: Google Images
اتبعت تعليمات القراءة التي قادتني إلى تحديد هذه المجموعة الأدنى من الإعدادات لتوليد الأيقونات بنجاح. ضع هذا في أسفل ملف pubspec.yaml
:
flutter_icons:
android: true
ios: true
remove_alpha_ios: true
image_path: "assets/app-icon.png"
مع وجود ما وراء ذلك، استخدم هذا الأمر لإنشاء أشكال الأيقون المطلوبة لكل من iOS وAndroid:
flutter pub run flutter_launcher_icons
يجب أن ترى مجموعة من ملفات الأيقونة المولدة للأندرويد و iOS في android/app/src/main/res/mipmap-hdpi/
و ios/Runner/Assets.xcassets/AppIcon.appiconset/
على التوالي.
على الرغم من ذلك، تغيير اسم التطبيق يبدو أنه لا يزال عملية تفاعلية. باستخدام مقال يسمى “كيفية تغيير اسم التطبيق في فلاتر—الطريقة الصحيحة في عام 2023” على Flutter Beads كدليل، غيرت اسم التطبيق في الملفين التاليين لـ iOS و Android على التوالي:
ios/Runner/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Flutter Amplify</string> <--- App Name Here
android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutterapp">
<application
android:label="Flutter Amplify" <--- App Name Here
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
هذا يمنحك أيقونة للتطبيق وعنوان جميلين الآن:
الخاتمة
لذا، هذا يختتم كيفية الحصول على التشغيل مع فلاتر و AWS Amplify، ونأمل أن يوضح كيف يمكن نشر الموارد المساندة وشفرة الهيكل اللازمة لتصنيع تطبيقات الجوال عبر المنصات بسرور.
I’m keen to get feedback on this tutorial, or any follow up tutorials people would like to see. All feedback is welcome and appreciated!
المشاكل المواجهة
إخفاق أدوات سطر الأوامر للأندرويد
موقع مدير بلدي SDK للأندرويد هو:
/Users/benfoster/Library/Android/sdk/tools/bin/sdkmanager
تنفيذ ما يلي قام بتثبيت أدوات سطر الأوامر للأندرويد: Stack Overflow “فشل في تثبيت مجموعة SDK للأندرويد Java Lang Noclassdeffounderror JavaX XML Bind A.”
flutter doctor --android-licenses
التطبيق يبقى مسجل دخول على iOS
عند التطوير، أردت تكرار عملية التسجيل دخول التطبيق. لسوء الحظ بالنسبة لي، كان التطبيق يحتفظ بمعلومات المستخدم بين إغلاق وإعادة فتح التطبيق – إغلاق وإعادة فتح التطبيق يحتفظ بالدخول.
خبرتي السابقة في تطوير Android و Amplify أقنعتني بأن إزالة التطبيق وإعادة تشغيل “flutter run” ستزيل الحالة المستخدمية وتبدأ من جديد. لسوء الحظ، لم يصل هذا حتى إلى التأثير المرغوب، لذا انتهى بي الأمر إلى مسح الهاتف كلما احتجت إلى البدء بلاشطباء:
Source:
https://dzone.com/articles/cross-platform-mobile-app-prototyping-with-flutter-and-aws-amplify