NCMBのReact Native SDKを使ってカメラメモアプリを作る（その1：画面の説明とSDKの導入）

NCMBでは公式SDKの他、コミュニティSDKとして各種言語、フレームワーク向けにSDKを提供しています。今回はReact Native SDKを使ったカメラメモアプリ開発を解説します。カメラで写真を撮影して、そこに説明を付けて保存できるというアプリです。ファイルストアとデータストアの機能を利用します。

今回は各画面やコンポーネントの説明、そしてSDKの導入方法について解説します。

## コードについて

今回のコードは[NCMBMania/react-native-camera-app: React Native SDKを使ったカメラメモアプリのデモです](https://github.com/NCMBMania/react-native-camera-app)にアップロードしてあります。また、Expo Snackでも使用できます。

[カメラメモアプリ](https://snack.expo.dev/@git/github.com/NCMBMania/react-native-camera-app)

※ Expo SnackのWebの場合、アイコン表示と画像のリスト表示に不具合があります

## 利用技術について

今回は次のような組み合わせになっています。

- React Native 42.0.0
- Expo 42.0.1
- NCMB React Native SDK 2.0.14

## 仕様について

今回はシンプルにするために認証は利用していません。複数人で利用される場合などは、ID/パスワード認証を利用すると良いでしょう。

## 利用する機能について

カメラメモアプリで利用するNCMBの機能は次の通りです。

- データストア
  - Memoデータの保存
  - Memoデータの取得
- ファイルストア
  - 写真の保存
  - 写真のダウンロード

## 画面について

今回は4つの画面、6つのコンポーネントを利用しています。

### App.js

![FireShot Capture 103 - 写真撮影 - localhost 20211013111751 - 103のコピー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/7f64151d-a119-af9e-13df-e47fe4bb7990.jpeg)


今回はタブを利用しています。そのために必要なコンポーネントはApp.jsにて準備しています。

```js
const Tab = createBottomTabNavigator();

export default function App() {  
  return (
    <SafeAreaView style={styles.container}>
      <NavigationContainer>
        <Tab.Navigator>
          <Tab.Screen
            name="HomeTab"
            component={HomeTab}
            options={{
              tabBarIcon: () => <MaterialCommunityIcons size={24} name="camera" />,
              headerShown: false,
              title: 'メモ'
            }}
          />
          <Tab.Screen
            name="ListTab"
            component={ListTab}
            options={{
              tabBarIcon: () => <MaterialCommunityIcons size={24} name="format-list-bulleted-type" />,
              headerShown: false,
              title: 'リスト'
            }}
          />
        </Tab.Navigator>
      </NavigationContainer>
    </SafeAreaView>    
  );
}
```

#### HomeTab

左側のタブに対する内容です。カメラ画面と入力画面を切り替えられるようにしています。

```js
// 各画面
import HomeScreen from '../screens/HomeScreen';
import InputScreen from '../screens/InputScreen';

// ホームタブ（左側）
export const HomeTab = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} options={{ title: '写真撮影' }} />
      <Stack.Screen name="Input" component={InputScreen} options={{ title: 'メモ' }} />
    </Stack.Navigator>
  );
};
```

#### ListTab

右側のタブに対する内容です。一覧画面と詳細画面を切り替えられるようにしています。

```js
// 各画面
import ListScreen from '../screens/ListScreen';
import DetailScreen from '../screens/DetailScreen';

// リストタブ（右側）
export const ListTab = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="List" component={ListScreen} options={{ title: 'メモ一覧' }} />
      <Stack.Screen name="Detail" component={DetailScreen} options={{ title: 'メモ詳細' }} />
    </Stack.Navigator>
  );
};
```

### HomeScreen

![FireShot Capture 103 - 写真撮影 - localhost 20211013111751 - 103.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e40d090a-83c7-ed20-96e7-e8109650bfdf.jpeg)


カメラアイコンを表示して、それをタップすると写真選択画面になります。

```js
// カメラ画面用
export default function HomeScreen({ navigation }) {
  return (
    <View>
      <Text style={styles.paragraph}>
        写真を選択・カメラで撮影してください
      </Text>
      <Card>
        <Home navigation={navigation} />
      </Card>
    </View>
  );
}
```

#### Homeコンポーネント

カメラアイコンを表示します。onPressなどのイベントは2回目以降の記事で解説します。

```js
export default function Home({ navigation }) {
  // 表示内容
  return (
    <View style={styles.container}>
      <IconButton icon="camera" size={100} onPress={onPress} />
    </View>
  );
}
```

### InputScreen

![FireShot Capture 102 - メモ詳細 - localhost 20211013111645 - 102.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/af4ffaeb-9b12-1445-40fb-43c6a8cb574e.jpeg)


HomeScreenで撮影した写真を、入力フォームへ送ります。

```js
// 入力画面用
export default function InputScreen({ route, navigation }) {
  const { image } = route.params;
  return (
    <View>
      <Text style={styles.paragraph}>
        メモを入力してください
      </Text>
      <Card style={styles.form}>
        <InputForm image={image} navigation={navigation} />
      </Card>
    </View>
  );
}
```

#### InputFormコンポーネント

受け取った画像の表示、そしてメモを入力します。ここでメモと写真の保存処理も実装します。

```js
export default function InputForm({ image, navigation }) {
  // メモの内容
  const [ text, setText ] = useState('');

  // 表示内容
  return (
    <View>
      <View>
        <Image source={{uri: image.uri}} style={styles.image} />
      </View>
      <View>
        <TextInput
          label="メモ"
          value={text}
          style={styles.textInput}
          multiline={true}
          numberOfLines={3}
          onChangeText={(text) => setText(text)}
        />
      </View>
      <View style={styles.save}>
        <IconButton
          icon="content-save"
          size={25}
          onPress={onPress}
        />
      </View>
    </View>
  )
}
```

### ListScreen

![FireShot Capture 102 - メモ詳細 - localhost 20211013111646 - 102.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/141d47d3-d2d3-f34e-6347-57380dec9d36.jpeg)


既存のメモを一覧表示します。

```js
// 一覧画面用
export default function ListScreen({ navigation }) {
  return (
    <View>
      <Card>
        <MemoList navigation={navigation} />
      </Card>
    </View>
  );
}
```

#### MemoListコンポーネント

既存のメモを一覧表示します。一行あたりのデータはMemoコンポーネントにて表示します。

```js
export default function MemoList({ navigation }) {
  // メモ一覧用の変数
  const [memos, setMemos] = useState([]);

  // 表示内容
  return (
    <View style={styles.container}>
      <List.Section>
        <FlatList
          data={memos}
          renderItem={({index, item }) => <Memo
            item={item}
            index={index}
            navigation={navigation}
          />}
          keyExtractor={item => item.get('objectId')}
        />
      </List.Section>
    </View>
  )
}
```

#### Memoコンポーネント

MemoListにて取得したメモの配列に対して、その1つ1つのメモを表示するのに利用します。メモをタップすると、詳細画面へ遷移します。

```js
export default function Detail({ navigation, index, item }) {
  // 画像のURI（dataURI）が入る
  const [uri, setUri] = useState(null);

  // 表示内容
  return (
    <List.Item
      title={item.get('text')}
      // リストをタップしたら、編集画面に遷移する
      onPress={() => navigation.push('Detail', { item, uri })}
      // タスクの完了状態を見てチェックボックスを変更
      left={props => <Image
          source={{ uri }}
          // チェックボックスをタップしたら、完了状態を反転
          style={{ height: null, width: 100, resizeMode: 'contain' }}
        />
      }
      // > を表示
      right={props => <List.Icon
          icon="chevron-right"        
        />
      }
    />
  )
}
```

### DetailScreen

![FireShot Capture 102 - メモ詳細 - localhost 20211013111647 - 102.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e10434fb-afea-335b-cb97-d00da71e0128.jpeg)


一覧画面で選択されたデータを詳細表示します。

```js
// 詳細画面用
export default function DetailScreen(props) {
  return (
    <View>
      <Card>
        <Detail {...props} />
      </Card>
    </View>
  );
}
```

#### Detailコンポーネント

メモの内容と画像を表示します。

```js
export default function Detail({ route }) {
  // 一覧画面から渡されるデータ
  const { item, uri } = route.params;
  // 描画内容
  return (
    <View>
      <View style={styles.container}>
        <Image
          source={{ uri }}
          style={{ height: 300, width: '100%', resizeMode: 'contain' }}
        />
      </View>
      <View style={styles.text}>
        <Text>{ item.get('text')}</Text>
      </View>
    </View>
  )
}
```

## NCMBの初期化

React Native用のSDKを読み込みます。

```
npm i ncmb-react-native
```

### ncmb.jsの作成

componentsの中に ncmb.js を作成します。ここではReact Native SDKを読み込み、初期化を行います。

```js
import NCMB, { NCMBObject, NCMBQuery, NCMBFile } from 'ncmb-react-native';
import config from '../app.json';

// NCMBの初期化
new NCMB(config.applicationKey, config.clientKey);
```

後は同じ内容で返します。

```js
export default NCMB;
export {
  NCMBObject,
  NCMBQuery,
  NCMBFile,
};
```

app.jsonは次のような内容です。

```json
{
  "applicationKey": "YOUR_APPLICATION_KEY",
  "clientKey": "YOUR_CLIENT_KEY"
}
```

これでncmb.jsを読み込めば、初期化済みのReact Native SDKを利用できます。

## まとめ

今回は画面の説明と、NCMBのSDKを初期化する手順を紹介しました。[次回は写真の撮影と、メモの保存を行う手順について解説](https://qiita.com/goofmint/items/7ee2c256902416e089a4)します。

[NCMBMania/ncmb-react-native: NCMB SDK for React Native](https://github.com/NCMBMania/ncmb-react-native)

## おまけ

NCMBのReact Native SDKを使ったハンズオンは随時開催しています。ぜひチェックしてください。

[【お昼のハンズオン】React Native × NCMBでTodoアプリを作ろう \- connpass](https://ncmb.connpass.com/) など