AWS 시작하기

Flutter 애플리케이션 구축

AWS Amplify를 사용하여 간단한 Flutter 애플리케이션 생성

모듈 4: 이미지 저장 기능 추가

이 모듈에서는 앱에서 이미지를 메모에 연결하는 기능과 스토리지를 추가합니다.

소개

이제 인증된 사용자만 앱에 들어갈 수 있으므로 사용자가 사진을 촬영하여 앱의 Amazon S3 버킷의 프라이빗 폴더에 업로드할 수 있습니다.

이 모듈에서는 Amplify 앱에 스토리지 범주를 추가하고 디바이스 카메라에서 찍은 사진을 업로드한 다음 개별 사용자와 관련된 모든 사진을 다운로드하여 표시합니다.

배우게 될 내용

  • 스토리지 범주 구성
  • Amazon S3에 파일 업로드
  • Amazon S3에서 파일 다운로드
  • URL에서 이미지 표시 및 캐싱

주요 개념

스토리지 - 스토리지의 개념은 파일을 위치에 저장하고 필요할 때 해당 파일을 검색하는 것입니다. 이 예에서는 이미지를 Amazon S3에 저장하고 이미지를 다운로드합니다.

 완료 시간

20분

 사용되는 서비스

구현

  • 스토리지 서비스 생성

    터미널을 통해 프로젝트의 루트 디렉터리에서 다음 명령을 입력하여 스토리지 서비스를 Amplify 프로젝트에 추가합니다.

    amplify add storage

    다른 범주와 마찬가지로 Amplify CLI가 스토리지 서비스를 구성하는 방법을 묻는 메시지를 표시합니다. Enter 키를 눌러 대부분의 질문에 기본 대답으로 답합니다.

    ➜  photo_gallery git:(master) amplify add storage
    ? Please select from one of the below mentioned services: Content (Images, audio
    , video, etc.)
    
    ? Please provide a friendly name for your resource that will be used to label th
    is category in the project: s33daafe54
    ? Please provide bucket name: photogalleryf3fb7bda3f5d47939322aa3899275aab
    ? Who should have access: Auth users only

    인증된 사용자에게 어떤 액세스 권한을 부여할지 묻는 메시지가 표시되면 해당 키를 눌러 생성/업데이트, 읽기 및 삭제를 선택합니다.

    ? What kind of access do you want for Authenticated users? create/update, read,
    delete

    그런 다음 스토리지 리소스가 완전히 구성될 때까지 기본 답변을 계속 입력합니다.

    ? Do you want to add a Lambda Trigger for your S3 Bucket? No
    Successfully added resource s33daafe54 locally

    이제 동기화를 유지할 수 있도록 구성된 스토리지 리소스를 백업된 리소스로 보내야 합니다. 다음 명령을 실행합니다.

    amplify push

    Amplify CLI가 변경 중인 사항에 대한 상태 보고서를 제공합니다.

    ✔ Successfully pulled backend environment dev from the cloud.
    
    Current Environment: dev
    
    | Category | Resource name        | Operation | Provider plugin   |
    | -------- | -------------------- | --------- | ----------------- |
    | Storage  | s33daafe54           | Create    | awscloudformation |
    | Auth     | photogallery42b5391b | No Change | awscloudformation |
    ? Are you sure you want to continue? (y/n)

    스토리지 범주를 생성 중이며 이전 모듈의 설정에서 Auth가 변경되지 않았음을 보여줍니다.

    백엔드에서 스토리지 리소스 구성이 완료되면 다음과 같은 성공 결과를 볼 수 있습니다.

    ✔ All resources are updated in the cloud
  • 종속 구성 요소 설치

    그런 다음 Visual Studio 코드에서 pubspec.yaml 파일을 열어 Storage 플러그인을 종속 구성 요소로 추가합니다.

    ... # amplify_auth_cognito: '<1.0.0'
    
    amplify_storage_s3: '<1.0.0'
    
    ... # dev_dependencies:

    이제 파일을 저장하여 Visual Studio 코드가 Amplify Auth Cognito 플러그인을 설치하도록 합니다. 저장 시에 종속 구성 요소가 설치되어 있지 않은 경우 터미널에서 $ flutter pub get을 실행할 수도 있습니다.

    다음과 같은 출력이 표시됩니다.

    exit code 0
  • 플러그인 구성

    이제 Main.dart로 돌아가 Amplify 인스턴스의 플러그인으로 스토리지를 추가할 수 있습니다.

    ... // void _configureAmplify() async {
    
    _amplify.addPlugin(
        authPlugins: [AmplifyAuthCognito()],
        storagePlugins: [AmplifyStorageS3()]);
    
    ... // try {

    앱을 실행합니다. Amplify가 여전히 올바르게 구성되어 있으며 Storage 플러그인이 포함되어 있음을 나타내는 성공 메시지가 로그에 계속 표시됩니다.

    flutter: Successfully configured Amplify 🎉
  • 기능 구현

    코드를 정리하기 위해 파일 업로드 및 다운로드 로직을 캡슐화할 storage_service.dart라는 별도의 파일을 생성해 보겠습니다. 다음 코드를 추가합니다.

    import 'dart:async';
    import 'package:amplify_core/amplify_core.dart';
    import 'package:amplify_storage_s3/amplify_storage_s3.dart';
    
    class StorageService {
      // 1
      final imageUrlsController = StreamController<List<String>>();
    
      // 2
      void getImages() async {
        try {
          // 3
          final listOptions =
              S3ListOptions(accessLevel: StorageAccessLevel.private);
          // 4
          final result = await Amplify.Storage.list(options: listOptions);
    
          // 5
          final getUrlOptions =
              GetUrlOptions(accessLevel: StorageAccessLevel.private);
          // 6
          final List<String> imageUrls =
              await Future.wait(result.items.map((item) async {
            final urlResult =
                await Amplify.Storage.getUrl(key: item.key, options: getUrlOptions);
            return urlResult.url;
          }));
    
          // 7
          imageUrlsController.add(imageUrls);
        
        // 8
        } catch (e) {
          print('Storage List error - $e');
        }
      }
    }
    1. 먼저 StreamController를 초기화하여 Amazon S3에서 검색된 이미지 URL을 관리합니다.
    2. 이 기능은 GalleryPage에 표시해야 하는 이미지를 가져오는 프로세스를 시작합니다.
    3. 사용자가 업로드한 사진만 표시하기 때문에 액세스 수준을 StorageAccessLevel.private로 지정하여 사용자의 개인 사진을 비공개로 유지합니다.
    4. 다음으로 S3ListOptions을 지정하여 모든 관련 사진을 나열하도록 Storage에 요청합니다.
    5. 목록 결과가 성공적이면 목록 결과에는 사진의 실제 URL이 아닌 키 목록만 포함되어 있기 때문에 각 사진의 실제 다운로드 URL을 가져와야 합니다.
    6. .map을 사용하여 목록 결과에서 각 항목마다 반복하고 각 항목의 다운로드 URL을 비동기식으로 반환합니다.
    7. 마지막으로, 관찰할 스트림으로 URL 목록을 전송하기만 하면 됩니다.
    8. 도중에 오류가 발생하면 인쇄하기만 하면 됩니다.

    iOS에서는 앱이 이미지를 다운로드할 수 있도록 Info.plist(ios > Runner > Info.plist)에서 App Transport Security를 업데이트해야 합니다.

    ... <!-- <string>Need to take pictures</string> -->
    
    <key>NSAppTransportSecurity</key>
    <dict>
       <key>NSAllowsArbitraryLoads</key>
       <true/>
    </dict>
    
    ... <!-- </dict> -->

    아무 것도 업로드하지 않는다면 S3에서 이미지 URL을 나열하는 것이 무의미하므로 이미지를 업로드하는 함수를 추가해 보겠습니다.

    // 1
    void uploadImageAtPath(String imagePath) async {
     final imageFile = File(imagePath);
     // 2
     final imageKey = '${DateTime.now().millisecondsSinceEpoch}.jpg';
    
     try {
       // 3
       final options = S3UploadFileOptions(
           accessLevel: StorageAccessLevel.private);
    
       // 4
       await Amplify.Storage.uploadFile(
           local: imageFile, key: imageKey, options: options);
    
       // 5
       getImages();
     } catch (e) {
       print('upload error - $e');
     }
    }
    1. 해당 함수는 카메라가 제공하는 이미지 경로를 받는 비동기식 함수입니다.
    2. 사진에 고유한 키가 부여되도록 하기 위해 타임스탬프를 키로 사용합니다.
    3. getImages를 구현할 때 설명했듯이, 액세스 수준을 StorageAccessLevel.private로 지정해야 사용자가 S3 버킷의 개인 폴더에 이미지를 업로드할 수 있습니다.
    4. 그런 다음 키를 지정하는 파일을 업로드하고 파일 옵션을 업로드하기만 하면 됩니다.
      마지막으로, getImages를 호출하여 최신 이미지 URL 목록을 받아 스트림으로 전송합니다.
    5. 업로드와 다운로드가 작동하는 데 필요한 모든 코딩을 완료했습니다. 이제 모두 연결하고 테스트해야 합니다.

    먼저 StreamController를 인수로 사용하도록 GalleryPage를 업데이트하여 Storage에서 검색된 이미지 URL을 관찰할 수 있게 하겠습니다.

    ... // class GalleryPage extends StatelessWidget {
    
    final StreamController<List<String>> imageUrlsController;
    
    ... // final VoidCallback shouldLogOut;
    ... // final VoidCallback shouldShowCamera;
    
    GalleryPage(
       {Key key,
       this.imageUrlsController,
       this.shouldLogOut,
       this.shouldShowCamera})
        : super(key: key);
    
    ... // @override

    다음으로, 단순히 GridView.builder를 반환하는 것이 아니라 StreamBuilder를 반환하도록 _galleryGrid를 업데이트하여 합니다.

    Widget _galleryGrid() {
     return StreamBuilder(
         // 1
         stream: imageUrlsController.stream,
         builder: (context, snapshot) {
           // 2
           if (snapshot.hasData) {
             // 3
             if (snapshot.data.length != 0) {
               return GridView.builder(
                   gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                       crossAxisCount: 2),
                   // 4
                   itemCount: snapshot.data.length,
                   itemBuilder: (context, index) {
                     return CachedNetworkImage(
                       imageUrl: snapshot.data[index],
                       fit: BoxFit.cover,
                       placeholder: (context, url) => Container(
                           alignment: Alignment.center,
                           child: CircularProgressIndicator()),
                     );
                   });
             } else {
               // 5
               return Center(child: Text('No images to display.'));
             }
           } else {
             // 6
             return Center(child: CircularProgressIndicator());
           }
         });
    }
    1. StreamBuilder는 StorageService에서 전달되는 imageUrlsController를 사용하여 데이터의 스냅샷을 제공합니다.
    2. UI를 사용하려면 스냅샷에 사용자와 관련하여 표시할 데이터가 있어야 합니다.
    3. 데이터에 실제로 항목이 있는지 여부도 확인해야 합니다. 항목이 있는 경우 계속해서 GridView를 빌드합니다.
    4. 이제 하드 코딩된 숫자를 사용하는 대신 스냅샷의 데이터 길이를 기준으로 GridView 크기를 조정할 수 있습니다.
    5. 스냅샷에 항목이 없는 경우 표시할 항목이 없음을 나타내는 텍스트를 표시합니다.

    지금도 그리드의 각 항목에 대한 자리 표시자가 표시됩니다. 스트림에서 제공된 URL에서 각 이미지를 다운로드해야 합니다. 이를 쉽게 하기 위해 pubspec.yaml에 새로운 종속 구성 요소를 추가합니다.

    ... # amplify_storage_s3: '<1.0.0'
    
    cached_network_image: ^2.3.3
    
    ... # dev_dependencies:

    이 라이브러리는 URL을 받아 이미지를 다운로드하고 캐시할 수 있는 위젯을 제공합니다. 변경 내용을 저장하고 GalleryPage로 이동하여 자리 표시자를 바꿀 수 있습니다.

    ... // itemBuilder: (context, index) {
    
    return CachedNetworkImage(
      imageUrl: snapshot.data[index],
      fit: BoxFit.cover,
      placeholder: (context, url) => Container(
          alignment: Alignment.center,
          child: CircularProgressIndicator()),
    );
    
    ... // itemBuilder closing });

    자리 표시자가 스냅샷의 URL을 전달하고 itemBuilder를 통해 인덱싱되는 CachedNetworkImage로 대체되었습니다. 이미지가 로드되는 동안 위젯에 CircularProgressIndicator가 표시됩니다.

    이제 GalleryPage 및 CameraPage를 _CameraFlowState의 StorageService에 연결할 수 있습니다. 먼저 StorageService 인스턴스를 저장할 속성을 생성합니다.

    ... // bool _shouldShowCamera = false;
    
    StorageService _storageService;
    
    ... // List<MaterialPage> get _pages {

    다음으로 initState 메서드에서 _storageService를 초기화합니다.

    ... // _getCamera();
    
    _storageService = StorageService();
    _storageService.getImages();
    
    ... // initState closing }

    StorageService를 초기화한 후 즉시 getImages를 호출하여 업로드된 이미지를 검색합니다.

    StreamController를 GalleryPage로 전달하겠습니다.

    ... // child: GalleryPage(
        
    imageUrlsController: _storageService.imageUrlsController,
    
    ... // shouldLogOut: widget.shouldLogOut,

    마지막으로, 사진 촬영 후 CameraPage의 didProvideImagePath 기능을 업데이트합니다.

    ... // this._toggleCameraOpen(false);
    
    this._storageService.uploadImageAtPath(imagePath);
    
    ... // didProvideImagePath closing }

    정말 간단합니다! 앱으로 사진을 찍어 S3에 업로드할 준비가 되었습니다.

    앱을 직접 구축하고 실행해보세요!

    EndofModule4-gif

결론

훌륭합니다! 앱의 핵심 기능이 구현되었으며 사용자가 앱의 S3 버킷의 프라이빗 섹션에 저장할 사진을 찍을 수 있습니다.

다음 모듈에서는 분석을 추가합니다.

이 모듈이 유용했습니까?

감사합니다.
좋아하는 사항을 알려주세요.
닫기
실망을 드려 죄송합니다.
오래되었거나 혼란스럽거나 부정확한 사항이 있습니까? 피드백을 제공하여 이 자습서를 개선할 수 있도록 도와주십시오.
닫기

분석 추가