פרוטופינג יישומים ניידים במכלול פלאטר ו-AWS Amplify



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.

אם תעקוב אחרי הדרכה זו, אין סיכוי שייקח לך יותר משעה. עם זאת, זה לקח לי מספר שעות במאבק עם בעיות שונות, אך אני מקווה שהקלטתי אותן טוב מספיק כדי שלא תיתקל בהן.

הנה המוצר הסופי. אם אתה רוצה את "הנה גרסה אחת שעשיתי מראש", עקוב אחרי השלבים בקובץ הreadme, ואז צריך להפעיל אותו תוך כחמש עשרה דקות. הנה הקישור ל-GitHub

הדרכה זו מורכבת מחמישה חלקים:

  1. הכרחיות והקמת מאגר הקוד
  2. הוספת אימות
  3. העלאת תמונת פרופיל
  4. אחסון פרטי המשתמש
  5. הוספת מרקם עיצובי

המלצה

Flutter הוא פלטפורמה בשלה מאוד שנמצאת בשימוש כבר מספר שנים, עם קהילה ממוקדת ורבים פלוגים ותוספות להשיג רוב הדברים.

Amplify, גם, היא פלטפורמה חזקה; עם זאת, מצאתי את האפשרות לפונקציית ה-API קשה לעבוד איתה והספריות של Flutter לא היו מעודכנות עם ההודעות האחרונות והתכונות ב-Amplify. במיוחד, עבודה עם AppSync GraphQL ו-DataStore (לאחסון מידע לא מקוון וסנכרון) היו די שבריריים (כפי שתראה מאוחר יותר).

בשילוב יחדיו, שני אלה הם צירוף מצוין לקידום הפיתוח של אבות טכנולוגיים ליישומים ניידים, אך כשאתה מרגיש שאתה מכופף את Amplify לרצונך, אל תהסס להשאיר אותו כדי לעבוד ישירות עם השירותים של AWS שהוא מפשט.

האפליקציה הדגמתית שבניתי מחזיקה מידע פרופיל משתמש—דרישה נפוצה של רבות מהאפליקציות. אפשר ליצור חשבון ולהתחבר, להעלות תמונת פרופיל ולהגיש פרטים מסוימים על עצמך. ניכנס לפרטים על כל המסעדה—עבודה עם Flutter ו-Dart לקוד האפליקציה עד לכמויות DynamoDB כדי לתת לך רוחב מלא של מה שאתה צריך לדעת.

חלק ראשון: קדם תנאים והקמת מאגר הקוד

מדריך זה מניח שיש לך את הדברים הבאים כבר מוכנים על המחשב שלך:

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

Flutter ו-Amplify יש כלי מרשמים שיוצרים את מבנה הפרויקט הראשוני שלך. חשוב לעשות את זה בסדר מסוים; אחרת, מבנה התיקיות שלך לא יתאים למה שהכלים מצפים, מה שיגרום לך כאב ראש לתקן מאוחר יותר.

הקפד ליצור את מבנה מאגר הקוד שלך באמצעות Flutter תחילה, ואז לאתחל את Amplify בתוכו.

I used the official Flutter getting started documentation to kick things off for my demo.

בואו נראה אם אפשר להשיג שפע Flutter. תחילה, כדי לבדוק אם זה נמצא בצורה נכונה ונוסף ל-PATH שלך, אפשר לרוץ flutter doctor.

אם זו הפעם הראשונה שלך בפיתוח מובייל, יהיו כמה דברים שצריך לטפל בהם כאן. עבורי זה היה:

  • התקנת Android Studio (ואבטחת השפע של Android SDK CLI).
  • התקנת XCode ו-CocoaPods.
  • הסכמת תנאי השימוש לכלים של Android ו-iOS.

יצירת מאגר קוד האפליקציה

כשיש לך את כל הקדם תנאים מוכנים, אפשר ליצור את המרשמים של Flutter. עושים זאת יוצרים את התיקייה שבה נעבוד, אז הפעל את הפקודה הזו מתיקיית ההורה:

 
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" (שם המאגר הגיטארי שלי).

בשלב זה, Flutter יצר לנו שבעים ושלושה קבצים. בואו נבחן מהם הם:


התיקיות שנבלה בהן רוב הזמן הן ios/android ו-lib/. בתוך התיקיות ios ו-android יש משאבי פרויקט שניתן לפתוח באקסדה ובאנדרואיד סטודיו בהתאמה. פרויקטים אלו משמשים כאינטראופ בין הקוד הדארט עצמאי לפלטפורמה לפלטפורמות היעד שלך, ותוכל להשתמש בהם לבדוק את האפליקציה שלך נגד הפלטפורמות המתאימות. בואו ננסה את זה עם iOS עכשיו:

הגדרת iOS

 

open -a Simulator
flutter run

על המק שלי, עם הגדרת אקסדה מינימלית, זה עבר משום דבר ישירות להפעלת סימולטור iPhone 14 Pro Max עם האפליקציה המרוסן של Flutter פועלת, שזה די מגניב. 


אם אתה רואה את הבאות: אזי מזל טוב, הצלחת לייצר את המבנה המרוסן.


אתה יכול גם לפתוח את הפרויקט ios/Runner.xcodeproj בתוך אקסדה, לחקור את תוכנו ולרוץ נגד סימולטורים ומכשירים פיזיים כמו שהיית עושה כל פרויקט אקסדה אחר.

הגדרת אנדרואיד

אנדרואיד הוא קצת פחות פשוט, שכן אתה חייב להגדיר באופן מפורש אמון באנדרואיד סטודיו לפני שתוכל להפעיל אותו. פתח את הפרויקט android/flutterapp_android.iml בתוך אנדרואיד סטודיו כדי להתחיל, ואז תוכל להגדיר ולהפעיל אמון לבדוק את האפליקציה.


תן ל-Android Studio כמה דקות כדי שיהיה מסוגל להוריד את Gradle ואת כל ההסמכויות הנדרשות לריצת האפליקציה – אפשר לעקוב אחר ההתקדמות בסרטוט התהליך בפינה הימנית התחתונה.

כשAndroid Studio יתמזג, אם כבר תכננתם מכשיר מדומה ב-AVD, אפשר יהיה ללחוץ על כפתור ההפעלה בפינה הימנית העליונה של החלון:


והנה, אותה אפליקציה ב-Android:


זה מדגים את הקוד של הדוגמא שמשודרג כשיוצרים פרויקט חדש ב-Flutter. במהלך הדרכה זו, נחליף בהדרגה את הקוד הזה בקוד שלנו.

זו נקודה טובה לבצע מאמץ גיט כעת, כשהקמנו את היסודות לפיתוח Flutter. אנו כעת בנקודה שבה נוכל להתחיל לשחק עם קוד Flutter ולראות את התוצאות שלנו ב-iOS ו-Android בו זמנית.

Flutter משתמש ב-Dart כשפת ביניים בין Android ל-iOS, וכל הקוד שאתה תהיה מחויב בו מתגורר בתיקיית lib/. צריך להיות קובץ main.dart, שם נתחיל לשחק.

הגדר ופרוס אפליקציה חדשה באמצעות Amplify

עכשיו, כשהכלי הנייד של האפליקציה מוכן לעבוד איתו, אנו זקוקים למיקום מבוסס מאחורי לתמיכה בפונקציונליות של האפליקציה.

נשתמש ב-AWS ובשירותים הרבים שלו לתמיכה באפליקציה שלנו, אך כולו ינוהל באמצעות שירות AWS Amplify. רובו יטופל באופן שקט עבורנו, ובמקום לדאוג לגבי אילו שירותים להשתמש, נתמקד במה שאנו רוצים לפרוס.

כדי להתחיל, בתיקיית הקוד שלך רוץ:

 
amplify init

פקודה זו מאתחלת את AWS Amplify בתוך הפרויקט שלך. אם לא נצלת ממנו קודם לכן, זה ישאל ממך מספר שאלות. לאנשים שמשתפים פעולה לאחר מכן בפרויקט ורוצים להריץ את הפקודה הזו, זה מכוון את הסביבה המקומית שלהם עם הציוד האמון מוגדר כבר במקום.



זה יסדר כמה משאבי AWS הראשוניים לאחסון הציוד האמון של היישום שלך והמצב, במיוחד חבילת S3.

הכיוון מעל ההתקדמות והמצב עשוי להיראות מוכר לכמה – זה CloudFormation, ובדיוק כמו AWS CDK, Amplify משתמש ב- CFN מאחורי הקלעים לספק את כל המשאבים הנדרשים. אפשר לפתוח את ממשק CloudFormation במחשב כדי לראות את זה בפעולה:


לבסוף, כשה- CLI שלך מוכן, כדאי לראות אישור דומה לנוכחי, ותוכל לראות את היישום החדש שלך שמופק בממשק האמון:



ניהול סביבות

ל- 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 App שלך (והסביבה המקומית) לפרוס את התכונות הללו.

כדי לפרוס תכונות (או כל שינויי תצורה) אתה צריך לבצע דחיפה:

 
amplify push

הערה: זה שונה מפקודת amplify publish, שבונה ומפרסם שירותים רקע וקדם. דחיפה מספקת רק משאבי רקע (וזה כל מה שנצטרך בדרך כלל במדריך זה כשנבנה אפליקציות ניידות).

כשאתה מוסיף אימות (או כל תכונת Amplify אחרת), Amplify מוסיף קובץ דרך הנקרא lib/amplifyconfiguration.dart. זה גיט נתרם כיוון שהוא מכיל רכיבים רגישים הקשורים למשאבים המופרשים שלך ומסונכרן אוטומטית עם הסביבת 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. הוסף את הסמיכות של Amplify Flutter על ידי הוספת התוכן הבא תחת "dependencies" בקובץ pubspec.yaml:

 

amplify_flutter: ^0.6.0
amplify_auth_cognito: ^0.6.0

אם אינך משתמש בסמיכות Flutter ו-Dart בתוך VSCode (או משתמש ב-VSCode) תצטרך לעקוב אחרי זה עם פקודת flutter pub get. אם אתה כן, אז VSCode יפעל אוטומטית כשאתה שומר את הקובץ pubspec.yaml.

יש גישה מהירה לשילוב אימות שמשתמשת בספריית UI מוכנה לאימות, מצוינת להתחלת זרימת התחברות שניתן להתאים מאוחר יותר. נשתמש בזה בדרכים המדריכות להדגמת הסט המפותח של ספריות Amplify ואיך ניתן לשלב אותן בקלות באפליקציה שלך.

שלבים לשילוב ספריית אימות OOTB הם כאן.

ניתן להעביר את האוטנטיקטור המקשט את הווידג' המוגדר בקוד הדוגמא מעל הקוד המסופק בדוגמא המהירה של 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 IDE).
          // שים לב שהמונה לא התאפס חזרה לאפס; היישום
          // לא מתחיל מחדש.
          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 משתמשת בווידג 'של "מרכז" המלווה בווידג' של "טקסט" כדי להציג פיסת טקסט מרכזית האומרת "שלום עולם".

הקוד הנ"ל מקשט את ווידג' MyHomePage עם ווידג' אוטנטיקטור, מוסיף את התוספת AmplifyAuthCognito, ולוקח את ההתאמה שהפקודה הקודמת amplify add auth יצרה בlib/amplifyconfiguration.dart כדי לחבר אוטומטית לבריכת המשתמשים AWS Cognito שלך.

לאחר שהפעלתי את Flutter והתחלתי להדגים את שילוב האימות, לקח לי זמן מה עד שהשלמתי את שלב "Running pod install" (כמעט 5 דקות). פשוט היה צריך להיות סבלני.


לאחר שביצעתם את שינויי האימות והתחלתם את האפליקציה, מופיעה מסך התחברות בסיסי אך פונקציונלי.



באמצעות זרימת העבודה "Create Account" ניתן לספק את מספר הטלפון וסיסמה, ואז מוצגת תגובת MFA כדי להשלים את ההרשמה. לאחר מכן ניתן לראות שנוצר המשתמש בבריכת משתמשים של Cognito:


ניתן לבדוק זאת בקלות גם על מכשיר אנדרואיד וירטואלי. אין צורך אפילו לצאת מ-VSCode אם התקינתם את התוספיות של Flutter ו-Dart, ולכן אין צורך לפתוח את Android Studio. פשוט בחרו את שם המכשיר הפעיל הנוכחי (iPhone) בפינה הימנית התחתונה של VSCode, החליפו למכשיר אנדרואיד וירטואלי שכבר נוצר, ולאחר מכן הקישו "F5" כדי להתחיל את הבדיקות. החוויה דומה למדי ל-iOS:



כשבניתי לראשונה לאחר שיישמתי את ספריית האימות, נתקלתי ביוצא מן הכלל הבא כשניסיתי לבנות את האפליקציה:

 
uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [:amplify_auth_cognito_android]

Flutter מאוד עוזר במצב זה, שכן מיד לאחר שמוצגת המעבה הזו מוצעת המלצה:


נראה שפיתוח ה-Flutter SDK כבר מנפה זאת בקובץ 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

ואמפליפיי יסדיר את השירותים הרקיעיים הנדרשים לאפליקציה שלך לשימוש באחסון בענן. אמפליפי משלב בקלות את 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 משולב קרוב למערכת עיצוב החומר, שמשמשת באופן נרחב בפיתוח יישומי סלולר כדי לספק מראה ותחושה עקביים. הוא מספק סט של רכיבים מתאימים לרשתות רבות, שסגנונם יכול להתבטל כדי לבנות חוויה מיוחדת למותג שלך.

תבנית ההתחלה של Flutter כבר מרכיבה כמה וידג'טים באמצעות וידג'ט MaterialApp. קודם צבענו את זה עם וידג'ט האוטנטיקטור. עכשיו, נרחיב את וידג'ט הילד MyHomePage של MaterialApp כדי לספק תמונת פרופיל.

אתה מרכיב וידג'טים יחד בעץ, המכונה "היררכיה של הווידג'טים"Widget Hierarchy". אתה תמיד מתחיל עם וידג'ט ראשי. באפליקציה שלנו הוא וידג'ט האימות האוטנטיקטור שמטפל בחתירת הכניסה הראשונה. Scaffold הוא וידג'ט טוב לבסיס הפרוסות שלך: זה נפוץ כמו וידג'ט ראשי עם אפליקציות חומר; ויש לזה מספר מיקומים מובנים, כמו כפתור פעילות צפית, דף תחתון (לגרירה כדי להוסיף פרטים נוספים), בר אפליקציה וכו '.

ראשית, בואו פשוט נוסיף וידג'ט תמונה שמצביע על כתובת רשת. נחליף את זה בהמשך באחד שאנו לוקחים ומעלים ל-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 לקובץ התצורה <שורש-הפרויקט>/ios/Runner/Info.plist של Xcode כדי לבקש שיתוף גישה לתמונות של המשתמש; אחרת, האפליקציה תקרוס.

נחבר את זה לפריט 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(), עדכון שדות הפריטים בתוך ה-Lambda שנותן לפלטר, כך שהוא ידע לקרוא את הפונקציה build(), שם המצב המעודכן יכול לשמש כדי לשכפל את הפריט. במקרה שלנו, תמונת הפרופיל תתמלא, אז ניצור פריט קונטיינר שמציג את התמונה. הפעולה ?? ללא דאגה ל-null מספקת תמונת מלכוד ברירת מחדל לפני שהמשתמש בחר תמונה.

כדאי גם להוסיף תמונת מלכוד פרופיל למאגר שלך ולהתייחס אליה בקובץ 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;
      });
    }
  }

חלק רביעי: אחסון פרטי המשתמש (נקוב)

מצוין, עכשיו יש לנו מודל של יישום נייד שמכיל משתמשים, אימות ותמונות פרופיל. בשלב הבא, בואו נראה אם נוכל ליצור API שמשתמש בפרטי האישוש של המשתמש כדי להשיג מידע נוסף עליהם.

בדרך כלל, הייתי אומר, "אתה רוצה API? פשוט:"

 
amplify add api

כאן מרבה המאמץ והפתרון של הבעיות כי, תלוי בהתאמה שתבחר, זה לא מתמוך לחלוטין בסביבת Amplify ו-Flutter. שימוש במסד הנתונים ובמודל המוכן לשימוש מלאכת החוץ יכול גם לגרום לתבניות קריאה לא יעילות, שיכולות להפוך במהירות ליקרות ולאיטיות מאוד.

Amplify מספק API ברמה גבוהה לאינטראקציה עם הנתונים ב-AppSync, אך במדריך זה אני אשתמש ב-GraphQL עם שאילתות ברמה נמוכה כפי שזה מספק יותר גמישות ומאפשר שימוש באינדקס משני גלובלי ב-DynamoDB כדי להימנע מסריקות טבלה. אם אתה רוצה להבין איך הגעתי לכאן ומהן המלכודות השונות, תסתכל על "אתגרים בעבודה עם ובכוונון AWS Amplify ו-Appsync עם Flutter."


Amplify מנסה לכתוב ברירת מחדל את השאלות שנשאלו כשיוצרים API, אבל אפשר לעקוף כל אחת מהן על ידי לחיצה למעלה על האפשרות שברצונך לשנות. בתרחיש זה, אנו רוצים שפת שיחה GraphQL (כדי למנוע DataStore), והאימות ה-API להתבצע על ידי בריכת המשתמשים של Cognito, כפי שאנו רוצים ליישם שליטה מדויקת על הגישה כך שרק המשתמש יכול לעדכן או למחוק את הפרטים שלהם (אבל משתמשים אחרים יכולים לצפות בהם).

כשאנו יוצרים את ה-API, Amplify יוצר סקימה בסיסית של סוג שאילתת GraphQL ל-ToDo. אנו נעדכן בכך ונוסיף כמה כללי אימות לפני שנדחוף את שינויי ה-API.

לשנות תבנית "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 מונע ממשתמשים לגשת ישירות ל-API של GraphQL – הם צריכים להשתמש באפליקציה ולהשתמש בתפקיד "לא מחובר" בבריכת הזהות של Cognito (כלומר, מחוץ) כדי לצפות בפרטים של המשתמש.

הכלל "בעל" מאפשר למשתמש שיצר את הפרופיל ליצור, לקרוא ולעדכן את הפרופיל שלהם. בדוגמה זו, אנו לא מאפשרים להם למחוק את הפרופיל שלהם.

בשלב זה, אנו יכולים לפרוס את המבנה הענן שלנו התומך בתכונת ה-API:

 
amplify push

כשאתה משנה את המודל הקיים של GraphQL מ-ToDo ל-UserProfile, אם בעבר ביצעת amplify push ופרוסת את המבנה, ייתכן שתקבל שגיאה המציינת שהשינוי המבוקש ידרוש את ההשמדת השולחן הנוכחי של DynamoDB. Amplify מונע ממך לעשות זאת במקרה של איבוד נתונים מהשמדת השולחן הקיים של ToDo. אם אתה מקבל שגיאה זו עליך לרוץ amplify push --allow-destructive-graphql-schema-updates.

כשאתה עושה amplify push, Amplify ו-CloudFormation יקימו שירות AppSync GraphQL API, פונקציות פירוק ביניים ושולחן DynamoDB המשתלב ככה:



לאחר שהגדרנו תוכניות גרפיקאל, אנו יכולים להשתמש ב-Amplify כדי לייצר את הקוד הדרטי המייצג את השכבה המודל והמאגר שיכולה לעבוד עם ה-API:

 
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,
                        )
                      ]

לאחר מכן לחבר את TextFields שלנו ל-AppSync GraphQL API כך שכאשר המשתמש לוחץ על כפתור הפעולה הצפה "Save" (שמור), השינויים מתחברים עם 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 ?? "";
      });
    }
  }

עכשיו יש לנו API שבו אנו יכולים לאחסן נתונים, מאובטח עם Cognito ובחזית על DynamoDB. די מגניב לחשוב שלא הייתי צריך לכתוב שום קוד מבנה כשירות.

אז בשלב זה, יש לנו דרך לשאילתא, וויזשאל ועדכון פרטי פרופיל המשתמש. מרגיש כמו נקודת שמור נוספת עבורי.

חלק חמישי: הוספת קצת מרה עיצובית

לבסוף, האפליקציה הדוגמא שהרחבנו נראית קצת פשוטה. הגיע הזמן להחיות אותה קצת.

עכשיו, אני לא מומחה UI, אז לקחתי קצת השראה מ-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, ומשתמשים בו כדי להגדיר את כותרת האפ שלנו. 
            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

אתה צריך לראות מספר קבצי סמל שנוצרו עבור Android ו-iOS בandroid/app/src/main/res/mipmap-hdpi/ ובios/Runner/Assets.xcassets/AppIcon.appiconset/ בהתאמה.

למרבה הצער, שינוי שמה של האפליקציה נמצא עדיין בתהליך האוטומטי. באמצעות מאמר בשם “איך לשנות את שמה של האפליקציה ב-Flutter—הדרך הנכונה ב-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">

זה נותן לך סמל אפליקציה קטן וכותרת עכשיו:


כריכת העניינים

אז זה מסכם איך לקום ולרוץ עם Flutter ו-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!

בעיות שנתקלתי בהן

חסרונות בכלי שורת הפקודה של Android

המיקום של מנהל ה-SDK של Android שלי הוא:

/Users/benfoster/Library/Android/sdk/tools/bin/sdkmanager

ביצוע הפעולות הבאות התקין את כלי שורת הפקודה של Android: Stack Overflow “Failed to Install Android 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