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連結。
本教程分為五個部分:
- 前置條件與代碼庫設置
- 添加認證
- 上傳頭像
- 存儲用戶詳情
- 增添一些設計亮點
推薦
Flutter是一個非常成熟的平台,已經使用多年,擁有活躍的社區和眾多插件及擴展,幾乎能實現大部分功能。
Amplify同樣是一個強大的平台;然而,我發現其API功能難以使用,且Flutter庫並未更新至Amplify最新的公告和特性。特別是在使用AppSync GraphQL和DataStore(用於離線數據存儲和同步)時,體驗相當脆弱(稍後您將看到)。
將兩者結合,對於加速移動應用原型的開發來說是一個很好的組合,但當您感覺像是在強迫Amplify適應您的需求時,不要猶豫,直接使用AWS服務,放棄Amplify的抽象。
我所建立的示範應用程式保存了使用者個人資料——這是許多應用程式的常見需求。您可以建立帳戶並登入、上傳個人頭像,並提交一些個人資料。我們將深入探討全端開發——從使用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”(我的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應用,相當酷。
如果你看到以下內容:那麼恭喜,你已成功生成框架。
你也可以在XCode中打開ios/Runner.xcodeproj
項目,探索其內容,並在模擬器和物理設備上運行,就像處理任何其他XCode項目一樣。
Android設置
Android稍微複雜一些,因為你必須在Android Studio中明確配置模擬器才能運行它。首先在Android Studio中打開android/flutterapp_android.iml
項目,然後你可以配置並運行一個模擬器來測試應用。
給Android Studio幾分鐘時間下載Gradle及運行應用所需的所有依賴項——您可以在窗口右下角的進度條中追蹤此過程。
當Android Studio穩定後,如果您已在AVD中配置了模擬設備,則應該能夠點擊窗口右上角的播放按鈕:
看哪,同樣的應用在Android上:
這演示了創建新Flutter項目時提供的示例應用代碼。在本教程過程中,我們將逐步用我們自己的代碼替換這些代碼。
這是進行git提交的好時機,現在我們已經為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會添加一個名為lib/amplifyconfiguration.dart
的dart文件。此文件被git忽略,因為它包含與您部署的資源相關的敏感憑證,並且會自動與您正在工作的Amplify環境同步。您可以在此處了解更多信息這裡。
至此,我們已經設置了Amplify,創建了應用程序和開發環境,並配置了Cognito進行身份驗證。如果你正在跟隨教程操作,現在是個好時機進行git提交,以便在需要時能夠回退到這一點。Amplify應該已經為你創建了一個.gitignore
文件,排除了所有不必要的文件。
既然我們已經建立了後端身份驗證基礎設施,我們可以開始使用Flutter構建我們的移動應用程序。
在我們的應用中驗證用戶
I’m following the steps outlined in the authentication for the AWS Amplify tutorial here.
這是使用amplify_flutter
內置的身份驗證UI屏幕和工作流程。通過在pubspec.yaml
文件中的“dependencies”下添加以下內容來添加Amplify Flutter依賴項:
amplify_flutter: ^0.6.0
amplify_auth_cognito: ^0.6.0
如果你沒有使用VSCode中的Flutter和Dart擴展(或使用VSCode),你需要接著運行flutter pub get
命令。如果你正在使用,那麼當你保存pubspec.yaml
文件時,VSCode將自動運行此命令。
有一種快速入門的方法來集成身份驗證,它使用了一個預製的Authenticator 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,然後調用
// "熱重載"(在您運行 "flutter run" 的控制台中按 "r",
// 或者只需將您的更改保存到 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');
}
}
}
什麼是 Widget?
它是 Flutter 中用於組合 UI 佈局和元件的基本構建塊。Flutter 佈局中的幾乎所有東西都是 Widget——欄、腳手架、填充和樣式、複雜元件等。Flutter 入門文檔中的範例使用了一個 “Center” Widget 跟隨一個 “Text” Widget 來顯示一個居中的文字,上面寫著 “Hello World.”
上述程式碼將一個驗證器 Widget 裝飾在 MyHomePage
上,添加了 AmplifyAuthCognito
插件,並採用了之前 amplify add auth
命令在 lib/amplifyconfiguration.dart
中生成的配置,自動連接到您的 AWS Cognito 使用者池。
在執行Flutter後,為了示範身份驗證整合,我在“執行pod安裝”這一步驟上花費了一些時間(將近5分鐘)。只需耐心等待即可。
一旦完成了身份驗證的修改並啟動應用,您將看到一個基本但功能齊全的登錄界面。
通過使用“創建帳戶”流程,您可以提供您的電話號碼和密碼,接著會出現一個MFA挑戰以完成註冊。您可以查看該用戶是否已在Cognito用戶池中創建:
您也可以在虛擬Android設備上輕鬆測試這一點。如果您已安裝Flutter和Dart插件,甚至不需要離開VSCode,因此無需打開Android Studio。只需在VSCode右下角選擇當前活動設備(iPhone)的名稱,切換到您已創建的虛擬Android設備,然後按“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: 指定您獨有的應用程式ID(參考:https://developer.android.com/studio/build/application-id.html)。
applicationId "com.example.flutterapp"
// 您可以更新以下值以符合您的應用需求。
// 更多資訊請見:https://docs.flutter.dev/deployment/android#檢閱建置配置。
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
雖然Flutter作為最低需求,需要使用API 16(在flutter.gradle
中宣告),但Amplify Auth庫至少需要API 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 Design 系統緊密整合,該系統在移動應用開發中廣泛使用,以提供一致的外觀和感覺。它提供了一套跨平台兼容的組件,其樣式可以被覆蓋,以建立符合您品牌特定體驗的介面。
Flutter 的入門模板已經使用 MaterialApp 小工具搭建了一些小工具。我們之前用驗證器小工具裝飾了這個。現在,我們將擴展 MaterialApp 的 MyHomePage 子小工具,提供一個頭像圖片。
您將小工具組合在一起形成一個樹狀結構,稱為“小工具層次結構”。您總是從頂層小工具開始。在我們的應用中,是處理初始登錄的驗證器包裝小工具。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,您必須在Xcode
配置文件<project root>/ios/Runner/Info.plist
中添加NSPhotoLibraryUsageDescription
鍵,以請求訪問用戶的照片;否則,應用程序將崩潰。
我們將這個功能連接到一個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中傳遞給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 repo中查看完成的組件,該repo包含了上述所有邏輯。接著,您可以整理_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中使用全局次要索引來避免表掃描。如果你想了解我是如何到達這裡以及各種陷阱是什麼,請查看“與Flutter配合使用和調整AWS Amplify和Appsync的挑戰”。
Amplify試圖默認回答創建API時提出的問題,但你可以通過向上滾動到你想更改的選項來覆蓋任何這些。在這種情況下,我們需要一個GraphQL端點(以便利用DataStore),並且API授權由Cognito用戶池處理,因為我們希望應用細粒度訪問控制,以便只有用戶可以更新或刪除自己的詳細信息(但其他用戶可以查看它們)。
當我們創建API時,Amplify會創建一個基本的ToDo GraphQL模式類型。我們將更新此內容並添加一些授權規則,然後再推送API更改。
修改“待辦事項”模板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 API——他們需要使用應用程式,並在我們的Cognito身份池中使用“未經身份驗證”角色(即已登出)來查看用戶詳細信息。
“擁有者”規則允許創建個人資料的用戶創建、讀取和更新自己的個人資料。在這個例子中,我們不允許他們刪除自己的個人資料。
至此,我們可以配置支持API功能的雲基礎設施:
amplify push
當您將現有的GraphQL模型從待辦事項更改為用戶個人資料時,如果您之前已執行amplify push
並配置了基礎設施,您可能會收到一個錯誤,指出所請求的更改將需要銷毀現有的DynamoDB表。Amplify防止您這樣做,以防因刪除現有的待辦事項表而丟失數據。如果遇到此錯誤,您需要運行amplify push --allow-destructive-graphql-schema-updates
。
當您執行amplify push
時,Amplify和CloudFormation將建立一個AppSync GraphQL API、中介解析器和一個類似於此的後端DynamoDB表:

一旦我們定義了 GraphQL 架構,就可以使用 Amplify 來生成代表模型和倉庫層的 Dart 代碼,這些代碼能夠與 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 連接,以便當用戶點擊“保存”浮動操作按鈕時,更改會與 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 方法創建的,並用它來設置我們的 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 圖片中隨便抓取了一個應用圖標:
來源:Google 圖片
遵循自述文件,我定義了這個最小配置集以成功生成圖標。將此放置在你的 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 Beads上的一篇文章“如何在2023年正確更改Flutter應用名稱”,我在以下兩個文件中分別更改了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命令行工具
我的Android SDK管理器位置是:
/Users/benfoster/Library/Android/sdk/tools/bin/sdkmanager
按照以下步驟安裝了Android命令行工具:Stack Overflow上的“安裝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