電通総研 テックブログ

電通総研が運営する技術ブログ

Expo Tutorial前編

電通国際情報サービス、オープンイノベーションラボの比嘉康雄です。

今回は、Expoの公式チュートリアルをやっていきたいと思います。

プロジェクトの作成

StickerSmashプロジェクトを作成しましょう。

npx create-expo-app StickerSmash && cd StickerSmash

Download assets

https://docs.expo.dev/static/images/tutorial/sticker-smash-assets.zip
をダウンロードしてから解凍し、StickerSmash/assetsに格納します。既存のファイルは置き換えてください。

Install dependencies

Webでも実行できるようにするために、必要なモジュールをインストールします。

npx expo install react-dom react-native-web @expo/webpack-config

アプリの実行

プロジェクトのディレクトリで、npx expo startを実行します。
wを押すと、Webアプリを試すことができます。

次のように表示されました。

コードの編集

プロジェクトのディレクトリで、code .を実行して、VS Codeを立ち上げます。
背景を#25292eに変更しましょう。

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#25292e",
    alignItems: "center",
    justifyContent: "center",
  },
});

次のように表示されました。

テキストのデフォルトの文字色は黒なので、背景が黒っぽくなったことで、文字が見え辛くなってしまいました。

テキストの文字色の変更

Textタグのstyle属性で、文字の色を白に指定しましょう。

<Text style={{ color: "#fff" }}>
    Open up App.js to start working on your app!
</Text>

次のように表示されました。

イメージの表示

イメージを表示しましょう。
Imageコンポーネントをインポートします。

import { StyleSheet, View, Image } from 'react-native';

次は、イメージパスの設定です。

const PlaceholderImage = require('./assets/images/background-image.png');

Imageタグを使う前に、スタイルシートの設定をしましょう。

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#25292e",
    alignItems: "center",
  },
  imageContainer: {
    flex: 1,
    paddingTop: 58,
  },
  image: {
    width: 320,
    height: 440,
    borderRadius: 18,
  },
});

準備ができたので、Imageコンポーネントを使ってみましょう。

<View style={styles.imageContainer}>
  <Image source={PlaceholderImage} style={styles.image} />
</View>

下記のように表示されました。

ImageViewerコンポーネント

先ほど作ったイメージ表示機能をコンポーネント化しましょう。
StickSmashディレクトリの中にcomponentsディレクトリを作成します。

.../StickerSmash$ mkdir components

VS CodeImageViewer.jsを作成します。

code components/ImageViewer.js

下記のコードをImageViewer.jsに書き込みます。

import { StyleSheet, Image } from 'react-native';

export default function ImageViewer({ placeholderImageSource }) {
  return (
    <Image source={placeholderImageSource} style={styles.image} />
  );
}

const styles = StyleSheet.create({
  image: {
    width: 320,
    height: 440,
    borderRadius: 18,
  },
});

App.jsからImageViewer.jsを呼び出します。

import ImageViewer from './components/ImageViewer';

const PlaceholderImage = require('./assets/images/background-image.png');

export default function App() {
  return (
    <View style={styles.container}>
      <View style={styles.imageContainer}>
        <ImageViewer placeholderImageSource={PlaceholderImage} />
      </View>
      <StatusBar style="auto" />
    </View>
  );
}

見た目はもちろん以前と一緒です。

Buttonコンポーネント

これからボタンを2つ作りますが、その前に、Buttonコンポーネントを作ってそれを利用することにしましょう。

VS CodeButton.jsを作成します。

code components/Button.js

下記のコードをButton.jsに書き込みます。

import { StyleSheet, View, Pressable, Text } from 'react-native';

export default function Button({ label }) {
  return (
    <View style={styles.buttonContainer}>
      <Pressable style={styles.button} onPress={() => alert('You pressed a button.')}>
        <Text style={styles.buttonLabel}>{label}</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  buttonContainer: {
    width: 320,
    height: 68,
    marginHorizontal: 20,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 3,
  },
  button: {
    borderRadius: 10,
    width: '100%',
    height: '100%',
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'row',
  },
  buttonLabel: {
    color: '#fff',
    fontSize: 16,
  },
});

App.jsからButton.jsを呼び出します。

import Button from './components/Button';

<View style={styles.footerContainer}>
  <Button label="Choose a photo" />
  <Button label="Use this photo" />
</View>

const styles = StyleSheet.create({
// Styles that are unchanged from previous step are hidden for brevity. 
  footerContainer: {
    flex: 1 / 3,
    alignItems: 'center',
  },
});

下記のように表示されました。

PhotoButtonコンポーネント

写真アイコンが表示されるボタンを作りましょう。@expo/vector-iconをインストールします。

npx expo install @expo/vector-icons

VS CodePhotoButton.jsを作成します。

code components/PhotoButton.js

下記のコードをPhotoButton.jsに書き込みます。

import { StyleSheet, View, Pressable, Text } from "react-native";
import FontAwesome from "@expo/vector-icons/FontAwesome";

export default function PhotoButton({ label }) {
  return (
    <View style={styles.buttonContainer}>
      <Pressable
        style={styles.button}
        onPress={() => alert("You pressed a button.")}
      >
        <FontAwesome
          name="picture-o"
          size={18}
          color="#25292e"
          style={styles.buttonIcon}
        />
        <Text style={[styles.buttonLabel, {}]}>{label}</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  buttonContainer: {
    width: 320,
    height: 68,
    marginHorizontal: 20,
    alignItems: "center",
    justifyContent: "center",
    padding: 3,
    borderWidth: 4,
    borderColor: "#ffd33d",
    borderRadius: 18,
  },
  button: {
    borderRadius: 10,
    width: "100%",
    height: "100%",
    alignItems: "center",
    justifyContent: "center",
    flexDirection: "row",
    backgroundColor: "#fff",
  },
  buttonIcon: {
    paddingRight: 8,
  },
  buttonLabel: {
    color: "#25292e",
    fontSize: 16,
  },
});

下記のように表示されました。

画像の選択

画像を選択する機能を実装しましょう。expo-image-pickerをインストールします。

npx expo install expo-image-picker

App.jspickImageAsyncファンクションを実装しましょう。選択した画像は、result.assets[0].uriに入っています。

import * as ImagePicker from 'expo-image-picker';

const pickImageAsync = async () => {
  const result = await ImagePicker.launchImageLibraryAsync({
    allowsEditing: true,
    quality: 1,
  });

  if (!result.canceled) {
    alert(result.assets[0].uri);
  } else {
    alert('You did not select any image.');
  }
};

PhotoButtonでボタンを押したときの動作が定義できるように、onPress属性を追加しましょう。

export default function PhotoButton({ label, onPress }) {
  return (
    <View style={styles.buttonContainer}>
      <Pressable
        style={styles.button}
        onPress={onPress}
      >
        <FontAwesome
          name="picture-o"
          size={18}
          color="#25292e"
          style={styles.buttonIcon}
        />
        <Text style={styles.buttonLabel>{label}</Text>
      </Pressable>
    </View>
  );
}

App.jsPhotoButtonが押されたときに、pickImageAsyncファンクションを呼び出します。

<PhotoButton label="Choose a photo" onPress={pickImageAsync} />

ImageViewerで選択した画像を表示できるように、selectedImage属性を追加します。

export default function ImageViewer(
  { placeholderImageSource, selectedImage }) {
  const imageSource = selectedImage ?
    { uri: selectedImage } :
    placeholderImageSource;
  return <Image source={imageSource} style={styles.image} />;
}

App.jsで、写真が選択されたら、ImageViewerに表示します。

import { useState } from "react";

const PlaceholderImage = require("./assets/images/background-image.png");

export default function App() {
  const [selectedImage, setSelectedImage] = useState(null);

  const pickImageAsync = async () => {
    const result = await ImagePicker.launchImageLibraryAsync({
      allowsEditing: true,
      quality: 1,
    });

    if (!result.canceled) {
      setSelectedImage(result.assets[0].uri);
    } else {
      alert("You did not select any image.");
    }
  };

  return (
    <View style={styles.container}>
      <View style={styles.imageContainer}>
        <ImageViewer
          placeholderImageSource={PlaceholderImage}
          selectedImage={selectedImage}
        />
      </View>
      <View style={styles.footerContainer}>
        <PhotoButton label="Choose a photo" onPress={pickImageAsync} />
        <Button label="Use this photo" />
      </View>
      <StatusBar style="auto" />
    </View>
  );
}

下記のように表示されました。

切りが良いので、前編はここまでとします。

まとめ

Expoの公式チュートリアルは、難易度もちょうどいい感じで、実践的な力が身につくなと感じました。ただ、JavaScriptじゃなくTypeScriptにしてほしかったところです。

仲間募集

私たちは同じグループで共に働いていただける仲間を募集しています。
現在、以下のような職種を募集しています。

執筆:@higa
Shodoで執筆されました