スクリーンショット_2019-12-18_18

Amplify DataStore を使ってオフライン対応Webアプリを作成する

先々週の2019年12月7日、Amplifyの新機能「Amplify DataStore」が発表されました!早速試します。

やりたいこと

・デバイスがオフラインのときも、ユーザはキャッシュデータにアクセスできる
・デバイスがオフラインのときも、データ作成および変更を中断させない
・デバイスがオンラインに戻ると、自動でバックエンドに再接続し、データを同期して、競合がある場合は解決する

画像1

環境

・Angular: 8.3.7
・amplify-cli: 4.6.0

手順

1. Angularプロジェクトを作成

$ ng new amplify-datastore --style=scss --routing
$ cd amplify-datastore

2. Amplifyの利用準備

$ npx amplify-app@latest

Angular6以上の場合、src/polyfills.tsに以下を追加

(window as any).global = window;
(window as any).process = {
  env: { DEBUG: undefined },
};

3. 必要なAmplifyライブラリをインストール

$ npm i @aws-amplify/core @aws-amplify/datastore

4. AWSアカウントと紐付ける設定の作成

$ amplify init

5. Amplify APIを追加

$ amplify add api

6. GraphQLスキーマを追加
amplify/backend/api/amplifyDatasource/schema.graphql

enum PostStatus {
  ACTIVE
  INACTIVE
}

type Post @model {
  id: ID!
  title: String!
  rating: Int!
  status: PostStatus!
}

7. モデルジェネレータ起動ッ

$ npm run amplify-modelgen

これでCFn設定とかアプリ用のモデル定義が作られたと思う、たぶん

8. src/main.tsで設定をインポート

import Amplify from '@aws-amplify/core';
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);

9. コンポーネントはこんな感じで作った
src/app/app.component.html

<div>
  <div>
    <button (click)="createPost()">NEW</button>
    <button (click)="deletePosts()">DELETE ALL</button>
  </div>
  <table border="1">
    <thead>
      <tr>
        <td>id</td>
        <td>title</td>
        <td>version</td>
        <td>actions</td>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let post of (posts | async)">
        <td>{{post.id.substring(0, 8)}}</td>
        <td>{{post.title}}</td>
        <td>{{post._version}}</td>
        <td>
          <button (click)="updatePost(post.id)">update</button>
          <button (click)="deletePost(post.id)">delete</button>
        </td>
      </tr>
    </tbody>
  </table>
</div>

src/app/app.component.ts

import {Component, OnDestroy, OnInit} from '@angular/core';
import {Post, PostStatus} from '../models';
import {DataStore, Predicates} from '@aws-amplify/datastore';
import {from, Observable} from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
  posts: Observable<any[]>;
  private sub: ZenObservable.Subscription;

  ngOnInit() {
    this.posts = from(DataStore.query(Post, Predicates.ALL));
    this.sub = DataStore.observe(Post as any).subscribe(msg => {
      this.getPosts();
    });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }

  createPost() {
    DataStore.save(
      new Post({
        title: `Created! ${Date.now()}`,
        rating: 1,
        status: PostStatus.ACTIVE
      })
    );
  }

  private getPost(id: string) {
    return from(DataStore.query(Post as any, id));
  }

  private getPosts() {
    this.posts = from(DataStore.query(Post, Predicates.ALL));
  }

  updatePost(id: string) {
    this.getPost(id).subscribe(item => {
      DataStore.save(
        Post.copyOf(item as any, updated => {
          updated.title = `Updated! ${Date.now()}`;
          updated.rating = 4;
        })
      );
    });
  }

  deletePost(id: string) {
    this.getPost(id).subscribe(item => DataStore.delete(item));
  }

  deletePosts() {
    DataStore.delete(Post, Predicates.ALL);
  }

}

自動生成のPostモデルには_versionのゲッターがないので、Postじゃなくてanyで取り回す。本来的にはフロントで出すような情報じゃないからだろうけど、デモとしてのわかりやすさ優先でむりやり出す。

スクリーンショット 2019-12-18 17.45.44

この状態でバックエンドなしでそこそこ動く。_versionはバックエンドの方で作られる値なんで空白。

10. バックエンドのリソースを作成

$ npm run amplify-push

これでAppSyncリソースが作られた。

スクリーンショット 2019-12-18 15.20.39

あとDynamoDBにテーブルが作られた。

スクリーンショット 2019-12-18 15.14.42

アプリを動かしたらば

スクリーンショット 2019-12-18 17.48.09

DynamoDBにレコードが記録された。

スクリーンショット 2019-12-18 18.37.46

ウェ〜イ🎉🎉🎉

トラブルシューッ

Cannot find module 'stream'. とか Cannot find name 'Buffer'. のとき

DataStore - Sync error subscription failed Connection failed: Buffer is not defined のとき
src/polyfills.tsに以下を追加

global.Buffer = global.Buffer || require('buffer').Buffer;

参考URL

感想

オフラインからの復帰で変更の競合が解決されるさまをgifにとったけど、noteのアップロード上限がそれを許さなかった。南無三。

オフライン処理って自力で書いたことないけど大変なことになるのは必至。こんなふうにさくっと外部化して、本当にやるべきことちゃんとやるっていうのはいいですね。

この記事が気に入ったらサポートをしてみませんか?