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.
이 튜토리얼을 따라하면 1시간 이내로 완료할 수 있을 것입니다. 물론 저는 다양한 문제들과 싸워 몇 시간이 걸렸지만, 이러한 문제들을 잘 문서화해두었기 때문에 여러분이 맞닥뜨릴 일은 없으리라 희망합니다.
여기 완성된 결과물입니다. 제가 미리 만들어둔 버전을 원하신다면, 리드미의 단계를 따르시면 약 15분 안에 실행할 수 있을 것입니다. 여기에 GitHub 링크가 있습니다.
이 튜토리얼은 다음과 같은 다섯 부분으로 구성되어 있습니다:
- 필수 조건 및 코드베이스 설정
- 인증 추가
- 프로필 사진 업로드
- 사용자 정보 저장
- 디자인 감각 더하기
추천
Flutter는 몇 년 동안 사용되어 왔고, 활발한 커뮤니티와 많은 플러그인 및 확장 기능을 가지고 있어 대부분의 작업을 수행할 수 있는 매우 성숙한 플랫폼입니다.
Amplify 또한 강력한 플랫폼이지만, API 기능이 작업하기 어려웠으며 Flutter 라이브러리들이 Amplify의 최신 발표와 기능들과 함께 업데이트되지 않은 것으로 파악되었습니다. 특히 AppSync GraphQL과 DataStore(오프라인 데이터 저장 및 동기화를 위한)은 상당히 연약했습니다(나중에 보게 될 것입니다).
이 둘을 결합하면 모바일 앱 프로토타입 개발을 가속화하는 데 탁월한 조합이지만, Amplify를 자신의 뜻대로 구부리고 있다고 느낄 때는 AWS 서비스를 직접 작업하는 것을 대신해보세요.
제가 만든 데모 앱은 사용자 프로필 정보를 보유하고 있습니다. 이는 많은 앱에서 일반적인 요구사항입니다. 계정을 생성하고 로그인하여 프로필 사진을 업로드하고 자신에 대한 세부 정보를 제출할 수 있습니다. 우리는 전체 스택에 대한 자세한 내용을 다룰 것입니다. Flutter와 Dart를 사용하여 앱 코드를 작성하고 DynamoDB와 같은 것들을 사용하여 필요한 모든 것의 전체적인 범위를 알아보겠습니다.
제1부: 사전 준비 사항 및 코드베이스 설정
이 튜토리얼은 다음이 이미 머신에 설정되어 있다고 가정합니다:
Code Editor/ IDE | I use VSCode as it has a good set of Flutter and Dart plugins that speed up development, such as auto loading of dependencies, Dart linting, and intellisense. You’re free to use whatever IDE works for you though |
AWS Account | Create an AWS account if you don’t already have one. Visit AWS’ official page for steps to create an AWS account.
All of what we’ll use today is part of the free tier, so it shouldn’t cost you anything to follow this tutorial. |
AWS CLI and AWS Amplify CLI | Install AWS and Amplify CLI tooling.
Make sure you have an up-to-date version of Node and NPM (this is what the CLIs use). Visit Node.Js’ official website to download the up-to-date version. If you need to run multiple versions of Node, I recommend using NVM to manage and switch between them. To install AWS CLI, visit AWS’ official page. |
XCode (for iOS) | If you don’t have access to a Mac, you can deploy EC2 instances running MacOS in AWS these days, which you can use when you need to build iOS artifacts.
Download Xcode through the Mac App Store. Follow the rest of the steps here to set it up ready for iOS Flutter development. |
Android Studio (for Android) | Follow the steps here to be ready for Android Flutter development. |
Flutter SDK | Follow these steps to get Flutter and its dependencies up and running (if you’re on a Mac that is, other guides are available for other OSes). |
Flutter와 Amplify는 초기 프로젝트 구조를 생성하는 스캐폴딩 도구를 제공합니다. 특정 순서대로 이러한 작업을 수행하는 것이 중요합니다. 그렇지 않으면 폴더 구조가 도구가 예상하는 것과 일치하지 않아 나중에 수정하기가 어려워질 것입니다.
Flutter를 먼저 사용하여 코드베이스 구조를 생성한 후 Amplify를 초기화하십시오.
I used the official Flutter getting started documentation to kick things off for my demo.
Flutter가 작동하는지 확인할 수 있는지 봅시다. 먼저, 정확하게 설치되어 있고 PATH에 추가되었는지 이중 확인하려면 flutter doctor
를 실행할 수 있습니다.
이것이 모바일 개발에 대한 첫 번째 시도라면, 여기서 해결해야 할 몇 가지 사항이 있습니다. 저의 경우:
- Android Studio (및 Android SDK CLI) 설치.
- XCode 및 CocoaPods 설치.
- Android 및 iOS 도구에 대한 약관에 동의.
앱 코드베이스 생성
모든 사전 준비가 완료되면 Flutter 스캐폴딩을 생성할 수 있습니다. 이를 통해 작업할 폴더를 생성하므로 부모 디렉토리에서 다음 명령을 실행하십시오.
flutter create flutterapp --platforms=android,ios
I’ve specified Android and iOS as target platforms to remove the unnecessary config for other platforms (e.g. web, Windows, Linux).
이 시점에서 앱 이름과 일치하지 않기를 원한다면 이때 생성된 최상위 디렉토리의 이름을 변경하실 수 있습니다. 저는 “flutterapp”에서 “flutter-amplify-tutorial”(제 깃 저장소 이름)으로 변경했습니다.
이 시점에서 Flutter는 우리를 위해 총 칠십삼개의 파일을 생성했습니다. 이것들이 무엇인지 살펴보겠습니다:
가장 많이 사용할 폴더는 ios/android
와 lib/
입니다. ios
및 android
폴더 안에는 각각 XCode와 Android Studio에서 열 수 있는 프로젝트 리소스가 있습니다. 이러한 프로젝트들은 플랫폼에 독립적인 Dart 코드와 대상 플랫폼 간의 상호 작용을 담당하며, 각 플랫폼에 대해 앱을 테스트하는 데 사용할 수 있습니다. 이제 iOS로 해보겠습니다:
iOS 설정
open -a Simulator
flutter run
제 Mac에서 최소한의 XCode 설정으로 이 작업을 시작해서 iPhone 14 Pro Max 시뮬레이터에 스캐폴드된 Flutter 앱이 실행되는 것까지 진행했습니다. 꽤 멋지네요.
다음을 보게 되면 기본 구조를 성공적으로 생성한 것을 축하드립니다.
또한 ios/Runner.xcodeproj
프로젝트를 XCode 내에서 열어 내용을 살펴보고 시뮬레이터 및 실제 기기에 대해 실행할 수도 있습니다.
Android 설정
Android는 약간 더 복잡합니다. Android Studio에서 에뮬레이터를 명시적으로 구성하기 전에는 실행할 수 없습니다. 먼저 Android Studio에서 android/flutterapp_android.iml
프로젝트를 열고, 에뮬레이터를 구성하고 앱을 테스트하기 위해 실행할 수 있습니다.
Android Studio에 몇 분 정도 시간을 주시면 Gradle과 앱을 실행하는 데 필요한 모든 의존성을 다운로드합니다. 이 진행 상황은 창 오른쪽 하단의 진행률 표시줄에서 추적할 수 있습니다.
Android Studio가 정지되면 AVD에 이미 구성된 시뮬레이션 디바이스가 있는 경우 창 오른쪽 상단의 재생 버튼을 누를 수 있습니다.
그리고 보세요, 안드로이드에서 같은 앱이 있습니다.
이는 새 Flutter 프로젝트를 생성할 때 제공되는 샘플 앱 코드를 데모하고 있습니다. 이 튜토리얼을 진행하면서 이 코드를 점차 우리 자신의 코드로 교체할 것입니다.
이 시점에서 git commit을 수행하는 것이 좋습니다. 이제 Flutter 개발을 위한 기초가 구축되었습니다. 이제 Flutter 코드를 조작하고 iOS와 Android에서 동시에 결과를 볼 수 있는 지점에 있습니다.
Flutter는 Android와 iOS 사이의 중간 언어로 Dart를 사용하며, 상호 작용하는 모든 코드는 lib/
폴더 내에 있습니다. main.dart
파일이 있어야 하며, 이곳에서 조작을 시작할 것입니다.
Amplify를 사용하여 새 앱 구성 및 배포
이제 모바일 앱 도구링이 작업할 준비가 되었으므로, 앱의 기능을 지원하기 위한 백엔드 인프라가 필요합니다.
AWS와 그 다양한 서비스를 사용하여 앱을 지원하겠지만, 모두 AWS Amplify 서비스를 통해 관리됩니다. 대부분은 우리에게 투명하게 처리되므로, 사용할 서비스에 대해 걱정하지 않고 배포하려는 기능에 초점을 맞출 것입니다.
우선, 코드 폴더 내에서 다음을 실행하세요:
amplify init
이 명령어는 프로젝트 내에서 AWS Amplify를 초기화합니다. 이전에 사용하지 않았다면, 여러 질문을 하게 됩니다. 이후 프로젝트에 협업하는 사람들이 이 명령어를 실행하면 Amplify 구성이 이미 준비된 로컬 환경을 설정합니다.
이는 구성 및 앱의 상태를 저장하기 위한 초기 AWS 리소스를 프로비저닝합니다. 특히 S3 버킷입니다.
위의 배포 진행률 바와 상태는 일부에게 익숙할 수 있습니다. 이는 CloudFormation이며, AWS CDK와 마찬가지로 Amplify는 필요한 모든 리소스를 프로비저닝하기 위해 CFN을 배후에서 사용합니다. CloudFormation 스택 콘솔을 열어 실제로 확인할 수 있습니다:
마지막으로 CLI가 완료되면 아래와 유사한 확인 메시지를 보게 되며, Amplify 콘솔에서 새로 배포된 앱을 볼 수 있습니다:
환경 관리
AWS Amplify는 “환경”의 개념을 가지고 있으며, 이는 애플리케이션 및 리소스의 격리된 배포를 의미합니다. 역사적으로 환경의 개념은 사용하는 생태계 내에서 만들어져야 했습니다(예: CloudFormation, CDK) 이름 규칙과 매개변수를 사용하여. Amplify에서는 이것이 기본 시민입니다. 변경 사항이 통과하는 패턴(예: Dev > QA > PreProd > Prod)을 허용하는 여러 환경을 가질 수 있고, 개발자 또는 특성 브랜치 당 환경을 제공합니다.
Amplify는 또한 Amplify 호스팅 추가를 사용하여 CI/CD 서비스를 구성 및 프로비저닝할 수 있으며, 앱에 통합하여 종단 간 개발 생태계를 제공합니다. 이는 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
라는 댄 파일을 추가합니다. 이 파일은 git에서 무시되는데, 이는 배포된 리소스와 관련된 민감한 자격 증명을 포함하고 있고 작업 중인 Amplify 환경과 자동으로 동기화되기 때문입니다. 자세한 내용은 여기에서 확인할 수 있습니다.
이 시점에서 우리는 Amplify를 설정하고 앱과 개발 환경을 생성하며 인증을 위한 Cognito를 구성했습니다. 따라가고 있다면 이때 git commit을 하는 것이 좋으므로, 필요한 경우 이 시점으로 되돌아갈 수 있습니다. Amplify는 이미 불필요한 모든 파일을 제외하는 .gitignore
파일을 생성해 줬을 것입니다.
이제 백엔드 인증 인프라를 구축했으므로 Flutter로 모바일 앱을 시작할 수 있습니다.
앱 내 사용자 인증
I’m following the steps outlined in the authentication for the AWS Amplify tutorial here.
이는 amplify_flutter
에 포함된 기본 인증 UI 화면 및 워크플로우를 사용하고 있습니다. 다음을 “dependencies” 아래에 추가하여 pubspec.yaml
파일 내에서 Amplify Flutter 종속성을 추가하십시오.
amplify_flutter: ^0.6.0
amplify_auth_cognito: ^0.6.0
Flutter 및 Dart 확장을 VSCode 내에서 사용하지 않거나(또는 VSCode를 사용하지 않는 경우) 이를 따라 flutter pub get
명령어를 실행해야 합니다. 만약 그렇다면, VSCode는 pubspec.yaml
파일을 저장할 때 자동으로 이 작업을 실행할 것입니다.
인증을 통합하는 빠른 시작 방법은 사전 제작된 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로 변경한 후 "hot reload"를 호출하세요.
// ("flutter run"을 실행한 콘솔에서 "r"를 누르거나,
// 플러터 IDE에서 "hot reload"로 변경 사항을 저장하십시오).
// 카운터가 0으로 재설정되지 않은 것을 알 수 있습니다; 애플리케이션
// 다시 시작되지 않았습니다.
primarySwatch: Colors.blue,
useMaterial3: true),
home: const MyHomePage(title: 'Flutter Amplify Quickstart'),
builder: Authenticator.builder(),
));
}
@override
void initState() {
super.initState();
_configureAmplify();
}
void _configureAmplify() async {
try {
await Amplify.addPlugin(AmplifyAuthCognito());
await Amplify.configure(amplifyconfig);
} on Exception catch (e) {
print('Error configuring Amplify: $e');
}
}
}
위젯이란 무엇인가?
Flutter에서 UI 레이아웃과 구성 요소를 구성하는 기본 빌딩 블록입니다. Flutter 레이아웃의 거의 모든 것이 위젯입니다—열, 스캐폴딩, 패딩 및 스타일링, 복잡한 구성 요소 등. Flutter 시작 문서의 예제에서는 “Center” 위젯 다음에 “Text” 위젯을 사용하여 “Hello World”라는 텍스트를 중앙에 정렬하여 표시합니다.
위의 코드는 MyHomePage
위젯에 인증자 위젯을 장식하고, AmplifyAuthCognito
플러그인을 추가하며, 이전 amplify add auth
명령이 lib/amplifyconfiguration.dart
에서 생성한 구성을 사용하여 AWS Cognito 사용자 풀에 자동으로 연결합니다.
Flutter을 실행한 후 인증 통합을 데모하기 위해 “Running pod install” 단계가 완료되기까지 시간이 좀 걸렸습니다. 인내심을 가지십시오(거의 5분).
인증 변경 사항이 수정되고 앱이 시작되면 기본적이지만 기능적인 로그인 화면이 표시됩니다.
“Create Account” 흐름을 사용하여 전화번호와 비밀번호를 제공하면 등록을 완료하기 위한 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#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
플러터는 최소한 API 16을 사용해야 합니다 (이는 flutter.gradle
에 선언되어 있음). 하지만 Amplify Auth 라이브러리는 최소한 21이 필요합니다. 이를 수정하려면 minSdkVersion
을 “flutter.minSdkVersion”에서 “21”로 변경하면 됩니다.
인증한 후에는 이전에 보았던 샘플 “버튼 클릭” 앱이 표시됩니다. 이제 우리의 요구에 맞게 맞춤화를 시작할 시간입니다.
세 번째 부분: 프로필 사진 업로드
이 예시에서는 이 기능을 사용하여 사용자가 앱 내에서 자신의 아바타로 사용할 자신의 사진을 업로드할 수 있게 합니다.
앱에 스토리지 기능을 추가하고 싶으신가요? 문제 없습니다. 다음과 같이 하면 됩니다:
amplify add storage
그리고 Amplify가 앱이 클라우드 기반 스토리지를 사용하는 데 필요한 백엔드 서비스를 프로비저닝합니다. Amplify는 Flutter를 S3와 쉽게 통합하여 앱 사용자가 객체를 저장할 수 있게 합니다. S3의 유연성을 통해 다양한 종류의 자산을 저장할 수 있으며, Cognito와 Amplify와 결합하면 사용자가 사진, 동영상, 파일 등을 저장할 수 있는 개인 영역을 쉽게 프로비저닝할 수 있습니다.
파일은 공개, 보호된 또는 개인 액세스로 저장될 수 있습니다:
Public | Read/Write/Delete by all users |
Protected | Creating Identify can Write and Delete, everyone else can Read |
Private | Read/Write/Delete only by Creating Identity |
우리의 프로필 사진의 경우, 사용자만이 아바타를 업데이트하고 삭제할 수 있지만 앱 내의 다른 사용자들은 그것을 볼 수 있도록 보호된 액세스로 생성합니다.
이곳에서 우리는 앱의 스타일링과 구조를 시작할 것입니다. Flutter는 모바일 앱 개발에서 일관된 모양과 느낌을 제공하기 위해 널리 사용되는 머티리얼 디자인 시스템과 긴밀하게 통합되어 있습니다. 이는 플랫폼 간에 호환되는 구성 요소 세트를 제공하며, 이 스타일을 재정의하여 브랜드에 맞는 특정 경험을 구축할 수 있습니다.
Flutter 시작 템플릿은 이미 MaterialApp 위젯을 사용하여 일부 위젯을 스캐폴딩합니다. 이전에 이것을 인증 위젯으로 꾸며 두었습니다. 이제 MaterialApp의 MyHomePage 자식 위젯을 확장하여 프로필 사진을 제공할 것입니다.
위젯을 트리 형태로 결합하여 “위젯 계층 구조(Widget Hierarchy)”라고 알려진 것을 구축합니다. 항상 최상위 위젯부터 시작합니다. 우리 앱에서는 초기 로그인을 처리하는 인증 래퍼 위젯입니다. Scaffold
는 레이아웃을 기반으로 하기에 좋은 위젯입니다: 일반적으로 머티리얼 앱의 최상위 위젯으로 사용되며; 플로팅 액션 버튼, 바텀 시트(추가 세부 정보를 스와이핑하기 위한), 앱 바 등과 같은 여러 플레이스홀더가 있습니다.
먼저 네트워크 URL을 가리키는 이미지 위젯을 추가합니다. 나중에 이것을 우리가 찍고 S3에 업로드한 것으로 교체할 것입니다. 다음 리소스를 사용하여 둥근 플레이스홀더가 있는 이미지를 추가했습니다.
- API Flutter
- Google Flutter
중첩된 컬럼 위젯의 자식 배열에 다음 컨테이너 위젯을 추가하십시오.
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
Container(
width: 200,
height: 200,
decoration: const BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
fit: BoxFit.fill),
),
)
],
이제 웹에서 이미지를 표시할 수 있습니다:
다음으로, 사용자가 자신의 기기에서 이미지를 선택하여 프로필 사진을 선택할 수 있도록 할 것입니다. 약간의 구글링을 통해 이미지 선택 세부 사항을 추상화하는 이 라이브러리를 발견했습니다:
- Google: “Pub Dev Packages Image Picker”
사용자에게 이미지를 선택하도록 요청하는 데 필요한 코드는 두 줄뿐입니다:
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
iOS의 경우, 사용자의 사진에 대한 액세스를 요청하기 위해 NSPhotoLibraryUsageDescription
키를 <project root>/ios/Runner/Info.plist
Xcode 구성 파일에 추가해야 합니다. 그렇지 않으면 앱이 충돌합니다.
이를 GestureDetector
위젯에 연결할 것입니다. 이 위젯은 탭을 수신할 때 사용자에게 프로필 사진용 이미지를 선택하도록 요청합니다:
ImageProvider? _image;
...
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
GestureDetector(
onTap: _selectNewProfilePicture,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: _image ?? _placeholderProfilePicture(), fit: BoxFit.fill),
),
),
)
...
]
void _selectNewProfilePicture() async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
var imageBytes = await image.readAsBytes();
setState(() {
_image = MemoryImage(imageBytes);
});
}
}
_placeholderProfilePicture() {
return const AssetImage("assets/profile-placeholder.png");
}
setState()
를 호출하여 Flutter에 전달된 Lambda 내부의 위젯 필드를 업데이트하고, 업데이트된 상태를 사용하여 build()
함수를 호출하고 위젯을 다시 그릴 수 있도록 합니다. 이 경우, 프로필 이미지가 채워지므로 이미지를 표시하는 컨테이너 위젯을 생성합니다. 널 아님 연산자 ??
는 사용자가 아직 이미지를 선택하지 않은 경우 기본 프로필 자리 표시자를 제공합니다.
또한 저장소에 프로필 자리 표시자 이미지를 추가하고 pubspec.yml
파일에 참조하여 빌드에 포함되도록 해야 합니다. 저장소의 이미지를 사용하고 다음을 pubspec.yml
파일에 추가하세요:
# 다음 섹션은 Flutter 패키지에 특정됩니다.
flutter:
...
# 애플리케이션에 에셋을 추가하려면 다음과 같은 에셋 섹션을 추가하세요:
assets:
- assets/profile-placeholder.png
이 시점에서 기기의 사진 갤러리에서 프로필 사진을 선택하고 앱에서 둥근 사진으로 표시할 수 있습니다. 그러나 이 사진은 어디에도 저장되지 않습니다. 앱이 닫히면 사라지고(다른 사용자가 당신의 사진을 볼 수도 없습니다).
다음으로 할 일은 클라우드 스토리지인 AWS S3에 연결하는 것입니다. 사용자가 기기 갤러리에서 사진을 선택할 때 이를 S3의 개인 영역에 업로드한 다음 이미지 위젯이 기기 대신 해당 위치에서 이미지를 가져오도록 할 것입니다.그 자체:
void _selectNewProfilePicture() async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
var imageBytes = await image.readAsBytes();
final UploadFileResult result = await Amplify.Storage.uploadFile(
local: File.fromUri(Uri.file(image.path)),
key: profilePictureKey,
onProgress: (progress) {
safePrint('Fraction completed: ${progress.getFractionCompleted()}');
},
options:
UploadFileOptions(accessLevel: StorageAccessLevel.protected));
setState(() {
_image = MemoryImage(imageBytes);
});
}
}
이제 사용자가 기기에서 사진을 선택할 때 앱은 이를 S3에 업로드하고 화면에 표시합니다.
다음으로, 앱이 시작될 때 사용자의 프로필 사진을 S3에서 다운로드하도록 할 것입니다.
@override
void initState() {
super.initState();
_retrieveProfilePicture();
}
void _retrieveProfilePicture() async {
final userFiles = await Amplify.Storage.list(
options: ListOptions(accessLevel: StorageAccessLevel.protected));
if (userFiles.items.any((element) => element.key == profilePictureKey)) {
final documentsDir = await getApplicationDocumentsDirectory();
final filepath = "${documentsDir.path}/ProfilePicture.jpg";
final file = File(filepath);
await Amplify.Storage.downloadFile(
key: profilePictureKey,
local: file,
options:
DownloadFileOptions(accessLevel: StorageAccessLevel.protected));
setState(() {
_image = FileImage(file);
});
} else {
setState(() {
_image = const AssetImage("assets/profile-placeholder.png");
});
}
}
다음으로 프로필 사진 로직을 재사용 가능한 컴포넌트로 리팩토링합니다. 위의 모든 로직을 포함하는 내 GitHub 저장소에서 완성된 컴포넌트를 볼 수 있습니다. 그런 다음 _MyHomePageStage
컴포넌트를 정리하고 새 위젯을 다음과 같이 계층 구조에 삽입할 수 있습니다.
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Text(
'User Profile',
style: Theme.of(context).textTheme.titleLarge,
)),
const ProfilePicture(),
TextField(...
프로필 사진에 대한 마무리로 로딩 스피너를 추가하여 사용자에게 무언가 진행 중임을 알리는 피드백을 제공합니다. 사진이 로딩되는 시기를 추적하기 위해 _isLoading
부울 필드를 사용하여 스피너 또는 사진이 표시되는지 여부를 전환합니다.
class _ProfilePictureState extends State<ProfilePicture> {
ImageProvider? _image;
bool _isLoading = true;
...
void _retrieveProfilePicture() async {
...
setState(() {
_image = FileImage(file);
_isLoading = false;
});
} else {
setState(() {
_image = const AssetImage("assets/profile-placeholder.png");
_isLoading = false;
});
}
}
void _selectNewProfilePicture() async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
_isLoading = true;
});
....
setState(() {
_image = MemoryImage(imageBytes);
_isLoading = false;
});
}
}
제4부: 사용자 세부 정보 저장 (간추린 버전)
좋아요, 이제 사용자, 인증, 프로필 사진을 포함한 모바일 앱 스켈레톤을 구축했습니다. 다음으로, 사용자 자격 증명을 활용하여 사용자에 대한 추가 정보를 검색할 수 있는 API를 만들 수 있는지 살펴보겠습니다.
보통은 “API가 필요하면 간단합니다:”라고 말하겠지만
amplify add api
여기서 대부분의 노력과 문제 해결이 집중되었습니다. 선택한 구성에 따라 Amplify와 Flutter 생태계 내에서 완전히 지원되지 않을 수 있기 때문입니다. 기본 데이터 스토어 및 모델을 사용하면 비효율적인 읽기 패턴이 발생할 수 있으며, 이는 비용이 많이 들고 느려질 수 있습니다.
Amplify는 AppSync에서 데이터와 상호 작용하기 위한 고수준 API를 제공하지만, 이 튜토리얼에서는 DynamoDB에서 테이블 검색을 피하기 위해 Global Secondary Index를 사용할 수 있도록 낮은 수준의 쿼리로 GraphQL을 사용하겠습니다. 어떻게 이런 상황이 왔는지, 어떤 위험이 있는지 이해하려면 “AWS Amplify 및 Appsync와 Flutter로 작업하고 튜닝하는 도전”을 확인하세요.
Amplify는 API를 생성할 때 묻는 질문을 기본값으로 설정하려고 하지만, 원하는 옵션으로 변경하려면 위로 스크롤하여 재정의할 수 있습니다. 이 시나리오에서는 DataStore를 활용하기 위해 GraphQL 엔드포인트를 원하며, API 인증은 미세한 액세스 제어를 적용하기 위해 Cognito User Pool에서 처리하려고 합니다. 즉, 사용자만 자신의 세부 정보를 업데이트하거나 삭제할 수 있지만(다른 사용자는 볼 수 있음).
API를 생성할 때 Amplify는 기본적인 ToDo GraphQL 스키마 유형을 생성합니다. 이를 업데이트하고 인증 규칙을 추가한 후 API 변경 사항을 푸시할 것입니다.
“ToDo” 템플릿 GraphQL 스키마를 사용자 프로필 정보에 맞게 수정합니다.
type UserProfile @model
@auth(rules: [
{ allow: private, operations: [read], provider: iam },
{ allow: owner, operations: [create, read, update, delete] }
])
{
userId: String! @index
name: String!
location: String
language: String
}
개인 규칙은 로그인한 사용자가 다른 사람의 프로필을 볼 수 있도록 합니다. 공개를 사용하지 않으면 로그인하지 않은 사용자가 프로필을 볼 수 없습니다. IAM 제공자는 사용자가 직접 GraphQL API에 액세스하지 못하게 합니다. 그들은 앱을 사용하고 우리의 Cognito 신원 풀 내에서 “인증되지 않음” 역할을 사용해야 합니다(즉, 로그아웃됨) 사용자 세부 정보를 볼 수 있습니다.
“소유자” 규칙은 프로필을 생성한 사용자가 자신의 프로필을 생성, 읽기 및 업데이트할 수 있도록 합니다. 이 예에서 우리는 그들이 자신의 프로필을 삭제하지 못하게 합니다.
이 시점에서 API 기능을 지원하는 클라우드 인프라를 프로비저닝할 수 있습니다:
amplify push
기존 GraphQL 모델을 ToDo에서 UserProfile로 변경할 때, 이전에 amplify push
를 수행하고 인프라를 프로비저닝한 경우 요청된 변경 사항이 기존 DynamoDB 테이블을 삭제해야 한다는 오류가 발생할 수 있습니다. Amplify는 기존 ToDo 테이블에서 데이터 손실을 방지하기 위해 이를 방지합니다. 이 오류가 발생하면 amplify push --allow-destructive-graphql-schema-updates
를 실행해야 합니다.
amplify push
를 수행할 때, Amplify와 CloudFormation은 다음과 유사한 AppSync GraphQL API, 중간 해결자 및 백엔드 DynamoDB 테이블을 구축합니다.

그래프QL 스키마를 정의한 후 Amplify를 사용하여 API와 함께 작동할 수 있는 모델 및 저장소 계층을 나타내는 Dart 코드를 생성할 수 있습니다.
amplify codegen models
이 시점에서 사용자의 이름, 위치 및 즐겨찾는 프로그래밍 언어를 입력할 수 있는 일부 입력 필드를 페이지에 추가할 수 있습니다.
이것이 텍스트 필드 변경 사항이 _MyHomePageState
컴포넌트에 나타나는 방식입니다.
class _MyHomePageState extends State<MyHomePage> {
final _nameController = TextEditingController();
final _locationController = TextEditingController();
final _languageController = TextEditingController();
@override
Widget build(BuildContext context) {
...
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Text(
'User Profile',
style: Theme.of(context).textTheme.titleLarge,
)),
const ProfilePicture(),
TextField(
decoration: const InputDecoration(labelText: "Name"),
controller: _nameController,
),
TextField(
decoration:
const InputDecoration(labelText: "Location"),
controller: _locationController,
),
TextField(
decoration: const InputDecoration(
labelText: "Favourite Language"),
controller: _languageController,
)
]
그런 다음 사용자가 “저장” 라는 라이트 액션 버튼을 누를 때 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 ?? "";
});
}
}
이제 Cognito로 보안되고 DynamoDB에 의해 지원되는 API를 통해 데이터를 저장할 수 있습니다. 인프라 코드를 작성하지 않아도 되니 꽤 괜찮은 것 같습니다.
이 시점에서 사용자 프로필 정보를 쿼리하고, 표시하고, 업데이트할 수 있는 수단이 있습니다. 또 다른 저장 지점이 생긴 것 같습니다.
5부: 디자인 감각 더하기
마지막으로 확장한 샘플 앱이 다소 평범해 보이네요. 약간의 생기를 불어넣을 시간입니다.
저는 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(
// 여기서 우리는 App.build 메서드에 의해 생성된 MyHomePage 객체에서 값을 가져와서 앱바 제목에 사용합니다.
// App.build 메서드에 의해 생성된 MyHomePage 객체에서 값을 가져와서 앱바 제목에 사용합니다.
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 패키지가 이러한 모든 노력을 대신 해줍니다. 앱 아이콘의 소스 이미지를 제공하면 iOS와 Android 모두에 필요한 모든 변형을 생성할 수 있습니다.
이 데모 앱의 경우, 구글 이미지에서 무작위 앱 아이콘을 가져왔습니다:
출처: 구글 이미지
README를 따라 아이콘을 성공적으로 생성하기 위한 이 최소한의 설정을 정의하게 되었습니다. 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/app/src/main/res/mipmap-hdpi/
및 ios/Runner/Assets.xcassets/AppIcon.appiconset/
에서 Android 및 iOS용 아이콘 파일 모음을 생성해야 합니다.
앱 이름을 변경하는 것은 아쉽게도 여전히 수동 작업입니다. Flutter Beads에서 “How to Change App Name in Flutter—The Right Way in 2023″라는 기사를 참조하여 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 “Failed to Install Android SDK Java Lang Noclassdeffounderror JavaX XML Bind A.”
flutter doctor --android-licenses
iOS에서 앱이 로그인된 상태로 유지됨
개발하면서 앱에 로그인하는 과정을 반복하고 싶었습니다. 불행히도(나에게는) 앱이 앱을 닫았을 때 사용자 정보를 유지하고 있었습니다. 앱을 닫고 다시 열어도 로그인 상태를 유지했습니다.
이전 Android 개발 및 Amplify 경험으로 인해 앱을 삭제하고 “flutter run”을 다시 실행하면 사용자 상태가 제거되고 새로 시작될 것이라고 확신했습니다. 하지만 이러한 방법도 원하는 효과를 얻을 수 없었으므로, 깨끗한 시작을 위해 필요할 때마다 전화기를 삭제하게 되었습니다:
Source:
https://dzone.com/articles/cross-platform-mobile-app-prototyping-with-flutter-and-aws-amplify