FlutterとAWS Amplifyを使ってモバイルアプリを作ってみた

はじめに

こんにちは。株式会社divxのエンジニア中尾です。

divxに入社してからAWSの学習を進める中で、AWSサービスの仕組みや概要がわかってきた気がしています。

自前でサーバーを構築する手間と初期投資費用を気にしすぎることがなくなったり、セキュリティー面を高めて安心安全なWebサービスを展開したり、容量無制限のストレージを利用できたり、世界中のユーザーに向けてコンテンツを高速配信できたり… アプリケーションを開発する中で「より高速に開発作業を行いたい・少ない手間でサービスを拡張させたい・運用に関わるコストを削減したい」など、さまざまな要求が出てきますよね?

そのほとんどの要求課題をAWSサービスを活用することでカバーできるようです。

私自身、今までAWSのペーパーによる学習は進めていましたが、AWSサービスを活用して実際どのように開発を行うことができるのだろう?と考えながら興味を膨らませていました。

今回は、そんなAWSサービスの中でもモバイルアプリ開発をより高速に行うことができるAWS AmplifyとGoogle社が開発したモバイルアプリ開発フレームワークのFlutterを使用し、簡単なモバイルアプリケーション開発を実践しながらAWSサービスの理解を深めていきたいと思います。

開発実践の前に、今回使用する技術の概要を確認しておきましょう。

AWS Amplifyの概要

引用元:https://aws.amazon.com/jp/blogs/startup/techblog-3reasons-amplify/

特徴

Amplifyを使うことで、フロントエンドアプリケーションとさまざまなAWSサービスとの連携を簡単に実現できます。たとえば、Amazon Cognitoによる認証機能やAmazon PinpointとAmazon Kinesisによる分析機能などユースケースに応じて実装が可能となります。 また、イレブンナインの耐久性を持つS3と世界中にコンテンツ配信を行うことができるCloudFrontも自動的にセットアップされることも特徴です。

Reactなどのようなモダンフレームワークに対応しており、開発するアプリの要件に合うものを柔軟に選択できます。また、各フレームワークの利用を始める際のクイックスタートや開発時に必要なAPIリファレンスなど、ドキュメントも整備されており開発スピードを加速させることができます。

AWSがOSSとしてGitHubで公開しており、世界中の開発者と協力して最適化したり、拡張したりなど自由にカスタマイズすることも可能です。

Dartの概要

ウェブアプリやモバイルアプリケーションのクライアント開発向けにGoogle社によって設計されており、2011年に初版が公開されたプログラミング言語です。UI作成に最適化され、生産的な開発が可能です。また、すべてのプラットフォームで高速処理を行えるというメリットがあります。マルチプラットフォームフレームワークであるFlutterで使用されていることで有名です。

特徴

  • 実行速度が速い。
  • 編集の結果をすばやく確認できる。
  • AOTとJITの両方をコンパイルするのに適した言語の1つ。
  • DartはネイティブコードまたはJavaScriptにコンパイルされる。
  • null safety機能があり、nullを許容する場合は明示的に許容を宣言する必要がある。

    null safety機能参考:https://dart.dev/null-safety

引用元:https://dart.dev/overview

用語補足

AOTコンパイラ(Ahead-Of-Time Compiler)

ソースコードのままでは人間には読み書きしやすいですが、コンピューターが解読して実行できないため、機械語にコンパイルし実行ファイルを作る必要があります。プログラムの実行前にその作業を行うのがAOTコンパイラです。

JITコンパイラ(Just-In-Time Compiler)

一度にコンパイルする範囲を限定し、実行しようとしている箇所のコードをプログラムの実行中にコンパイルする方式です。この方式では、機械語のオブジェクトコードをメモリに保存する関係でメモリの消費は増えます。しかしながら、保存しておいたオブジェクトコードがあることで、通常のコンパイル方式と同等の速度で実行できる利点もあります。

Flutterの概要

“Flutter is an open source framework by Google for building beautiful, natively compiled, multi-platform applications from a single codebase.”

引用元:https://flutter.dev/

1つのコードから美しいネイティブコンパイルされた、マルチプラットフォームアプリケーションを構築するためのGoogleによるオープンソースフレームワークです。

歴史

従来のAndroidアプリケーションは、通常Javaで開発されていました。しかし、それまでのフレームレート60FPSから倍の120FPSという、より高速かつWebとの緊密な連携が可能なフレームワークを目指してFlutterは開発されました。 2015年にSkyプロジェクトという名で実験的に披露され、2018年にFlutter1.0が正式版としてリリースされています。2022年6月14日現在の最新安定版は、Flutter3.0.2となっています。

引用元:https://docs.flutter.dev/development/tools/sdk/releases?tab=macos

特徴

  • Flutterコードは、JavaScriptだけでなくARMまたはIntelマシンコードにもコンパイルされるため、あらゆるデバイスで高速なパフォーマンスを実現可能。(Fast)

  • Hot Reload機能を備えており、アプリの再起動を行うことなく、コード変更をUIに反映させることができる。(Productive)

  • さまざまなデバイスで、どの画面でも美しくカスタマイズされたデザインが作成される。(Flexible)

  • Android・macOS・Webの開発がシングルコードベースで可能なマルチプラットフォーム

  • WidgetというUIの構成パーツを用いてUIを作成していく。

  • クリーンアーキテクチャを採用している。

    クリーンアーキテクチャとは

    引用元:https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

同心円状のアーキテクチャであり、それぞれの円は以下の考え方で表されています。

エンティティ

ビジネスルールをロジックとして表しています。 他のレイヤーの変更が影響しない部分となっており、アプリケーションの肝となります。

ユースケース

アプリケーション固有のビジネスロジックが含まれている部分です。 エンティティのロジックを強調し、そのソフトウェアが何をできるのかを表しています。

インターフェイスアダプター

データの入出力・保存を担当する部分です。 入力されたデータをユースケースやエンティティで使用される形のデータに変換する機能も有しています。

フレームワークとドライバー

データベースやウェブフレームワークなどを表す部分です。ひとつ内側の円と通信するつなぎのコードも含まれます。 ウェブやデータベースは実体の詳細部分であるとの考えから、これらが悪影響を及ぼさないようにレイヤーの一番外側に置かれます。

実際に作ってみよう!

Todoアプリ開発

今回の実践ではAWS Amplifyと、Flutterで開発したシンプルなTodoアプリを連携させる手順を確認してきたいと思います。

Flutter環境構築

Flutterでアプリケーションを開発するための環境構築を行ってみたいと思います。 以下、Flutterの最新版を使って環境構築する際の手順をみていきます。

Flutterのサイトにアクセスし、右上のGet startedからご自身のOSと合致したFlutterをインストールします。

Flutterサイトはこちら

今回、私はmacOSを使用して開発するので、macOSを選択します。

FlutterのSDKをインストールします。インストール時点で最新の安定版が表示されています。

ダウンロードしたFlutterのSDKを解凍し、Homeディレクトリへ移動しておきます。

 $ cd ~/development
 $ unzip ~/Downloads/flutter_macos_3.0.2-stable.zip

Flutterを使用できるようにするためパスを通していきます。

$ ~ % ls
Applications    Desktop     Documents   Downloads   Library     Movies      Music       Pictures    Public      VirtualBox VMs  flutter     sandbox

$ ~ % cd flutter //Flutterのディレクトリに移動

$ flutter % ls
AUTHORS         CONTRIBUTING.md     README.md       bin         examples        packages
CODEOWNERS      LICENSE         TESTOWNERS      dartdoc_options.yaml    flutter_console.bat version
CODE_OF_CONDUCT.md  PATENT_GRANT        analysis_options.yaml   dev         flutter_root.iml

$ flutter % cd bin //binディレクトリへ移動
$ bin % pwd        //binディレクトリに移動し、pwdでパスを確認
/Users/divx/flutter/bin  //ここのパスをコピーしておく

% echo $SHELL   //自身のシェルを確認する
/bin/zsh

% echo export PATH="$PATH:hogehoge" >> ~/.zshrc   //hogehogeの箇所に、コピーしたパスを入力。
$ ~ % echo export PATH="$PATH:/Users/divx/flutter/bin" >> ~/.zshrc   //パスを通す

//ターミナルを「command + q」で一旦終了し、再起動する。
$ ~ % flutter   //flutterが認識されればok

$ echo $PATH   //flutter/binディレクトリがPATHに含まれていることを確認
$ ~ % echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/divx/flutter/bin

$ which flutter   //パスが表示されればOK

$ ~ % which flutter
/Users/divx/flutter/bin/flutter
//開発に必要な準備できているかを確認できるコマンド
flutter doctor

チェックがつかなかった箇所への対応

①Android Studioのインストール

公式サイトはこちら

Android StudioをApplicationsにドラッグアンドドロップします。

Android Studioを使用できるようにするためのセットアップを行います。

ライセンス同意を求められるので、同意してコンポーネントをダウンロードします。

「Welcome to Android Studio」が表示された状態で、flutter doctor を実行して確認します。

Android StudioでSDK Manager を開きます。

Android SDK > SDK Toolsの画面で、最下部の「Hide Obsolete Packages」のチェックを外すと「Android SDK tools」が表示されます。

Android SDK Toolsをダウンロードします。

Android SDK Command-line Toolsをインストールします。

flutter doctor --android-licenses を実行して、Android toolchainのライセンスを承諾します。

続いて、Xcodeをインストールしていきます。(インストールは、けっこう時間がかかるかもしれません…)

Apple store で、Xcodeをインストールします。(macOSの場合、バージョン12以上が必要なため、必要に応じてOSのアップグレードを行なってください。)

Xcodeインストール後、flutter doctor にて確認するとCocoa Podsのインストールを行うように指示が出ていますので実施していきます。

ライブラリ管理ツールのCocoa Podsのインストール

公式サイトはこちら

//インストールコマンド
brew install cocoapods

flutter doctorで確認します。

Xcodeインストール後、Xcodeチェック項目内に出ている実行コードを入力します。

再度、flutter doctorを実行し、正常に終了すると画面のような状態となります。

Android StudioにFlutterをインストールしていきます。

AndroidStudioで、New Flutter Projectが作成できるようになっていればokです。

今回はVSCodeで開発を行うので、VSCodeでも同様にFlutterをインストールして環境構築は終了です。

いよいよプロジェクトの作成に進みます。

Flutterプロジェクトの作成

今回はVSCodeをエディタとして使用しますので、以下の流れで新規プロジェクトを作成していきます。

VSCode起動 >> コマンドパレット >> Flutter:New Project >> Application の順で新規プロジェクトを作成します。

デフォルトのサンプルアプリが作成され、起動できる状態となります。

今回は、このデフォルトアプリを改修する形で新規アプリケーションを作成していきます。

//デフォルトアプリコード

main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

デフォルトアプリ画面

Amplifyのインストールと初期化

Amplify CLIとは?

AWSでサーバーレスなバックエンドを構築・管理するためのCLIツールです。コマンドを実行して対話的に質問に回答するだけで、サーバーレスなバックエンドを構築することができます。

「やりたいこと」から機能を構築することができるようになります。

たとえば認証機能を追加する場合は、

amplify add auth

とするだけでAWSの各サービスを組み合わせて認証機能のバックエンドを構築することができます。

Amplify CLI デベロッパープレビューをインストールする

npm install -g @aws-amplify/cli@flutter-preview
(base) $ todo_lesson % npm install -g @aws-amplify/cli@flutter-preview
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: amplify-codegen@2.28.1
npm WARN Found: amplify-cli-core@1.30.0
npm WARN node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation/node_modules/amplify-cli-core
npm WARN   amplify-cli-core@"1.30.0" from amplify-provider-awscloudformation@4.61.1
npm WARN   node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation
npm WARN     amplify-provider-awscloudformation@"4.61.1" from @aws-amplify/amplify-category-api@1.1.6
npm WARN     node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api
npm WARN   1 more (graphql-transformer-core)
npm WARN 
npm WARN Could not resolve dependency:
npm WARN peer amplify-cli-core@"^2.3.0" from amplify-codegen@2.28.1
npm WARN node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation/node_modules/amplify-codegen
npm WARN   amplify-codegen@"^2.23.1" from amplify-provider-awscloudformation@4.61.1
npm WARN   node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation
npm WARN 
npm WARN Conflicting peer dependency: amplify-cli-core@2.9.1
npm WARN node_modules/amplify-cli-core
npm WARN   peer amplify-cli-core@"^2.3.0" from amplify-codegen@2.28.1
npm WARN   node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation/node_modules/amplify-codegen
npm WARN     amplify-codegen@"^2.23.1" from amplify-provider-awscloudformation@4.61.1
npm WARN     node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: amplify-codegen@2.28.1
npm WARN Found: graphql-transformer-core@6.30.0
npm WARN node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation/node_modules/graphql-transformer-core
npm WARN   graphql-transformer-core@"6.30.0" from amplify-provider-awscloudformation@4.61.1
npm WARN   node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation
npm WARN     amplify-provider-awscloudformation@"4.61.1" from @aws-amplify/amplify-category-api@1.1.6
npm WARN     node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api
npm WARN 
npm WARN Could not resolve dependency:
npm WARN peer graphql-transformer-core@"^7.2.1" from amplify-codegen@2.28.1
npm WARN node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation/node_modules/amplify-codegen
npm WARN   amplify-codegen@"^2.23.1" from amplify-provider-awscloudformation@4.61.1
npm WARN   node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation
npm WARN 
npm WARN Conflicting peer dependency: graphql-transformer-core@7.6.1
npm WARN node_modules/graphql-transformer-core
npm WARN   peer graphql-transformer-core@"^7.2.1" from amplify-codegen@2.28.1
npm WARN   node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation/node_modules/amplify-codegen
npm WARN     amplify-codegen@"^2.23.1" from amplify-provider-awscloudformation@4.61.1
npm WARN     node_modules/@aws-amplify/cli/node_modules/@aws-amplify/amplify-category-api/node_modules/amplify-provider-awscloudformation
npm WARN deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated
npm WARN deprecated graphql-import@0.7.1: GraphQL Import has been deprecated and merged into GraphQL Tools, so it will no longer get updates. Use GraphQL Tools instead to stay up-to-date! Check out https://www.graphql-tools.com/docs/migration-from-import for migration and https://the-guild.dev/blog/graphql-tools-v6 for new changes.
npm WARN deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated event-to-promise@0.8.0: Use promise-toolbox/fromEvent instead
npm WARN deprecated graphql-tools@4.0.8: This package has been deprecated and now it only exports makeExecutableSchema.\nAnd it will no longer receive updates.\nWe recommend you to migrate to scoped packages such as @graphql-tools/schema, @graphql-tools/utils and etc.\nCheck out https://www.graphql-tools.com to learn what package you should use instead
npm WARN deprecated @graphql-toolkit/common@0.6.6: GraphQL Toolkit is deprecated and merged into GraphQL Tools, so it will no longer get updates. Use GraphQL Tools instead to stay up-to-date! Check out https://www.graphql-tools.com/docs/migration-from-toolkit for migration and https://the-guild.dev/blog/graphql-tools-v6 for new changes.
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated @aws-amplify/cli@7.7.0-flutter-preview.2: This version is no longer supported. Please use 7.6.15 or higher.
npm WARN deprecated core-js@2.6.12: core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.
npm WARN deprecated core-js@2.6.12: core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.
npm WARN deprecated core-js@2.6.12: core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.
npm WARN deprecated @graphql-toolkit/common@0.9.7: GraphQL Toolkit is deprecated and merged into GraphQL Tools, so it will no longer get updates. Use GraphQL Tools instead to stay up-to-date! Check out https://www.graphql-tools.com/docs/migration-from-toolkit for migration and https://the-guild.dev/blog/graphql-tools-v6 for new changes.
npm WARN deprecated @graphql-toolkit/common@0.9.7: GraphQL Toolkit is deprecated and merged into GraphQL Tools, so it will no longer get updates. Use GraphQL Tools instead to stay up-to-date! Check out https://www.graphql-tools.com/docs/migration-from-toolkit for migration and https://the-guild.dev/blog/graphql-tools-v6 for new changes.

added 1583 packages, and audited 1609 packages in 2m

57 packages are looking for funding
  run `npm fund` for details

38 vulnerabilities (4 low, 7 moderate, 26 high, 1 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
npm notice 
npm notice New patch version of npm available! 8.12.1 -> 8.12.2
npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.12.2
npm notice Run npm install -g npm@8.12.2 to update!
npm notice

CLI の Flutter プレビューバージョンを使用していることを確認します。

amplify --version
(base) $ todo_lesson % amplify --version 
7.7.0-flutter-preview.2

Amplifyを構成します。以下のコマンドを実行すると、ブラウザが開きAWS コンソールにサインインを求められます。画面に従って進み、アクセスキーとシークレットキーを取得します。

amplify configure

取得したアクセスキーとシークレットキーを入力し、Amplifyの初期化に進みます。

Amplifyのプロジェクト初期化

AWS マネジメントコンソールでAmplifyを検索します。

コンソール上で新しいアプリケーションを作成します。

アプリケーションをビルドを選択します。

アプリケーション名を入力し、確定すると環境の構築が始まります。

入力したアプリケーション名のプロジェクトが反映され、Amplify Studioが起動できるようになります。

Amplify Studioの操作画面

Create data modal >> Add modal >> データのフィールドを追加 >> Save and Deploy

コンソール右上で、デプロイ中であることがわかります。

完了後

必要なパッケージを導入して、pubspec.yamlに必要事項を記述しましょう。

pubspec.yamlファイルとは?

pubspec.yamlはプロジェクトの設定ファイルであり、パッケージマネージャーとしての役割もあります。pubspec.yamlの中にはプロジェクトで使うsdkのバージョンやパッケージの他、画像ファイルの保存場所、フォント情報も記述します。

GetX エコシステム- 状態管理パッケージを導入

https://pub.dev/packages/getにアクセスします。

get: ^4.6.5 をクリップボードにコピー >> pubspec.yamlファイルに追加します。

amplify_flutter - パッケージを導入

https://pub.dev/packages/amplify_flutterにアクセスします。

amplify_flutter: ^0.5.1をクリップボードにコピー >> pubspec.yamlに追加します。

amplify_datastore - パッケージを導入

https://pub.dev/packages/amplify_datastoreにアクセスします。

amplify_datastore: ^0.5.1をクリップボードにコピー >> pubspec.yamlに追加します。

amplify_api - パッケージを導入

https://pub.dev/packages/amplify_apiにアクセスします。

amplify_api: ^0.5.1をクリップボードにコピー >> pubspec.yamlに追加します。

Flutter コーディング

pubspec.yamlにインポートするパッケージを追記します。

dev_dependencies:
  flutter_test:
    sdk: flutter

  get: ^4.6.5
  amplify_flutter: ^0.5.1
  amplify_datastore: ^0.5.1
  amplify_api: ^0.5.1

ドキュメントを確認

https://docs.amplify.aws/start/q/integration/flutter/にアクセスします

Next steps >> DataStore >> Getting started

DataStoreの初期化

DataStoreプラグインを導入するため、ドキュメント内の下記コードをコピーしてcontroller.dartファイルに追記します。

  Future<void> _configureAmplify() async {
    // Add the following lines to your app initialization to add the DataStore plugin
    final datastorePlugin =
        AmplifyDataStore(modelProvider: ModelProvider.instance);
    await Amplify.addPlugin(datastorePlugin);
    try {
      await Amplify.configure(amplifyconfig);
    } on AmplifyAlreadyConfiguredException {
      safePrint(
          'Tried to reconfigure Amplify; this can occur when your app restarts on Android.');
    }
  }

パッケージを使用するため、ターミナルに 以下コマンドを打ち込みます。

flutter pub get

参考:https://docs.flutter.dev/development/packages-and-plugins/using-packages

各パッケージがimportできる状態になるので、controller.dartファイルにインポートしていきます。

import 'package:amplify_datastore/amplify_datastore.dart';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:get/get_state_manager/src/simple/get_controllers.dart';

再度ドキュメントを参照(Next steps >> DataStore >> Syncing data to cloud)し、データをクラウドに同期するためドキュメント内の下記コードを参考にcontroller.dartファイルを編集します。

void _configureAmplify() async {
  final datastorePlugin = AmplifyDataStore(
    modelProvider: ModelProvider.instance,
  );
  // Add the following line and update your function call with `addPlugins`
  final api = AmplifyAPI();
  await Amplify.addPlugins([datastorePlugin, api]);
  try {
    await Amplify.configure(amplifyconfig);
  } on AmplifyAlreadyConfiguredException {
    print('Tried to reconfigure Amplify; this can occur when your app restarts on Android.');
  }
}

controller.dartファイルを編集し、APIライブラリをインポートします。

screensフォルダ内に、home_screen.dartファイルを作成します。

class HomeScreen extends StatelessWidget {
   const HomeScreen({Key? key}) : super(key: key);

   @override
   Widget build(BuildContext context) {
        return Container();
   }
}

home_screen.dartファイルにmarterial.dartファイルをインポートします。

ここから、アプリケーションのUIを作っていきます。

マテリアルデザイン用のWidgetであるScaffold内にappBar、body、floatingActionButtonを設置しています。

main.dartファイルに戻り、home_screen.dartファイルをインポートします。

Amplify Studio に戻り、右上のclick for next stepsをクリックします。

コードをコピーします。

Amplify CLI を使用して、このバックエンド環境にアプリケーションを接続します。

Amplify CLIにログインするか聞かれるので、Yesを選択します。

ターミナルに戻り、使用エディタなどいくつか質問されるので順番に答えていきます。

正常に終了すると、以下のような画面になります。

amplify statusを確認すると、API機能がカテゴリーに追加されていることがわかります。

controller.dartファイルに以下のパッケージをインポートします。

import 'package:flutter_amplify/amplifyconfiguration.dart';
import 'package:flutter_amplify/models/ModelProvider.dart';

実装後イメージ

Todoアプリ

Amplify Studioに戻り、サイドバーのContentを押下します。

Content内では、アプリが保持しているデータを読み書きできます。

Amplify Studio上でも追加したTodoが確認できます。

以下、完成版のコード

//main.dart

import 'package:flutter/material.dart';
import 'package:flutter_amplify/controller/controller_bindings.dart';
import 'package:flutter_amplify/screens/home_screen.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart';

void main() {
  runApp(const MyApp());                     //最初に呼ばれるルートウィジェット
}

class MyApp extends StatelessWidget {        //Myappは、状態の変化がないStatelessWidgetを継承している。
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {       //build()メソッド内にウィジェットツリーを記述して画面を表示する。
    return GetMaterialApp(
      initialBinding: ControllerBindings(),
      home: const HomeScreen(),
    );
  }
}
//controller_bindings.dart

import 'package:flutter_amplify/controller/controller.dart';
import 'package:get/get.dart';

class ControllerBindings extends Bindings {
  @override
  void dependencies() {
    Get.put(Controller());
  }
}
//controller.dart

import 'package:amplify_api/amplify_api.dart';
import 'package:amplify_datastore/amplify_datastore.dart';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:flutter_amplify/amplifyconfiguration.dart';
import 'package:flutter_amplify/models/ModelProvider.dart';
import 'package:get/get.dart';

class Controller extends GetxController {
  var todoList = <Todos>[].obs;
  @override
  void onInit() {
    _configureAmplify();
    super.onInit();
  }

  // amplifyの構成を行う。
  Future<void> _configureAmplify() async {
    // Add the following lines to your app initialization to add the DataStore plugin
    final datastorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance);
    await Amplify.addPlugin(datastorePlugin);
    try {
      await Amplify.configure(amplifyconfig);
    } on AmplifyAlreadyConfiguredException {
      safePrint('Tried to reconfigure Amplify; this can occur when your app restarts on Android.');
    }
  }

  //read data
  Future<void> readData() async {
    try {
      todoList = RxList(await Amplify.DataStore.query(Todos.classType));
      update();
    } on Exception catch (e) {
      print(e);
    }
  }

  //add new todo
  Future<void> addPost(String? task) async {
    try {
      var _newTodo = Todos(task: task!, isDone: false);

      await Amplify.DataStore.save(_newTodo);
      readData();
    } on Exception catch (e) {
      print(e);
    }
  }

  //update todo
  Future<void> updatePost(String? id, isDone) async {
    try {
      var _oldTodos = (await Amplify.DataStore.query(Todos.classType, where: Todos.ID.eq(id)))[0];

      var _newTodos = _oldTodos.copyWith(id: id!, task: _oldTodos.task, isDone: isDone!);

      await Amplify.DataStore.save(_newTodos);
      readData();
    } on Exception catch (e) {
      print(e);
    }
  }

  //delete todo
  Future<void> deleteTodo(String? id) async {
    (await Amplify.DataStore.query(Todos.classType, where: Todos.ID.eq(id))).forEach((element) async {
      try {
        await Amplify.DataStore.delete(element);
      } on Exception catch (e) {
        print(e);
      }
    });
    readData();
  }
}
//home_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_amplify/controller/controller.dart';
import 'package:get/get_state_manager/get_state_manager.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final TextEditingController _taskcontroller = TextEditingController();
  @override
  Widget build(BuildContext context) {
    return GetBuilder<Controller>(
        init: Controller(),
        initState: (_) {},
        builder: (_) {
          return Scaffold(
            appBar: AppBar(title: const Text('Flutter Amplify')),
            body: ListView.builder(
              itemCount: _.todoList.length,
              itemBuilder: (context, index) => Dismissible(
                key: UniqueKey(),
                onDismissed: (direction) => _.deleteTodo(_.todoList[index].id),
                background: Row(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: const [
                    Icon(
                      Icons.delete,
                      color: Colors.red,
                    )
                  ],
                ),
                child: _.todoList.isNotEmpty
                    ? Card(
                        child: ListTile(
                          title: Text(_.todoList[index].task),
                          trailing: Checkbox(
                            onChanged: (value) => _.updatePost(_.todoList[index].id, value),
                            value: _.todoList[index].isDone,
                          ),
                        ),
                      )
                    : const Center(
                        child: Text('No tasks'),
                      ),
              ),
            ),
            floatingActionButton: FloatingActionButton(
              child: const Icon(Icons.add),
              onPressed: () => showModalBottomSheet(
                context: context,
                builder: (context) => Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Column(
                    children: [
                      TextField(
                        controller: _taskcontroller,
                      ),
                      ElevatedButton(
                        onPressed: () => {
                          _.addPost(_taskcontroller.text.trim()),
                          Navigator.pop(context),
                          _taskcontroller.clear(),
                        },
                        child: const Text('save'),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          );
        });
  }
}

実装後の結果・結論

今回の実践でAWS AmplifyとFlutterを連携することができました。Amplify Studioを活用することで、初めにプロジェクトの環境をAWS上で準備してアプリケーションを順次構築していくことが可能だということがわかりました。必要な機能を順次追加して、Amplify側で提供されているバックエンドと組み合わせることによって開発スピードが上がるのだということがわかりました。

終わりに

いかがだったでしょうか?

AWS Amplifyを活用して、作成したモバイルアプリ開発を進めるイメージは湧いたでしょうか?

今回のテックブログ執筆にあたり、開発言語やフレームワーク・アーキテクチャーについて調査したことで、使用言語や技術が生まれた背景について興味関心が高まりました。また、AWS Amplifyを活用することで必要な機能を順次追加していくことが可能であるということがわかったので、引き続き認証機能や画像投稿機能を実装することで、AWSサービスと自作アプリを繋ぎ合わせて知識と技術の向上に努めたいと思います。

今回は以上になります。

最後まで読んでいただきありがとうございました。

divxでは一緒に働ける仲間を募集しています。 興味があるかたはぜひ採用ページを御覧ください。

divx.co.jp

実行環境

(base) $ flutter_amplify % flutter --version
Flutter 2.10.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 5464c5bac7 (10 weeks ago) • 2022-04-18 09:55:37 -0700
Engine • revision 57d3bac3dd
Tools • Dart 2.16.2 • DevTools 2.9.2

//本記事のTodoアプリのFlutterバージョンは2.10.5を使用しています。

(base) $ flutter_amplify % amplify --version
8.5.1

MacBook Air(M1,2020)

mac OS Montereyバージョン12.2.1

出典・参考資料

・Amplify

https://aws.amazon.com/jp/amplify/?nc=sn&loc=1

・Flutter

https://flutter.dev/

https://arstechnica.com/gadgets/2015/05/googles-dart-language-on-android-aims-for-java-free-120-fps-apps/

https://9to5google.com/2018/12/04/flutter-1-0-release/

基礎から学ぶFlutter(石井幸次, シーアンドアール研究所, 2019/12/25)

https://www.amazon.co.jp/基礎から学ぶ-Flutter-石井-幸次/dp/4863542941

・Dart

https://dart.dev/overview

https://hackernoon.com/why-flutter-uses-dart-dd635a054ebf

https://programming-place.net/ppp/contents/glossary/alphabet/jit_compiler.html

https://programming-place.net/ppp/contents/glossary/alphabet/aot_compiler.html

・クリーンアーキテクチャ

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html