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.
Wenn du diesem Tutorial folgst, sollte es dich nicht mehr als eine Stunde in Anspruch nehmen. Nun, es hat mich mehrere Stunden gekostet, da ich mit verschiedenen Problemen zu kämpfen hatte, aber hoffentlich habe ich sie gut genug dokumentiert, sodass du sie nicht treffen solltest.
Hier ist das fertige Produkt. Wenn du die „hier ist eine, die ich zuvor gemacht habe“-Version haben möchtest, folge den Schritten im Readme, du solltest es in etwa fünfzehn Minuten betriebsbereit haben. Hier ist der GitHub-Link.
Dieses Tutorial besteht aus fünf Teilen:
- Voraussetzungen und Einrichtung des Codebases
- Hinzufügen von Authentifizierung
- Hochladen eines Profilbildes
- Speichern von Benutzerdetails
- Hinzufügen von Designaufwand
Empfehlung
Flutter ist eine sehr ausgereifte Plattform, die bereits seit mehreren Jahren verwendet wird, mit einer blühenden Community und vielen Plugins und Erweiterungen, um die meisten Dinge zu erreichen.
Amplify ist ebenfalls eine starke Plattform; allerdings fand ich die API-Funktionalität schwierig zu handhaben und die Flutter-Bibliotheken waren nicht auf dem neuesten Stand mit den jüngsten Ankündigungen und Funktionen in Amplify. Insbesondere die Arbeit mit AppSync GraphQL und DataStore (für den Offline-Datenspeicher und die Synchronisation) waren ziemlich brüchig (wie du später sehen wirst).
In Kombination sind diese beiden eine großartige Kombination, um die Entwicklung von Mobilprototypen zu beschleunigen, aber wenn du das Gefühl hast, du musst Amplify zu deinem Willen biegen, fürchte dich nicht, es zugunsten der direkten Arbeit mit den AWS-Diensten, die es abstrahiert, fallen zu lassen.
Die Demo-App, die ich erstellt habe, speichert Benutzerprofilinformationen – ein gängiges Anforderungsmerkmal vieler Apps. Sie können ein Konto erstellen und sich anmelden, ein Profilbild hochladen und einige Details über sich selbst eingeben. Wir werden im Detail auf das Full-Stack eingehen – mit Flutter und Dart für die App-Code bis hin zu Technologien wie DynamoDB, um Ihnen einen umfassenden Überblick darüber zu geben, was Sie wissen müssen.
Teil Eins: Voraussetzungen und Einrichtung des Codebases
Dieses Tutorial geht davon aus, dass Sie die folgenden Voraussetzungen bereits auf Ihrem Computer eingerichtet haben:
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 und Amplify verfügen über Skelettierungswerkzeuge, die Ihre initiale Projektstruktur erstellen. Es ist wichtig, diese in einer bestimmten Reihenfolge durchzuführen; andernfalls stimmt Ihre Ordnerstruktur nicht mit dem überein, was die Werkzeuge erwarten, was Sie später zu korrigieren ein Kopfzerbrechen verursachen wird.
Stellen Sie sicher, dass Sie Ihre Codebasisstruktur zuerst mit Flutter erstellen und dann Amplify darin initialisieren.
I used the official Flutter getting started documentation to kick things off for my demo.
Versuchen wir, ob wir Flutter zum Laufen bringen können. Zunächst können Sie überprüfen, ob es korrekt installiert ist und in Ihrem Pfad hinzugefügt wurde, indem Sie flutter doctor
ausführen.
Wenn dies Ihr erster Einstieg in die Mobile Entwicklung ist, müssen hier einige Dinge geklärt werden. Für mich waren das:
- Installieren von Android Studio (und Android SDK CLI).
- Installieren von XCode und CocoaPods.
- Einwilligung in die Geschäftsbedingungen für Android und iOS-Werkzeuge.
Erstellen Ihrer App-Codebasis
Wenn Sie alle Voraussetzungen erfüllt haben, können Sie das Flutter-Skelett erstellen. Dies erstellt den Ordner, in dem wir arbeiten werden, also führen Sie diesen Befehl aus einem übergeordneten Verzeichnis aus:
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).
Du könntest den obersten Ordner, der an diesem Punkt erstellt wurde, umbenennen, falls du nicht möchtest, dass er dem Namen deiner App entspricht. Ich habe ihn von „flutterapp“ in „flutter-amplify-tutorial“ (mein Git-Repository-Name) geändert.
An diesem Punkt hat Flutter uns siebenundsiebzig Dateien erstellt. Schauen wir uns an, was diese sind:
Die Ordner, mit denen wir die meiste Zeit verbringen werden, sind ios/android
und lib/
. Innerhalb der ios
und android
Ordner befinden sich Projektressourcen, die mit XCode und Android Studio entsprechend geöffnet werden können. Diese Projekte fungieren als Interop zwischen der plattformunabhängigen Dart-Code und deinen Zielplattformen, und du kannst sie verwenden, um deine App gegen die jeweiligen Plattformen zu testen. Versuchen wir das jetzt mit iOS:
iOS-Setup
open -a Simulator
flutter run
Auf meinem Mac, mit minimalem XCode-Setup, ging dies von nichts direkt bis hin zum Betrieb eines iPhone 14 Pro Max-Simulators mit der aufgesetzten Flutter-App, was ziemlich cool ist.
Wenn du das unten siehst: dann herzlichen Glückwunsch, du hast es geschafft, die Gerüstbau zu erzeugen.
Du kannst auch das ios/Runner.xcodeproj
Projekt innerhalb von XCode öffnen, sein Inhalt erkunden und gegen Simulatoren und physische Geräte laufen lassen, wie du es bei jedem anderen XCode-Projekt tun würdest.
Android-Setup
Android ist etwas weniger einfach, da du ein Emulator in Android Studio explizit konfigurieren musst, bevor du ihn ausführen kannst. Öffne das android/flutterapp_android.iml
Projekt innerhalb von Android Studio, um damit zu beginnen, und dann kannst du einen Emulator konfigurieren und ausführen, um die App zu testen.
Gib Android Studio einige Minuten Zeit, um Gradle und alle Abhängigkeiten herunterzuladen, die zum Ausführen der App benötigt werden – du kannst den Fortschritt in der unteren rechten Ecke im Fortschrittsbalken verfolgen.
Sobald Android Studio eingespielt ist und du bereits ein simuliertes Gerät in AVD konfiguriert hast, solltest du in der Lage sein, auf die Play-Taste in der oberen rechten Ecke des Fensters zu drücken:
Und siehe da, die gleiche App auf Android:
Dies demonstriert den Beispiel-App-Code, der beim Erstellen eines neuen Flutter-Projekts bereitgestellt wird. Im Laufe dieses Tutorials werden wir diesen Code Schritt für Schritt durch unseren eigenen ersetzen.
Dies ist ein guter Zeitpunkt für einen git Commit, da wir nun die Grundlagen für die Flutter-Entwicklung eingerichtet haben. Wir sind nun an einem Punkt, an dem wir mit dem Flutter-Code herumspielen und unsere Ergebnisse gleichzeitig auf iOS und Android sehen können.
Flutter verwendet Dart als Zwischensprache zwischen Android und iOS, und all der Code, mit dem du interagieren wirst, befindet sich im Verzeichnis lib/
. Es sollte eine Datei namens main.dart
geben, in der wir anfangen werden, herumzuspielen.
Konfigurieren und Bereitstellen einer neuen App mit Amplify
Nun, da wir die mobile App-Tooling bereit haben, benötigen wir eine Backend-Infrastruktur zur Unterstützung der App-Funktionalität.
Wir werden AWS und seine zahlreichen Dienste verwenden, um unsere App zu unterstützen, aber alles davon wird mithilfe des AWS Amplify-Dienstes verwaltet. Der Großteil davon wird für uns transparent behandelt, und anstatt uns darum zu kümmern, welche Dienste zu verwenden sind, werden wir uns auf die Funktionen konzentrieren, die wir bereitstellen möchten.
Zu Beginn führe im Code-Ordner Folgendes aus:
amplify init
Dieser Befehl initialisiert AWS Amplify in Ihrem Projekt. Wenn Sie es noch nie verwendet haben, wird Ihnen eine Reihe von Fragen gestellt. Für nachfolgende Personen, die am Projekt zusammenarbeiten, richtet dieser Befehl ihre lokale Umgebung mit der bereits vorhandenen Amplify-Konfiguration ein.
Dies wird einige anfängliche AWS-Ressourcen bereitstellen, um die Konfiguration und den Status Ihres Amplify-Apps zu speichern, nämlich einen S3-Bucket.
Der obige Bereitstellungs-Fortschrittsbalken und der Status könnten für einige vertraut aussehen – es ist CloudFormation, und genau wie bei AWS CDK verwendet Amplify CFN hinter den Kulissen, um alle erforderlichen Ressourcen bereitzustellen. Sie können die CloudFormation-Stacks-Konsole öffnen, um es in Aktion zu sehen:
Schließlich, wenn die CLI abgeschlossen ist, sollten Sie eine Bestätigung ähnlich der folgenden sehen, und Sie können Ihre neu bereitgestellte App im Amplify-Konsolenbereich sehen:
Umweltmanagement
AWS Amplify verfügt über die Vorstellung von „Umgebungen“, die isolierte Bereitstellungen Ihrer Anwendung und Ressourcen sind. Historisch gesehen musste die Vorstellung von Umgebungen innerhalb dessen geschaffen werden, was Sie hatten: (z.B. CloudFormation, CDK), unter Verwendung von Benennungskonventionen und Parametern. In Amplify ist es eine erste Klasse – Sie können mehrere Umgebungen haben, die Muster erlauben, wie z.B. die Bereitstellung von gemeinsamen Umgebungen, durch die Änderungen gefördert werden (z.B. Dev > QA > PreProd > Prod) sowie die Bereitstellung von Umgebungen pro Entwickler oder Feature-Branch.
Amplify kann auch CI/CD-Dienste für Sie konfigurieren und bereitstellen, indem Sie die Amplify-Hosting-Erweiterung verwenden und sie in Ihre Apps integrieren, um eine end-to-end-Entwicklungsecosystem zu bieten. Dies richtet CodeCommit, CodeBuild und CodeDeploy ein, um Quellcodeverwaltung, Erstellung und Bereitstellung von Anwendungen zu verwalten. Dies wird in diesem Tutorial nicht behandelt, könnte jedoch zum Automatisieren des Erstellens, Testens und Veröffentlichens von Releases in App-Stores verwendet werden.
Teil Zwei: Authentifizierung Hinzufügen
Normalerweise müssten Sie sich über AWS‘ Authentifizierungsdienst Cognito und unterstützende Dienste, wie IAM, informieren und alles mit etwas wie CloudFormation, Terraform oder CDK zusammenbauen. In Amplify ist es so einfach wie:
amplify add auth
Amplify hinzufügen
ermöglicht es Ihnen, verschiedene „Funktionen“ Ihrem Projekt hinzuzufügen. Im Hintergrund stellt Amplify alle erforderlichen Dienste bereit, die Sie benötigen, mithilfe von CloudFormation, damit Sie sich mehr auf die Funktionen Ihrer Apps konzentrieren können und weniger auf die Verkabelung.
Wenn ich sage, es ist so einfach wie das Eingeben dieser drei magischen Wörter oben… ist es nicht ganz so einfach. Amplify wird Ihnen verschiedene Fragen stellen, um zu verstehen, wie Sie möchten, dass Menschen authentifiziert werden und welche Kontrollen Sie einrichten möchten. Wenn Sie „Standardkonfiguration“ wählen, wird Amplify die Authentifizierung mit sinnvollen Standardeinstellungen einrichten, um Sie schnell am Laufen zu bringen. Ich werde „Manuelle Konfiguration“ wählen, um zu demonstrieren, wie konfigurierbar Amplify ist.
Die obige Einrichtung ermöglicht die Erstellung von Konten mit nur Ihrer Mobiltelefonnummer (keine E-Mail-Adresse erforderlich) und verifiziert, dass Sie der tatsächliche Eigentümer dieser Nummer sind, indem sie MFA zur Verifizierung und weiteren Anmeldeversuchen verwendet. Ich rate stark davon ab, OAuth als standardisiertes Authentifizierungsverfahren zu verwenden, aber ich habe es hier aus Gründen der Einfachheit nicht verwendet.
Wenn Sie jetzt Funktionen hinzufügen, werden sie nicht sofort bereitgestellt. Deshalb sind die Befehle unheimlich schnell abgeschlossen. Alle diese Befehle bereiten die Konfiguration Ihres Amplify-Apps (und den lokalen Umgebung) vor, um diese Funktionen bereitzustellen.
Um Funktionen (oder Konfigurationsänderungen) bereitzustellen, müssen Sie einen Push ausführen:
amplify push
Hinweis: Dies unterscheidet sich vom amplify publish
Befehl, der sowohl Backend- als auch Frontend-Dienste erstellt und bereitstellt. Push bereitet nur Backend-Ressourcen vor (und das ist alles, was wir in diesem Tutorial benötigen, da wir mobile Apps erstellen werden).
Wenn Sie Auth (oder eine beliebige Amplify-Funktion) hinzufügen, fügt Amplify eine Dart-Datei namens lib/amplifyconfiguration.dart
hinzu. Diese Datei wird von Git ignoriert, da sie sensible Anmeldeinformationen in Bezug auf Ihre bereitgestellten Ressourcen enthält und automatisch mit der Amplify-Umgebung, in der Sie arbeiten, synchronisiert wird. Weitere Informationen dazu finden Sie hier.
An diesem Punkt haben wir Amplify mit einer App und einer Entwicklungsumgebung eingerichtet und Cognito für die Authentifizierung konfiguriert. Es ist ein guter Zeitpunkt für einen Git-Commit, wenn Sie Schritt für Schritt folgen, damit Sie zu diesem Zeitpunkt zurückkehren können, falls erforderlich. Amplify sollte bereits eine .gitignore
Datei für Sie erstellt haben, die alle unnötigen Dateien ausschließt.
Nun, da wir die Backend-Authentifizierungs-Infrastruktur eingerichtet haben, können wir mit dem Aufbau unserer mobilen App mit Flutter beginnen.
Authentifizierung von Benutzern in unserer App
I’m following the steps outlined in the authentication for the AWS Amplify tutorial here.
Dies verwendet die vorkonfigurierten Authentifizierungs-UI-Bildschirme und -Workflows, die im amplify_flutter
Paket enthalten sind. Fügen Sie Amplify Flutter-Abhängigkeiten hinzu, indem Sie Folgendes unter “dependencies” innerhalb der pubspec.yaml
Datei hinzufügen:
amplify_flutter: ^0.6.0
amplify_auth_cognito: ^0.6.0
Wenn Sie nicht die Flutter- und Dart-Erweiterungen innerhalb von VSCode verwenden (oder VSCode verwenden), müssen Sie dies mit einem flutter pub get
Befehl fortsetzen. Wenn Sie dies tun, wird VSCode dies automatisch ausführen, wenn Sie die pubspec.yaml
Datei speichern.
Es gibt einen Schnellstart-Ansatz zur Integration der Authentifizierung, der eine vorgefertigte Authenticator-UI-Bibliothek verwendet, die großartig ist, um schnell einen Anmeldeflow zu bootstrappen, der später angepasst werden kann. Wir werden dies in diesem Tutorial verwenden, um die umfangreiche Auswahl an Amplify-Bibliotheken zu demonstrieren und wie schnell Sie sie in Ihrer App integrieren können.
Die Schritte zur Integration der OOTB-Authentifizierungsbibliothek finden Sie hier.
Wir können den Authentifizierer, der das Widget im Beispielcode verziert, wie folgt in den in der Flutter Schnellstartbeispiel bereitgestellten Code übertragen:
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return Authenticator(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// Dies ist das Thema Ihrer Anwendung.
//
// Versuchen Sie, Ihre Anwendung mit "flutter run" auszuführen. Sie werden sehen, dass
// die Anwendung eine blaue Symbolleiste hat. Fahren Sie dann ohne die App zu beenden fort
// und ändern Sie primarySwatch unten in Colors.green und rufen Sie dann
// "hot reload" auf (drücken Sie "r" im Konsolenfenster, in dem Sie "flutter run" ausgeführt haben,
// oder speichern Sie einfach Ihre Änderungen, um "hot reload" in einer Flutter-IDE aufzurufen).
// Beachten Sie, dass der Zähler nicht zurück auf Null zurückgesetzt wurde; die Anwendung
// wird nicht neu gestartet.
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');
}
}
}
Was ist ein Widget?
Es ist das grundlegende Baustein in Flutter, der verwendet wird, um UI-Layouts und Komponenten zusammenzustellen. So ziemlich alles in Flutter-Layouts sind Widgets – Spalten, Gerüstbau, Abstand und Stil, komplexe Komponenten usw. Das Beispiel in den Flutter-Erste-Schritte-Dokumenten verwendet ein „Center“-Widget gefolgt von einem „Text“-Widget, um ein zentral ausgerichtetes Textstück anzuzeigen, das „Hello World“ sagt.
Der obige Code verziert das MyHomePage
Widget mit einem Authentifizierer-Widget, fügt das AmplifyAuthCognito
Plugin hinzu und verwendet die Konfiguration, die der vorherige amplify add auth
Befehl in lib/amplifyconfiguration.dart
erzeugt hat, um automatisch zu Ihrem AWS Cognito User Pool zu verbinden.
Nach dem Starten von Flutter und dem Demo der Authentifizierungsintegration, dauerte es bei mir eine Weile, bis der Schritt „Running pod install“ abgeschlossen war. Sei einfach geduldig (fast 5 Minuten).
Sobald diese Authentifizierungsänderungen vorgenommen wurden und die App startet, wird man mit einem einfachen aber funktionsfähigen Login-Bildschirm begrüßt.
Indem man den „Create Account“-Flow nutzt, kann man seine Telefonnummer und ein Passwort angeben und wird dann mit einer MFA-Herausforderung konfrontiert, um die Registrierung abzuschließen. Man kann dann sehen, dass der Benutzer im Cognito User Pool erstellt wurde:
Man kann dies auch leicht genug auf einem virtuellen Android-Gerät testen. Man muss nicht einmal VSCode verlassen, wenn man die Flutter- und Dart-Plugins installiert hat, also ist das Öffnen von Android Studio nicht notwendig. Wähle einfach den Namen des aktuell aktiven Geräts (iPhone) in der unteren rechten Ecke von VSCode aus, wechsle zu einem bereits erstellten virtuellen Android-Gerät und drücke dann „F5“, um das Debuggen zu starten. Die Erfahrung ist ziemlich ähnlich wie bei iOS:
Beim ersten Deployment nach der Implementierung der Authentifizierungsbibliothek trat beim Versuch, die App zu erstellen, die folgende Ausnahme auf:
uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [:amplify_auth_cognito_android]
Flutter ist in dieser Situation sehr hilfreich, da es direkt nach dem Ausgeben dieses Stapelverfolgungsberichts einen Vorschlag macht:
Das Flutter SDK scheint dies bereits in unserem build.gradle
-Datei zu überschreiben:
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
...
defaultConfig {
// TODO: Legen Sie Ihre eigene eindeutige Anwendungs-ID fest (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.flutterapp"
// Sie können die folgenden Werte an Ihre Anwendungsanforderungen anpassen.
// Weitere Informationen finden Sie unter: https://docs.flutter.dev/deployment/android#prüfen-der-buildkonfiguration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
Obwohl Flutter als Minimalanforderung API 16 benötigt (in flutter.gradle
deklariert), benötigt die Amplify Auth-Bibliothek mindestens 21. Um dies zu korrigieren, ändern Sie einfach die minSdkVersion
von “flutter.minSdkVersion” auf “21.”
Sobald Sie sich authentifizieren, werden Sie mit der zuvor gezeigten Beispiel-“Button-Klicker”-App konfrontiert. Jetzt ist es Zeit, diese nach unseren Bedürfnissen anzupassen.
Teil Drei: Hochladen eines Profilbilds
In diesem Beispiel nutzen wir diese Funktion, um Benutzern zu ermöglichen, ein Foto von sich selbst hochzuladen, das innerhalb der App als ihr Avatar verwendet wird.
Möchten Sie Speicherfunktionen für Ihre App hinzufügen? Kein Problem, führen Sie einfach Folgendes aus:
amplify add storage
und Amplify wird die Backend-Dienste bereitstellen, die für die Verwendung von cloudbasiertem Speicher in Ihrer App erforderlich sind. Amplify integriert Flutter leicht mit S3, um Benutzern Ihrer App das Speichern von Objekten zu ermöglichen. Die Flexibilität von S3 ermöglicht es Ihnen, alle möglichen Assets zu speichern, und in Kombination mit Cognito und Amplify können Sie einfach private Bereiche für Benutzer zur Verfügung stellen, um Fotos, Videos, Dateien usw. zu speichern.
Dateien können mit öffentlichem, geschütztem oder privatem Zugriff gespeichert werden:
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 |
Für unser Profilbild erstellen wir es mit geschütztem Zugriff, sodass nur der Benutzer seinen Avatar aktualisieren und löschen kann, aber andere in der App könnten ihn anzeigen.
Hier beginnen wir mit dem Styling und dem Aufbau der Struktur unserer App. Flutter ist eng mit dem Material Design-System verbunden, das im mobilen App-Entwicklungsprozess weit verbreitet ist, um ein konsistentes Erscheinungsbild zu gewährleisten. Es bietet eine Reihe plattformübergreifend kompatibler Komponenten, deren Stile überschrieben werden können, um eine Erfahrung spezifisch für Ihre Marke zu schaffen.
Das Flutter Getting Started-Template hat bereits einige Widgets mit dem MaterialApp-Widget vorgefertigt. Wir haben dies zuvor mit einem Authenticator-Widget dekoriert. Jetzt werden wir das MyHomePage-Kindwidget von MaterialApp erweitern, um ein Profilbild bereitzustellen.
Sie kombinieren Widgets zu einer Baumstruktur, die als „Widget-Hierarchie“ bekannt ist. Sie beginnen immer mit einem Hauptwidget. In unserer App ist das Authenticator-Wrapper-Widget, das den initialen Anmeldevorgang behandelt. Scaffold
ist ein gutes Widget, um Ihre Layouts aufzubauen: Es wird häufig als Hauptwidget mit Material-Apps verwendet und verfügt über eine Vielzahl von Platzhaltern wie z.B. einen fliegenden Aktionbutton, eine Unterseite (zum Hochschieben zusätzlicher Details), eine App-Leiste usw.
Zunächst fügen wir einfach ein Bild-Widget hinzu, das auf eine Netzwerk-URL zeigt. Wir werden dies später durch eines ersetzen, das wir aufgenommen und auf S3 hochgeladen haben. Ich habe die folgenden Ressourcen verwendet, um ein Bild mit einem abgerundeten Platzhalter hinzuzufügen:
- API Flutter
- Google Flutter
Fügen Sie im Kinderarray des eingebetteten Spaltenwidgets das folgende Container-Widget hinzu:
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),
),
)
],
Wir können nun ein Bild aus dem Web anzeigen:
Als Nächstes ermöglichen wir dem Benutzer, ein Profilbild aus einem Bild auf seinem Gerät auszuwählen. Ein kleiner Google-Suchaufruf ergab diese Bibliothek, die die Details zum Auswählen von Bildern abstrahiert:
- Google: „Pub Dev Packages Image Picker“
Zwei Codezeilen genügen, um den Benutzer aufzufordern, ein Bild auszuwählen:
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
Für iOS müssen Sie den Schlüssel NSPhotoLibraryUsageDescription
zum <Projektverzeichnis>/ios/Runner/Info.plist
Xcode-Konfigurationsdatei hinzufügen, um den Zugriff auf die Benutzerfotos anzufordern; andernfalls stürzt die App ab.
Wir werden dies mit einem GestureDetector
-Widget verknüpfen, das bei einer Berührung den Benutzer auffordert, ein Bild für sein Profilbild auszuwählen:
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");
}
Rufen Sie setState()
auf, aktualisieren Sie die Widgetfelder innerhalb des Lambda, das an Flutter übergeben wird, damit dieser die build()
-Funktion aufruft, wo der aktualisierte Zustand zum erneuten Zeichnen des Widgets verwendet werden kann. In unserem Fall wird das Profilbild aufgefüllt, also erstellen wir ein Container-Widget, das das Bild anzeigt. Der nullsichere ??
-Operator bietet einen Standard-Profil-Platzhalter für den Fall, dass der Benutzer noch kein Bild ausgewählt hat.
Sie müssen auch einen Profil-Platzhalterbild in Ihr Repository aufnehmen und in Ihrer pubspec.yml
-Datei referenzieren, damit es in der Erstellung aufgenommen wird. Sie können das Bild aus meinem Repository verwenden, während Sie dies in Ihrer pubspec.yml
-Datei hinzufügen:
# Die folgende Sektion ist spezifisch für Flutter-Pakete.
flutter:
...
# Um Assets zu Ihrer Anwendung hinzuzufügen, fügen Sie eine Assets-Sektion hinzu, wie folgt:
assets:
- assets/profile-placeholder.png
An dieser Stelle können wir ein Profilbild aus der Foto-Galerie des Geräts auswählen und es als abgerundetes Bild in der App anzeigen. Dieses Bild wird jedoch nirgends gespeichert – schließt man die App, geht es verloren (und auch andere Nutzer könnten Ihr Bild nicht sehen).
Als Nächstes werden wir dies mit einer Cloud-Speicherlösung – AWS S3 – verbinden. Wenn der Nutzer ein Foto aus der Galerie seines Geräts auswählt, wird es in seinem privaten Bereich in S3 hochgeladen, und dann wird das Bild-Widget das Bild von dort abrufen (anstatt direkt vom Gerät) selbst:
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);
});
}
}
Nun, wenn unser Nutzer ein Bild von seinem Gerät auswählt, wird unsere App es zu S3 hochladen und dann auf dem Bildschirm anzeigen.
Als nächstes werden wir die App dazu bringen, das Profilbild des Nutzers bei der Startseite von S3 herunterzuladen:
@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");
});
}
}
Als nächstes werden wir die Logik für das Profilbild in einen eigenen wiederverwendbaren Komponente umstrukturieren. Sie können die fertige Komponente in meinem GitHub-Repo sehen, das alle oben genannten Logiken enthält. Sie können dann das _MyHomePageStage
Komponente aufräumen und Ihr neues Widget in die Hierarchie wie folgt einsetzen:
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Text(
'User Profile',
style: Theme.of(context).textTheme.titleLarge,
)),
const ProfilePicture(),
TextField(...
Um das Thema Profilbild abzuschließen, werden wir einen Lade-Spinner hinzufügen, um den Nutzern Feedback zu geben, dass etwas passiert. Wir werden ein _isLoading
boolean Feld verwenden, um den Ladezustand des Bildes zu verfolgen, was entscheidet, ob der Spinner oder ein Bild angezeigt wird:
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;
});
}
}
Teil Vier: Speichern von Benutzerdetails (gekürzt)
Großartig, wir haben nun einen mobilen App-Rahmen in Betrieb, der Benutzer, Authentifizierung und Profilbilder umfasst. Als nächstes schauen wir, ob wir eine API erstellen können, die Benutzeranmeldeinformationen nutzt, um zusätzliche Informationen über sie abzurufen.
Normalerweise würde ich sagen: „Du willst eine API? Einfach:“
amplify add api
Hier lag der Großteil des Aufwands und der Fehlersuche, da es je nach gewählter Konfiguration nicht vollständig innerhalb des Amplify- und Flutter-Ökosystems unterstützt wird. Die Verwendung des vorkonfigurierten Datenspeichers und Modells kann auch zu ineffizienten Lesemustern führen, die sehr schnell kostspielig und langsam werden können.
Amplify bietet eine hochlevelige API zur Interaktion mit Daten in AppSync, aber in diesem Tutorial verwende ich GraphQL mit niedrigleveligen Abfragen, da dies mehr Flexibilität bietet und die Verwendung eines Global Secondary Index in DynamoDB ermöglicht, um Tabellenscans zu vermeiden. Wenn du verstehen möchtest, wie ich hierher gekommen bin und welche Stolpersteine es gibt, schau dir „Challenges Working with and Tuning AWS Amplify and Appsync with Flutter“ an.
Amplify versucht, die gestellten Fragen beim Erstellen einer API standardmäßig zu beantworten, aber du kannst alle diese überschreiben, indem du nach oben scrollst zum gewünschten Option änderst. In dieser Situation möchten wir eine GraphQL-Endpunkt (um DataStore zu nutzen) und die API-Autorisierung durch das Cognito User Pool behandelt werden, da wir eine feingranulare Zugriffskontrolle anwenden möchten, so dass nur der Benutzer seine eigenen Details aktualisieren oder löschen kann (andere Benutzer können sie jedoch anzeigen).
Wenn wir die API erstellen, erstellt Amplify einen grundlegenden ToDo GraphQL-Schematyp. Wir werden diesen aktualisieren und einige Autorisierungsregeln hinzufügen, bevor wir die API-Änderungen pushen.
Ändern Sie das „ToDo“-Template für das GraphQL-Schema, um unsere Benutzerprofilinformationen.
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
}
zu berücksichtigen. Die private Regel ermöglicht angemeldeten Benutzern das Ansehen der Profile von jedem anderen Benutzer. Indem wir nicht auf „öffentlich“ setzen, verhindern wir, dass Personen, die nicht angemeldet sind, Profile ansehen können. Der IAM-Anbieter verhindert, dass Benutzer direkt auf die GraphQL-API zugreifen – sie müssen die App verwenden und die „unauthenticated“-Rolle in unserer Cognito-Identitätsgruppe nutzen (d. h. ausgeloggt), um Benutzerdetails anzuzeigen.
Die „owner“-Regel ermöglicht dem Benutzer, der das Profil erstellt hat, das Erstellen, Lesen und Aktualisieren seines eigenen Profils. In diesem Beispiel erlauben wir ihnen jedoch nicht, ihr eigenes Profil zu löschen.
An dieser Stelle können wir die Cloud-Infrastruktur bereitstellen, die die API-Funktion unterstützt:
amplify push
Wenn Sie das vorhandene GraphQL-Modell von ToDo in UserProfile ändern, und Sie zuvor einen amplify push
ausgeführt und die Infrastruktur bereitgestellt haben, erhalten Sie möglicherweise einen Fehler, der besagt, dass die gewünschte Änderung die Zerstörung der vorhandenen DynamoDB-Tabelle erfordern würde. Amplify verhindert dies, falls Daten verloren gehen, indem die vorhandene ToDo-Tabelle gelöscht wird. Wenn Sie diesen Fehler erhalten, müssen Sie amplify push --allow-destructive-graphql-schema-updates
ausführen.
Wenn Sie einen amplify push
ausführen, wird Amplify und CloudFormation eine AppSync GraphQL-API, Zwischenlösungen und eine unterstützende DynamoDB-Tabelle ähnlich wie diese erstellen:

Sobald wir ein GraphQL-Schema definiert haben, können wir Amplify verwenden, um Dart-Code zu generieren, der die Modell- und Repository-Schicht darstellt und mit der API funktionieren kann:
amplify codegen models
An dieser Stelle können wir einige Eingabefelder auf unserer Seite hinzufügen, um den Namen, den Standort und das bevorzugte Programmiersprachen des Benutzers zu erfassen.
So sehen die Änderungen an den Textfeldern in unserem _MyHomePageState
-Komponente aus:
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,
)
]
Dann verbinden wir unsere TextFields mit der AppSync GraphQL API, so dass, wenn der Benutzer eine „Speichern“-Schaltfläche für schwebende Aktionen drückt, die Änderungen mit DynamoDB synchronisiert werden:
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}');
}
}
Schließlich, wenn unsere Benutzer die App öffnen, möchten wir die aktuellsten Profilinformationen aus der Cloud abrufen. Um dies zu erreichen, machen wir einen Anruf als Teil der Initialisierung unseres _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 ?? "";
});
}
}
Wir haben jetzt eine API, in der wir Daten speichern können, die mit Cognito gesichert ist und von DynamoDB unterstützt wird. Ziemlich cool, wenn man bedenkt, dass ich keine Infrastruktur-as-Code geschrieben habe.
An dieser Stelle haben wir eine Möglichkeit, Profilinformationen eines Benutzers abzufragen, anzuzeigen und zu aktualisieren. Fühlt sich für mich wie ein weiterer Speicherpunkt an.
Teil Fünf: Etwas Designflair hinzufügen
Schließlich sieht die erweiterte Beispiel-App etwas einfach aus. Zeit, sie ein wenig zum Leben zu erwecken.
Nun, ich bin kein UI-Experte, also habe ich mich bei dribbble.com inspirieren lassen und mich für einen lauten Hintergrund und einen kontrastierenden weißen Kartenbereich für die Profildetails entschieden.
Hinzufügen eines Hintergrundbildes
Zuerst wollte ich ein Hintergrundbild hinzufügen, um der App etwas Farbe zu verleihen.
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.
Das resultierende Code-Snippet sieht folgendermaßen aus:
@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(
// Hier nehmen wir den Wert vom MyHomePage-Objekt, das von der App.build-Methode erstellt wurde, und verwenden ihn, um den Titel unserer App-Leiste festzulegen.
// der App.build-Methode erstellt wurde, und verwenden ihn, um den Titel unserer App-Leiste festzulegen.
title: Text(widget.title),
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
),
body: Center(
...
Nun, das sieht ziemlich hübsch aus, aber der Hintergrund wirkt etwas irritierend gegenüber den editierbaren Elementen auf dem Bildschirm:
Also habe ich die Textfelder und das Profilbild in einem Card
so eingewickelt und einige Abstände und Innenabstände gesetzt, damit es nicht eng aussieht:
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(
...
Dies ist eine Möglichkeit, es zu tun, obwohl ich vermute, dass es einen idiomatischeren Ansatz gibt, der das Material-Design-System nutzt. Vielleicht ein Thema für einen anderen Beitrag.
Ändern Sie das App-Symbol und den Titel im Menü
Wenn Sie das Symbol Ihrer App ändern möchten, müssen Sie eine Reihe von Varianten Ihres Logos bereitstellen, alle in unterschiedlichen Auflösungen für iOS und Android separat. Beide haben auch separate Anforderungen (von denen einige Sie ignorieren werden, um die Genehmigung Ihrer App zu verhindern), sodass dies schnell eine mühsame Aufgabe wird.
Glücklicherweise gibt es ein Dart-Paket, das die ganze schwere Arbeit erledigt. Angenommen, Sie haben ein Quellbild Ihres App-Symbols, kann es alle für beide Plattformen erforderlichen Permutationen erzeugen.
Für diese Demo-App habe ich einfach ein zufälliges App-Symbol von Google Images abgeholt:
Quelle:Google Images
Die Anleitung führte mich dazu, dieses minimale Konfigurationsset zu definieren, um erfolgreich Symbole zu generieren. Setzen Sie dies am Ende Ihres pubspec.yaml
Datei:
flutter_icons:
android: true
ios: true
remove_alpha_ios: true
image_path: "assets/app-icon.png"
Mit dem oben genannten im Platz, führen Sie diesen Befehl aus, um Icon-Varianten für beide iOS und Android zu generieren:
flutter pub run flutter_launcher_icons
Sie sollten eine Vielzahl von Icon-Dateien für Android und iOS in den Verzeichnissen android/app/src/main/res/mipmap-hdpi/
und ios/Runner/Assets.xcassets/AppIcon.appiconset/
jeweils generiert finden.
Leider ist das Ändern des App-Namens nach meinen Recherchen immer noch ein manueller Prozess. Unter Verwendung eines Artikels namens „How to Change App Name in Flutter—The Right Way in 2023“ auf Flutter Beads als Anleitung, habe ich den App-Namen in den folgenden beiden Dateien für iOS und Android jeweils geändert:
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">
Dies gibt Ihnen nun ein schönes kleines App-Symbol und einen Titel:
Abschluss
Damit ist die Einführung in die Arbeit mit Flutter und AWS Amplify abgeschlossen und zeigt hoffentlich, wie schnell die Bereitstellung der erforderlichen Unterstützungsressourcen und Rahmencodes für die schnelle Prototyping einer mobilen Anwendung für mehrere Plattformen erfolgen kann.
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!
Erfahrungen mit Problemen
Fehlende Android-Kommandozeilentools
Der Speicherort meines Android SDK Managers ist:
/Users/benfoster/Library/Android/sdk/tools/bin/sdkmanager
Durch das Folgende wurden die Android-Kommandozeilentools installiert: Stack Overflow „Failed to Install Android SDK Java Lang Noclassdeffounderror JavaX XML Bind A.“
flutter doctor --android-licenses
App bleibt auf iOS eingeloggt
Bei der Entwicklung wollte ich den Prozess des Anmeldens im App-Zustand wiederholen. Leider behielt die App zwischen App-Schließungen die Benutzerinformationen bei – das Schließen und erneute Öffnen der App hielt die Anmeldung aufrecht.
Meine vorherige Erfahrung mit Android-Entwicklung und Amplify hatte mich überzeugt, dass das Entfernen der App und das erneute Ausführen von „flutter run“ den Benutzerzustand entfernt und von vorne beginnt. Leider zeigte sich auch dies nicht erfolgreich, weshalb ich mich dazu entschied, das Telefon jedes Mal zu löschen, wenn ich mit einem sauberen Brett beginnen musste:
Source:
https://dzone.com/articles/cross-platform-mobile-app-prototyping-with-flutter-and-aws-amplify