AWS Mobile Hub による react-native アプリ開発（Cloud API REST呼び出し）


AWS mobile-hub が ```react-native``` に対応したとのことなので、マニュアル (Developer Guide) を元にサンプルアプリを作成・試してみる。

今回は Dynamodb にてテーブルを作成し、CRUDアプリケーションをサンプルに沿って作成してみた。

# はじめに

前回の記事から Dynamodb に登録・参照する簡単なサンプルプログラムを実行してみる。

# Backend (Mobile Hub) の設定

## テーブルの作成

コマンドから下記を実行

```
awsmobile database enable --prompt
```

マニュアル通りの Notes テーブルを作成

```
Welcome to NoSQL database wizard
You will be asked a series of questions to help determine how to best construct your NoSQL database table.

? Should the data of this table be open or restricted by user? Open
? Table name Notes

You can now add columns to the table.

? What would you like to name this column NoteId
? Choose the data type string
? Would you like to add another column Yes
? What would you like to name this column NoteTitle
? Choose the data type string
? Would you like to add another column Yes
? What would you like to name this column NoteContent
? Choose the data type string
? Would you like to add another column No


Before you create the database, you must specify how items in your table are uniquely organized. This is done by specifying a Primary key. The primary key uniquely identifies each item in the table, so that no two items can have the same key.
This could be and individual column or a combination that has "primary key" and a "sort key".
To learn more about primary key:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey


? Select primary key NoteId
? Select sort key (No Sort Key)

You can optionally add global secondary indexes for this table. These are useful when running queries defined by a different column than the primary key.
To learn more about indexes:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.SecondaryIndexes

? Add index No
Table Notes saved
```

## CRUD API の作成

下記コマンドにて cloud-api を作成

```
awsmobile cloud-api enable --prompt
```

先ほど作成した Notes テーブルから生成を選択。ログイン必須を選択した。

```
This feature will create an API using Amazon API Gateway and AWS Lambda. You can optionally have the lambda function perform CRUD operations against your Amazon DynamoDB table.


? Select from one of the choices below. Create CRUD API for an existing Amazon DynamoDB table
? Select Amazon DynamoDB table to connect to a CRUD API Notes
? Restrict API access to signed-in users Yes
Adding lambda function code on: 
/Users/masanao/dev/awsmobile/awsmobilejs/backend/cloud-api/Notes/
...
Path to be used on API for get and remove an object should be like:
/Notes/object/:NoteId

Path to be used on API for list objects on get method should be like:
/Notes/:NoteId

JSON to be used as data on put request should be like:
{
  "NoteTitle": "INSERT VALUE HERE",
  "NoteContent": "INSERT VALUE HERE",
  "NoteId": "INSERT VALUE HERE"
}
To test the api from the command line (after awsmobile push) use this commands
awsmobile cloud-api invoke NotesCRUD <method> <path> [init]
Api NotesCRUD saved
```

## 設定の反映

下記コマンドにて AWS のモジュールを作成

```shell
awsmobile push
```

作成に少し時間がかかるので、完了するのを待つ。

```
building backend
   building cloud-api
      zipping Notes
   generating backend project content
   backend project content generation successful
done, build artifacts are saved at: 
/Users/masanao/dev/awsmobile/awsmobilejs/.awsmobile/backend-build

preparing for backend project update: awsmobile
   uploading Notes-20180108140947.zip
   upload Successful  Notes-20180108140947.zip
done

updating backend project: awsmobile
awsmobile update api call returned with no error
waiting for the formation of cloud-api to complete
cloud-api update finished with status code: CREATE_COMPLETE

Successfully updated the backend awsmobile project: awsmobile

retrieving the latest backend awsmobile project information
awsmobile project's details logged at: awsmobilejs/#current-backend-info/backend-details.json
awsmobile project's access information logged at: awsmobilejs/#current-backend-info/aws-exports.js
awsmobile project's access information copied to: aws-exports.js
awsmobile project's specifications logged at: awsmobilejs/#current-backend-info/mobile-hub-project.yml
contents in #current-backend-info/ is synchronized with the latest in the aws cloud
```


# フロントエンドの構築

App.js を編集・CRUDのコードを書く。

## モジュール import

```
import { API } from 'aws-amplify-react-native';
```

## CRUDコードの記述

サンプルコードをそのまま貼り付けすることとした。

state の追加

```
state = {
  apiResponse: null,
  noteId: ''
     };

  handleChangeNoteId = (event) => {
        this.setState({noteId: event});
}
```

saveNote 関数の追加

```
  // Create a new Note according to the columns we defined earlier
    async saveNote() {
      let newNote = {
        body: {
          "NoteTitle": "My first note!",
          "NoteContent": "This is so cool!",
          "NoteId": this.state.noteId
        }
      }
      const path = "/Notes";

      // Use the API module to save the note to the database
      try {
        const apiResponse = await API.put("NotesCRUD", path, newNote)
        console.log("response from saving note: " + apiResponse);
        this.setState({apiResponse});
      } catch (e) {
        console.log(e);
      }
    }
```

getNote 関数の追加

```
  // noteId is the primary key of the particular record you want to fetch
      async getNote() {
        const path = "/Notes/object/" + this.state.noteId;
        try {
          const apiResponse = await API.get("NotesCRUD", path);
          console.log("response from getting note: " + apiResponse);
          this.setState({apiResponse});
        } catch (e) {
          console.log(e);
        }
      }
```

deleteNote 関数の追加

```
  // noteId is the NoteId of the particular record you want to delete
      async deleteNote() {
        const path = "/Notes/object/" + this.state.noteId;
        try {
          const apiResponse = await API.del("NotesCRUD", path);
          console.log("response from deleteing note: " + apiResponse);
          this.setState({apiResponse});
        } catch (e) {
          console.log(e);
        }
      }
```

rennder 関数の変更（Viewの変更）

```
<View style={styles.container}>
        <Text>Response: {this.state.apiResponse && JSON.stringify(this.state.apiResponse)}</Text>
        <Button title="Save Note" onPress={this.saveNote.bind(this)} />
        <Button title="Get Note" onPress={this.getNote.bind(this)} />
        <Button title="Delete Note" onPress={this.deleteNote.bind(this)} />
        <TextInput style={styles.textInput} autoCapitalize='none' onChangeText={this.handleChangeNoteId}/>
</View>

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  textInput: {
      margin: 15,
      height: 30,
      width: 200,
      borderWidth: 1,
      color: 'green',
      fontSize: 20,
      backgroundColor: 'black'
   }
});
```

作成したApp.js ソース

react-native のサンプルソースを元にしたため、一部不要なソースコードがありますがそのままにしています。

```
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import Amplify from 'aws-amplify-react-native';
import { API } from 'aws-amplify-react-native';
import { withAuthenticator } from 'aws-amplify-react-native';
import aws_exports from './aws-exports';

Amplify.configure(aws_exports);

import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  Button,
  TextInput,
  View
} from 'react-native';

const instructions = Platform.select({
  ios: 'Press Cmd+R to reload,\n' +
    'Cmd+D or shake for dev menu',
  android: 'Double tap R on your keyboard to reload,\n' +
    'Shake or press menu button for dev menu',
});

class App extends Component<{}> {
  constructor(props) {
        super(props);
	this.state = {
	  apiResponse: null,
	  noteId: ''
	};
  }

  handleChangeNoteId = (event) => {
        this.setState({noteId: event});
  }

  // Create a new Note according to the columns we defined earlier
    async saveNote() {
      let newNote = {
        body: {
          "NoteTitle": "My first note!",
          "NoteContent": "This is so cool!",
          "NoteId": this.state.noteId
        }
      }
      const path = "/Notes";

      // Use the API module to save the note to the database
      try {
        const apiResponse = await API.put("NotesCRUD", path, newNote)
        console.log("response from saving note: " + apiResponse);
        this.setState({apiResponse});
      } catch (e) {
        console.log(e);
      }
    }
     // noteId is the primary key of the particular record you want to fetch
      async getNote() {
        const path = "/Notes/object/" + this.state.noteId;
        try {
          const apiResponse = await API.get("NotesCRUD", path);
          console.log("response from getting note: " + apiResponse);
          this.setState({apiResponse});
        } catch (e) {
          console.log(e);
        }
      }

     // noteId is the NoteId of the particular record you want to delete
      async deleteNote() {
        const path = "/Notes/object/" + this.state.noteId;
        try {
          const apiResponse = await API.del("NotesCRUD", path);
          console.log("response from deleteing note: " + apiResponse);
          this.setState({apiResponse});
        } catch (e) {
          console.log(e);
        }
      }

  render() {
    return (
<View style={styles.container}>
        <Text>Response: {this.state.apiResponse && JSON.stringify(this.state.apiResponse)}</Text>
        <Button title="Save Note" onPress={this.saveNote.bind(this)} />
        <Button title="Get Note" onPress={this.getNote.bind(this)} />
        <Button title="Delete Note" onPress={this.deleteNote.bind(this)} />
        <TextInput style={styles.textInput} autoCapitalize='none' onChangeText={this.handleChangeNoteId}/>
</View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  textInput: {
      margin: 15,
      height: 30,
      width: 200,
      borderWidth: 1,
      color: 'green',
      fontSize: 20,
      backgroundColor: 'black'
   }
});
export default withAuthenticator(App);
```

# 実行

```
react-native run-ios
```

![スクリーンショット 2018-01-08 18.51.18.png](https://qiita-image-store.s3.amazonaws.com/0/141148/c2dedcec-90e0-4013-a38d-105d610a70fe.png)

テキストボックス内に NoteID を入力して "Save Note" を押すと、保存されます。

内容はソースコードに書いてあるように固定文字が登録されます。
あくまでサンプルソースということで・・・。

# AWS 確認

```Mobile Hub``` の Cloud Logic には下記のように登録されています。

![スクリーンショット 2018-01-08 19.01.38.png](https://qiita-image-store.s3.amazonaws.com/0/141148/105d96ff-2576-940e-8fa2-107dba4e1bf3.png)

Lambda も確認すると、NotesCRUD-xxxx という名前で登録されているのが確認できます。

![スクリーンショット 2018-01-08 19.02.59.png](https://qiita-image-store.s3.amazonaws.com/0/141148/915b47b1-84d0-0592-ddf8-69509d3fd37b.png)

DynamoDB も同様にテーブルが作成され、データも登録されているのがわかります。

![スクリーンショット 2018-01-08 19.24.53.png](https://qiita-image-store.s3.amazonaws.com/0/141148/ccc9ed59-6687-7f3b-597b-4e99d575bbb7.png)

通常のCRUD であればほぼノンプログラミングでバックエンドロジックが生成できますね。