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.
Si vous suivez ce tutoriel, cela ne devrait pas vous prendre plus d’une heure. Eh bien, cela m’a pris plusieurs heures en me battant avec divers problèmes, mais j’espère avoir assez bien documenté pour que vous ne les rencontriez pas.
Voici le produit fini. Si vous voulez la version « voici celle que j’ai faite plus tôt », suivez les étapes dans le fichier readme, vous devriez la faire fonctionner en environ quinze minutes. Voici le lien GitHub.
Ce tutoriel se compose de cinq parties:
- Prérequis et Configuration du Codebase
- Ajout d’Authentification
- Téléchargement d’une Photo de Profil
- Stockage des Détails de l’Utilisateur
- Ajout d’une Touche de Design
Recommandation
Flutter est une plateforme très mature qui est utilisée depuis plusieurs années maintenant, avec une communauté florissante et de nombreux plugins et extensions pour réaliser la plupart des choses.
Amplify, lui aussi, est une plateforme solide ; cependant, j’ai trouvé que la fonctionnalité API était difficile à utiliser et les bibliothèques Flutter n’étaient pas à jour avec les dernières annonces et fonctionnalités d’Amplify. En particulier, travailler avec AppSync GraphQL et DataStore (pour le stockage de données hors ligne et la synchronisation) était assez fragile (comme vous le verrez plus tard).
Ensemble, ces deux sont une excellente combinaison pour accélérer le développement de prototypes d’applications mobiles, mais lorsque vous avez l’impression de forcer Amplify à votre manière, n’hésitez pas à l’abandonner au profit de travailler directement avec les services AWS qu’il abstrait.
L’application de démonstration que j’ai créée contient des informations de profil utilisateur – un besoin commun de nombreuses applications. Vous pouvez créer un compte et vous connecter, télécharger une photo de profil et soumettre quelques détails sur vous-même. Nous allons entrer dans les détails du full-stack – travailler avec Flutter et Dart pour le code de l’application jusqu’aux likes de DynamoDB pour vous donner une pleine étendue de ce dont vous avez besoin de savoir.
Partie Une: Prérequis et Configuration du Codebase
Ce tutoriel suppose que vous avez déjà les éléments suivants configurés sur votre machine:
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 et Amplify ont des outils de gabarit qui créent la structure de base de votre projet. Il est important de les faire dans un certain ordre ; sinon, votre structure de dossiers ne correspondra pas à ce que les outils attendent, ce qui vous causera des ennuis à rectifier plus tard.
Assurez-vous de créer la structure de votre codebase en utilisant Flutter en premier, puis initialisez Amplify à l’intérieur.
I used the official Flutter getting started documentation to kick things off for my demo.
Voyons si nous pouvons faire fonctionner Flutter. Tout d’abord, pour vérifier si vous l’avez correctement installé et ajouté à votre PATH, vous pouvez exécuter flutter doctor
.
Si c’est votre première expérience dans le développement mobile, il y aura quelques éléments qui doivent être traités ici. Pour moi, c’était:
- Installer Android Studio (et Android SDK CLI).
- Installer XCode et CocoaPods.
- Accepter les termes et conditions pour les outils Android et iOS.
Création de votre Codebase de l’Application
Lorsque vous avez tous les prérequis prêts, vous pouvez créer le gabarit Flutter. Cela crée le dossier dans lequel nous travaillerons, alors exécutez cette commande à partir d’un répertoire parent :
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).
Vous pourriez vouloir renommer le répertoire de niveau supérieur créé à ce stade si vous ne souhaitez pas qu’il corresponde au nom de votre application. Je l’ai changé de « flutterapp » en « flutter-amplify-tutorial » (le nom de mon dépôt git).
À ce stade, Flutter a créé soixante-treize fichiers pour nous. Jetons un coup d’œil à ce qu’ils sont :
Les dossiers dans lesquels nous passerons le plus de temps sont ios/android
et lib/
. À l’intérieur des dossiers ios
et android
se trouvent des ressources de projet qui peuvent être ouvertes avec XCode et Android Studio respectivement. Ces projets agissent comme une interopérabilité entre le code Dart indépendant de la plateforme et vos plateformes cibles, et vous pouvez les utiliser pour tester votre application contre les plateformes respectives. Essayons cela maintenant avec iOS :
Configuration iOS
open -a Simulator
flutter run
Sur mon Mac, avec une configuration minimale de XCode, cela est passé de rien à exécuter un simulateur iPhone 14 Pro Max avec l’application Flutter pré-scaffoldée en cours d’exécution, ce qui est assez cool.
Si vous voyez ce qui suit : alors félicitations, vous avez réussi à générer le squelette avec succès.
Vous pouvez également ouvrir le projet ios/Runner.xcodeproj
dans XCode, explorer son contenu et l’exécuter contre des simulateurs et des appareils physiques comme vous le feriez pour tout autre projet XCode.
Configuration Android
Android est un peu moins direct, car vous devez configurer explicitement un émulateur dans Android Studio avant de pouvoir l’exécuter. Ouvrez le projet android/flutterapp_android.iml
dans Android Studio pour commencer, puis vous pouvez configurer et exécuter un émulateur pour tester l’application.
Donnez à Android Studio quelques minutes pour télécharger Gradle et toutes les dépendances nécessaires pour exécuter l’application—vous pouvez suivre la progression de ce processus dans la barre de progression en bas à droite.
Lorsqu’Android Studio est stabilisé, si vous avez déjà configuré un appareil simulé dans AVD, vous devriez pouvoir appuyer sur le bouton de lecture en haut à droite de la fenêtre:
Et voici, la même application sous Android:
Ceci démontre le code de l’exemple d’application fourni lorsque vous créez un nouveau projet Flutter. Au cours de ce tutoriel, nous remplacerons progressivement ce code par le nôtre.
C’est un bon moment pour faire un commit git, maintenant que nous avons mis en place les fondations pour le développement Flutter. Nous sommes maintenant à un stade où nous pouvons commencer à jouer avec le code Flutter et voir nos résultats simultanément sur iOS et Android.
Flutter utilise Dart comme langage intermédiaire entre Android et iOS, et tout le code avec lequel vous interagirez se trouve dans le dossier lib/
. Il devrait y avoir un fichier main.dart
, c’est là que nous commencerons à jouer.
Configurer et déployer une nouvelle application avec Amplify
Maintenant que nous avons les outils mobiles prêts à travailler, nous avons besoin d’une infrastructure backend pour soutenir la fonctionnalité de l’application.
Nous utiliserons AWS et ses nombreux services pour soutenir notre application, mais tout cela sera géré à l’aide du service AWS Amplify. La plupart de cela sera géré de manière transparente pour nous, et au lieu de nous soucier des services à utiliser, nous nous concentrerons sur les fonctionnalités que nous voulons déployer.
Pour commencer, dans votre dossier de code, exécutez ce qui suit:
amplify init
Ce commande initialise AWS Amplify dans votre projet. Si vous ne l’avez pas utilisé auparavant, il vous posera un certain nombre de questions. Pour les personnes suivantes qui collaborent sur le projet, l’exécution de cette commande configure leur environnement local avec la configuration Amplify déjà en place.
Cela va provisionner certaines ressources AWS initiales pour stocker la configuration et l’état de votre application Amplify, à savoir un bucket S3.
La barre de progression et le statut de déploiement ci-dessus peuvent sembler familiers à certains—c’est CloudFormation, et tout comme AWS CDK, Amplify utilise CFN dans les coulisses pour provisionner toutes les ressources requises. Vous pouvez ouvrir la console des stacks CloudFormation pour le voir en action:
Enfin, lorsque l’interface CLI est complète, vous devriez voir une confirmation similaire à celle ci-dessous, et vous pourrez voir votre nouvelle application déployée dans la Console Amplify:
Gestion de l’environnement
AWS Amplify possède la notion d' »environnements », qui sont des déploiements isolés de votre application et ressources. Historiquement, la notion d’environnements devait être créée dans l’écosystème que vous aviez : (par exemple, CloudFormation, CDK), en utilisant des conventions de nommage et des paramètres. Dans Amplify, c’est un citoyen de première classe—vous pouvez avoir plusieurs environnements qui permettent des modèles, tels que la provision de milieux partagés, dont les modifications sont promues (par exemple, Dev > QA > PreProd > Prod) ainsi que la fourniture d’environnements par développeur ou par branche de fonctionnalité.
Amplify peut également configurer et provisionner des services CI/CD pour vous en utilisant l’hébergement Amplify et les intégrer dans vos applications pour fournir un écosystème de développement end-to-end. Cela configure CodeCommit, CodeBuild et CodeDeploy pour gérer la gestion des contrôles de source, la construction et le déploiement des applications. Cela n’est pas couvert dans ce tutoriel, mais pourrait être utilisé pour automatiser la construction, les tests et la publication des versions sur les boutiques d’applications.
Partie Deux : Ajout de l’authentification
Habituellement, vous auriez besoin d’apprendre à propos du service d’authentification AWS Cognito et des services de soutien, tels que IAM, de tout relier à l’aide de quelque chose comme CloudFormation, Terraform ou CDK. Dans Amplify, c’est aussi simple que de faire :
amplify add auth
Amplify add
vous permet d’ajouter diverses « fonctionnalités » à votre projet. Dans les coulisses, Amplify déploiera et configurera tous les services requis dont vous avez besoin à l’aide de CloudFormation, afin que vous puissiez vous concentrer davantage sur les fonctionnalités de vos applications et moins sur le câblage.
Lorsque je dis que c’est aussi simple que d’écrire ces trois mots magiques ci-dessus… ce n’est pas tout à fait aussi simple. Amplify vous posera diverses questions pour comprendre comment vous souhaitez que les personnes s’authentifient et quelles sont les contrôles que vous souhaitez mettre en place. Si vous choisissez « Configuration par défaut« , Amplify configurera l’authentification avec des valeurs par défaut sensibles pour vous aider à démarrer rapidement. Je vais choisir « Configuration manuelle » pour démontrer à quel point Amplify est configurable.
Le montage ci-dessus vous permet de créer des comptes avec uniquement votre numéro de téléphone (pas besoin d’adresse e-mail), et de vérifier que vous êtes le propriétaire réel de ce numéro en utilisant l’authentification multifacteur (MFA) pour la vérification et les tentatives de connexion ultérieures. Je recommande vivement d’utiliser OAuth comme mécanisme d’authentification standardisé, mais je ne l’ai pas utilisé ici pour des raisons de simplicité.
Dès que vous ajoutez des fonctionnalités, elles ne sont pas immédiatement provisionnées. C’est pourquoi les commandes sont mystérieusement rapides à s’exécuter. Toutes ces commandes préparent la configuration de votre application Amplify (et l’environnement local) pour déployer ces fonctionnalités.
Pour déployer des fonctionnalités (ou tout changement de configuration), vous devez effectuer un push:
amplify push
Note: cela diffère de la commande amplify publish
, qui construit et déploie à la fois les services backend et frontend. Le push ne provisionne que les ressources backend (et c’est tout ce dont nous aurons besoin dans ce tutoriel car nous construirons des applications mobiles).
Lorsque vous ajoutez l’authentification (ou toute fonctionnalité Amplify), Amplify ajoute un fichier Dart appelé lib/amplifyconfiguration.dart
. Ce fichier est ignoré par Git car il contient des informations sensibles concernant vos ressources déployées et est automatiquement synchronisé avec l’environnement Amplify dans lequel vous travaillez. Vous pouvez trouver plus d’informations à ce sujet ici.
À ce stade, nous avons configuré Amplify avec une application et un environnement de développement créés et Cognito configuré pour l’authentification. C’est un bon moment pour faire un commit git si vous suivez, afin de pouvoir revenir à ce point si nécessaire. Amplify devrait avoir déjà créé un fichier .gitignore
pour vous, excluant tous les fichiers inutiles.
Maintenant que nous avons mis en place l’infrastructure d’authentification backend, nous pouvons commencer à construire notre application mobile avec Flutter.
Authentification des utilisateurs dans notre application
I’m following the steps outlined in the authentication for the AWS Amplify tutorial here.
Ceci utilise les écrans et le workflow d’authentification prêts à l’emploi fournis dans amplify_flutter
. Ajoutez les dépendances Amplify Flutter en ajoutant ce qui suit sous “dependencies” dans le fichier pubspec.yaml
:
amplify_flutter: ^0.6.0
amplify_auth_cognito: ^0.6.0
Si vous n’utilisez pas les extensions Flutter et Dart dans VSCode (ou si vous n’utilisez pas VSCode), vous devrez suivre cela avec une commande flutter pub get
. Si vous le faites, alors VSCode exécutera automatiquement cela lorsque vous enregistrerez le fichier pubspec.yaml
.
Il existe une approche de démarrage rapide pour intégrer l’authentification qui utilise une bibliothèque d’interface utilisateur Authenticator préfaite, idéale pour amorcer rapidement un flux de connexion qui peut être personnalisé plus tard. Nous utiliserons cela dans ce tutoriel pour démontrer l’ensemble étendu de bibliothèques Amplify disponibles, et à quelle vitesse vous pouvez les intégrer dans votre application.
Les étapes pour intégrer la bibliothèque d’authentification OOTB sont ici.
Nous pouvons transposer le widget d’authentificateur décoratif configuré dans le code d’exemple sur le code fourni dans l’exemple de démarrage rapide Flutter comme ceci:
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return Authenticator(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// Ceci est le thème de votre application.
//
// Essayez d'exécuter votre application avec "flutter run". Vous verrez que
// l'application a une barre de navigation bleue. Ensuite, sans quitter l'application, essayez
// de changer primarySwatch ci-dessous en Colors.green, puis invoquez
// "rechargement à chaud" (appuyez sur "r" dans la console où vous avez exécuté "flutter run",
// ou simplement enregistrez vos modifications pour "rechargement à chaud" dans un IDE Flutter).
// Notez que le compteur ne remet pas à zéro ; l'application
// n'est pas redémarrée.
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');
}
}
}
Qu’est-ce qu’un Widget?
C’est le bloc de construction de base dans Flutter utilisé pour composer les dispositions de l’interface utilisateur et les composants. Presque tout dans les dispositions Flutter sont des widgets—colonnes, échafaudage, rembourrage et style, composants complexes, etc. L’exemple sur la documentation de démarrage de Flutter utilise un « Center » Widget suivi d’un « Text » Widget pour afficher un morceau de texte centré qui dit « Hello World. »
Le code ci-dessus décore le widget MyHomePage
avec un widget d’authentificateur, ajoute le plugin AmplifyAuthCognito
, et prend la configuration que la commande précédente amplify add auth
a générée dans lib/amplifyconfiguration.dart
pour se connecter automatiquement à votre AWS Cognito User Pool.
Après avoir lancé Flutter, pour présenter l’intégration de l’authentification, il m’a fallu un certain temps pour que l’étape « Running pod install » se termine. Soyez patient (près de 5 minutes).
Une fois ces modifications d’authentification effectuées et l’application démarrée, vous êtes accueilli par un écran de connexion basique mais fonctionnel.
En utilisant le flux « Créer un compte« , vous pouvez fournir votre numéro de téléphone et un mot de passe, puis vous êtes confronté à un défi MFA pour finaliser l’inscription. Vous pouvez alors voir que l’utilisateur est créé dans le Pool d’utilisateurs Cognito :
Vous pouvez tester cela facilement sur un appareil Android virtuel également. Vous n’avez même pas besoin de quitter VSCode si vous avez installé les plugins Flutter et Dart, donc il n’est pas nécessaire d’ouvrir Android Studio. Il suffit de sélectionner le nom de l’appareil actif (iPhone) dans le coin inférieur droit de VSCode, de basculer vers un appareil Android virtuel que vous avez déjà créé, puis d’appuyer sur « F5 » pour démarrer le débogage. L’expérience est assez similaire à iOS :
Lors du déploiement pour la première fois après avoir implémenté la bibliothèque d’authentification, j’ai rencontré l’exception suivante lors de la tentative de construction de l’application :
uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [:amplify_auth_cognito_android]
Flutter est vraiment utile dans ce scénario, car juste après l’affichage de cette trace de pile, il propose une recommandation :
Le SDK Flutter semble déjà remplacer cela dans notre fichier build.gradle
:
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
...
defaultConfig {
// TODO: Spécifiez votre propre ID d'application unique (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.flutterapp"
// Vous pouvez mettre à jour les valeurs suivantes pour correspondre aux besoins de votre application.
// Pour plus d'informations, voir : https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
Bien que Flutter, en tant que minimum, nécessite l’utilisation de l’API 16 (déclarée dans flutter.gradle
), la bibliothèque Amplify Auth nécessite au moins 21. Pour corriger cela, il suffit de changer le minSdkVersion
de “flutter.minSdkVersion” à “21.”
Une fois que vous vous authentifiez, vous êtes présenté avec l’application d’exemple « bouton cliquer » présentée précédemment. Maintenant, il est temps de commencer à personnaliser selon nos besoins.
Partie Trois : Téléchargement d’une Photo de Profil
Dans cet exemple, nous utiliserons cette capacité pour permettre aux utilisateurs de télécharger une photo d’eux-mêmes à utiliser comme avatar dans l’application.
Voulez-vous ajouter des fonctionnalités de stockage à votre application ? Pas de problème, il suffit de faire :
amplify add storage
et Amplify provisionnera les services back-end nécessaires pour que votre application utilise le stockage basé sur le cloud. Amplify intègre facilement Flutter avec S3 pour permettre aux utilisateurs de votre application de stocker des objets. La flexibilité de S3 vous permet de stocker toutes sortes d’actifs, et couplée avec Cognito et Amplify, vous pouvez facilement provisionner des zones privées pour les utilisateurs de stocker des photos, vidéos, fichiers, etc.
Les fichiers peuvent être enregistrés avec un accès public, protégé ou privé :
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 |
Pour notre photo de profil, nous allons la créer avec un accès protégé pour que seul l’utilisateur puisse mettre à jour et supprimer leur avatar, mais que les autres dans l’application puissent le visualiser.
Voici où nous allons commencer à styliser et à construire la structure de notre application. Flutter est étroitement intégré au système de conception matériel, utilisé intensivement dans le développement d’applications mobiles pour fournir une apparence et un sentiment cohérents. Il fournit un ensemble de composants compatibles entre plateformes, dont les styles peuvent être remplacés pour créer une expérience spécifique à votre marque.
Le modèle de démarrage de Flutter scaffold déjà certains widgets en utilisant le widget MaterialApp. Nous l’avons précédemment décoré avec un widget authenticateur. Maintenant, nous allons élargir le widget enfant MyHomePage de MaterialApp pour fournir une image de profil.
Vous composez des widgets ensemble dans un arbre, connu sous le nom de « Hiérarchie des Widgets« . Vous commencez toujours avec un widget de niveau supérieur. Dans notre application, c’est le widget enveloppant authenticateur qui gère la connexion initiale. Scaffold
est un bon widget pour baser vos dispositions : il est couramment utilisé comme widget de niveau supérieur avec les applications matérielles ; et il dispose de nombreux espaces réservés, tels qu’un bouton d’action flottant, un volet inférieur (pour faire glisser vers le haut des détails supplémentaires), une barre d’application, etc.
Tout d’abord, ajoutons simplement un widget image pointant vers une URL réseau. Nous remplacerons cela plus tard par celui que nous prenons et téléchargeons sur S3. J’ai utilisé les ressources suivantes pour ajouter une image avec un espace réservé arrondi:
- API Flutter
- Google Flutter
Dans le tableau children du widget column imbriqué, ajoutez le widget container suivant:
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),
),
)
],
Nous pouvons maintenant afficher une image depuis le web :
Ensuite, nous permettrons à l’utilisateur de choisir un avatar à partir d’une image sur son appareil. Un peu de recherche sur Google a révélé cette bibliothèque qui simplifie le processus de sélection d’images :
- Google : « Pub Dev Packages Image Picker »
Deux lignes de code suffisent pour inviter l’utilisateur à sélectionner une image :
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
Sur iOS, il faut ajouter la clé NSPhotoLibraryUsageDescription
au fichier de configuration Xcode <racine du projet>/ios/Runner/Info.plist
pour demander l’accès à la vue des photos de l’utilisateur ; sinon, l’application plantera.
Nous intégrerons cela à un widget GestureDetector
, qui, lorsqu’il reçoit un toucher, invitera l’utilisateur à choisir une image pour son avatar :
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");
}
Appelez setState()
, mettant à jour les champs du widget dans la Lambda passée à Flutter, afin qu’il sache qu’il doit appeler la fonction build()
, où l’état mis à jour peut être utilisé pour redessiner le widget. Dans notre cas, l’image de profil sera remplie, nous créerons donc un widget de conteneur qui affiche l’image. L’opérateur ??
sensible à la nullité fournit un avatar par défaut lorsque l’utilisateur n’a pas encore sélectionné d’image.
Vous devrez également ajouter une image de placeholdeur pour le profil dans votre référentiel et la référencer dans votre fichier pubspec.yml
pour qu’elle soit prise en compte lors de la construction. Vous pouvez utiliser l’image de mon dépôt, tout en ajoutant ceci à votre fichier pubspec.yml
:
# La section suivante est spécifique aux packages Flutter.
flutter:
...
# Pour ajouter des ressources à votre application, ajoutez une section des ressources, comme ceci :
assets:
- assets/profile-placeholder.png
À ce stade, nous pouvons sélectionner un avatar à partir de la galerie d’images de l’appareil et le faire apparaître en tant que photo arrondie dans l’application. Cependant, cette image n’est stockée nulle part—une fois que l’application se ferme, elle est perdue (et aucun autre utilisateur ne pourra voir votre photo non plus).
Ce que nous allons faire ensuite, c’est connecter cela à un stockage en nuage—AWS S3. Lorsque l’utilisateur sélectionne une photo de la galerie de son appareil, nous allons l’envoyer dans sa zone privée sur S3, puis demander au widget d’image de récupérer l’image à partir de là (au lieu de le faire directement depuis l’appareil) lui-même:
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);
});
}
}
Désormais, lorsque notre utilisateur sélectionne une image depuis son appareil, notre application l’enverra à S3 puis l’affichera à l’écran.
Ensuite, nous allons demander à l’application de télécharger l’avatar de l’utilisateur depuis S3 lorsqu’elle démarre:
@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");
});
}
}
Par la suite, nous allons réorganiser la logique de l’avatar en un composant réutilisable. Vous pouvez consulter le composant terminé dans mon dépôt GitHub qui abrite toutes les logiques ci-dessus. Vous pouvez ensuite nettoyer le composant _MyHomePageStage
et insérer votre nouveau widget dans la hiérarchie comme suit:
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Text(
'User Profile',
style: Theme.of(context).textTheme.titleLarge,
)),
const ProfilePicture(),
TextField(...
Pour conclure sur l’avatar, nous ajouterons une barre de progression pour donner des informations de feedback aux utilisateurs indiquant qu’un processus est en cours. Nous utiliserons un champ booléen _isLoading
pour suivre quand l’image est en cours de chargement, ce qui permettra de basculer entre l’affichage de la barre de progression ou de l’image :
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;
});
}
}
Partie Quatre : Stocker les Détails de l’Utilisateur (Résumé)
Super, nous avons maintenant un squelette d’application mobile en place qui inclut des utilisateurs, une authentification et des photos de profil. Ensuite, voyons si nous pouvons créer une API qui utilise les identifiants des utilisateurs pour récupérer des informations supplémentaires sur eux.
Normalement, je dirais : « vous voulez une API ? Simple : »
amplify add api
C’est ici que la majorité des efforts et des dépannages ont été nécessaires, car en fonction de la configuration choisie, ce n’est pas entièrement pris en charge dans l’écosystème Amplify et Flutter. L’utilisation de la base de données et du modèle de la boutique peut également entraîner des schémas de lecture inefficaces, qui peuvent très rapidement devenir coûteux et lents.
Amplify fournit une API de haut niveau pour interagir avec les données dans AppSync, mais dans ce tutoriel, je vais utiliser GraphQL avec des requêtes de bas niveau car cela offre plus de flexibilité et permet d’utiliser un Index Secondaire Global dans DynamoDB pour éviter les scans de table. Si vous voulez comprendre comment je suis arrivé ici et quels sont les différents pièges, consultez « Défis de travail avec et réglage AWS Amplify et Appsync avec Flutter. »
Amplify tente de définir par défaut les questions posées lors de la création d’une API, mais vous pouvez remplacer n’importe laquelle d’entre elles en faisant défiler vers le haut jusqu’à l’option que vous souhaitez changer. Dans ce scénario, nous voulons une fin de point GraphQL (pour tirer parti de DataStore), et l’autorisation de l’API doit être gérée par le Pool d’Utilisateurs Cognito, car nous voulons appliquer un contrôle d’accès granulaire pour que seul l’utilisateur puisse mettre à jour ou supprimer ses propres détails (mais d’autres utilisateurs peuvent les consulter).
Lorsque nous créons l’API, Amplify crée un schéma de type GraphQL ToDo de base. Nous allons mettre à jour cela et ajouter des règles d’autorisation avant de pousser les modifications de l’API.
Modifiez le schéma GraphQL du modèle « ToDo » pour prendre en charge les informations de profil des utilisateurs information.
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
}
La règle privée permet aux utilisateurs connectés de consulter les profils de tout autre utilisateur. En n’utilisant pas public, nous empêchons les personnes non connectées de consulter les profils. Le fournisseur IAM empêche les utilisateurs d’accéder directement à l’API GraphQL – ils doivent utiliser l’application et utiliser le rôle « non authentifié » dans notre pool d’identité Cognito (c’est-à-dire déconnecté) pour consulter les détails des utilisateurs.
La règle « owner » permet à l’utilisateur qui a créé le profil de créer, lire et mettre à jour son propre profil. Dans cet exemple, nous ne leur permettons pas de supprimer leur propre profil cependant.
À ce stade, nous pouvons provisionner notre infrastructure cloud qui prend en charge la fonctionnalité de l’API :
amplify push
Lorsque vous modifiez le modèle GraphQL existant de ToDo à UserProfile, si vous avez précédemment effectué un amplify push
et provisionné l’infrastructure, vous pouvez recevoir une erreur indiquant que le changement demandé nécessiterait la destruction de la table DynamoDB existante. Amplify vous empêche de le faire en cas de perte de données en supprimant la table ToDo existante. Si vous obtenez cette erreur, vous devez exécuter amplify push --allow-destructive-graphql-schema-updates
.
Lorsque vous effectuez un amplify push
, Amplify et CloudFormation mettront en place une API GraphQL AppSync, des résolveurs intermédiaires et une table DynamoDB de support similaire à ceci :

Une fois que nous avons défini un schéma GraphQL, nous pouvons utiliser Amplify pour générer le code Dart qui représente la couche de modèle et de référentiel qui peut fonctionner avec l’API :
amplify codegen models
À ce stade, nous pouvons ajouter quelques champs d’entrée sur notre page pour remplir le nom de l’utilisateur, l’emplacement et le langage de programmation préféré.
Voici à quoi ressemblent les modifications des champs de texte dans notre composant _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,
)
]
Ensuite, connectez nos TextFields à l’API GraphQL AppSync afin que lorsque l’utilisateur appuie sur un bouton d’action flottant « Enregistrer », les modifications soient synchronisées avec 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}');
}
}
Enfin, lorsque nos utilisateurs ouvrent l’application, nous voulons extraire le dernier profil à partir du nuage. Pour ce faire, nous effectuons un appel dans le cadre de l’initialisation de notre _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 ?? "";
});
}
}
Nous avons maintenant une API dans laquelle nous pouvons stocker des données, sécurisée avec Cognito et adossée à DynamoDB. Plutôt cool, considérant que je n’ai pas eu à écrire de code d’infrastructure.
Donc, à ce stade, nous avons un moyen de requêter, d’afficher et de mettre à jour les informations de profil d’un utilisateur. Ça me semble être un autre point de sauvegarde pour moi.
Partie Cinq : Ajout d’une touche de design
Enfin, l’application d’exemple que nous avons étendue a l’air un peu simple. Il est temps de lui donner un peu de vie.
Maintenant, je ne suis pas un expert en interface utilisateur, alors j’ai pris de l’inspiration sur dribbble.com et j’ai opté pour un arrière-plan provocant et une zone de carte blanche contrastante pour les détails du profil.
Ajout d’une Image d’Arrière-Plan
En premier lieu, je voulais ajouter une image d’arrière-plan pour ajouter de la couleur à l’application.
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.
Le code résultant ressemble à ceci :
@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(
// Ici, nous prenons la valeur de l'objet MyHomePage créé par
// la méthode App.build, et l'utilisons pour définir le titre de notre appbar.
title: Text(widget.title),
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
),
body: Center(
...
Eh bien, cela a l’air plutôt joli, mais l’arrière-plan est un peu choquant par rapport aux éléments éditable sur l’écran :
J’ai donc enveloppé les zones de texte et l’image de profil dans un Card
comme ceci, en définissant un certain marge et rembourrage pour ne pas avoir l’air serré :
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(
...
C’est une façon de le faire, bien que je soupçonne qu’il y ait une approche plus idiomatique qui utilise le système de conception matériel. Peut-être une pour un autre post.
Changer l’Icône et le Titre de l’Application dans le Menu
Si vous souhaitez changer l’icône de votre application, vous devez fournir un certain nombre de variantes de votre logo, toutes à des résolutions différentes pour iOS et Android séparément. Les deux ont également des exigences séparées (dont certaines vous ignorerez pour empêcher votre application d’être approuvée), donc cela devient rapidement un travail fastidieux.
Heureusement, il existe un package Dart qui fait tout le travail difficile. Étant donné une image source de votre icône d’application, il peut générer toutes les permutations requises pour les deux plateformes.
Pour cette application de démonstration, j’ai simplement pris une icône d’application au hasard sur Google Images :
Source : Google Images
En suivant le readme, je me suis dirigé vers la définition de ce jeu minimal de configuration pour générer des icônes avec succès. Placez ceci au bas de votre fichier pubspec.yaml
:
flutter_icons:
android: true
ios: true
remove_alpha_ios: true
image_path: "assets/app-icon.png"
Avec ce qui précède en place, exécutez cette commande pour générer les variantes d’icônes nécessaires pour iOS et Android :
flutter pub run flutter_launcher_icons
Vous devriez voir un ensemble d’icônes générées pour Android et iOS respectivement dans android/app/src/main/res/mipmap-hdpi/
et ios/Runner/Assets.xcassets/AppIcon.appiconset/
.
Malheureusement, changer le nom de l’application semble encore être un processus manuel. En me basant sur un article intitulé “How to Change App Name in Flutter—The Right Way in 2023” sur Flutter Beads, j’ai modifié le nom de l’application dans les deux fichiers suivants pour iOS et Android respectivement:
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">
Cela vous donne une jolie petite icône d’application et un titre maintenant:
Résumé
Donc, cela conclut comment se lancer avec Flutter et AWS Amplify, et j’espère démontrer à quelle vitesse il est possible de déployer les ressources et le code de base nécessaires pour prototyper rapidement une application mobile multiplateforme.
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!
Problèmes Rencontrés
Outils de ligne de commande Android manquants
L’emplacement de mon Android SDK Manager est le suivant:
/Users/benfoster/Library/Android/sdk/tools/bin/sdkmanager
L’installation des outils de ligne de commande Android a été faite en suivant ceci : Stack Overflow “Failed to Install Android SDK Java Lang Noclassdeffounderror JavaX XML Bind A.”
flutter doctor --android-licenses
L’application reste connectée sur iOS
Pendant le développement, je souhaitais répéter le processus de connexion à l’application. Malheureusement (pour moi), l’application conservait les informations de l’utilisateur entre les fermetures de l’application—la fermeture et la réouverture de l’application la gardaient connectée.
Mon expérience précédente avec le développement Android et Amplify m’avait convaincu que supprimer l’application et relancer « flutter run » supprimerait l’état utilisateur et recommencerait à zéro. Malheureusement, même cela n’a pas eu l’effet souhaité, alors j’ai fini par effacer le téléphone chaque fois que j’avais besoin de repartir sur une page blanche :
Source:
https://dzone.com/articles/cross-platform-mobile-app-prototyping-with-flutter-and-aws-amplify