35
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ateam LifestyleAdvent Calendar 2017

Day 3

React Native でアプリ開発!初心者のための実装 Tips

Last updated at Posted at 2017-12-02

エイチームライフスタイルアドベントカレンダー2017、3日目です。

本日は株式会社エイチームライフスタイルの、エンジニア兼ソース顔担当の @maa0984 がお送りします。
入社間もない頃に先輩社員から「顔がアルパカに似てる」と言われて以来、トレードマークにしています。

最近英語を勉強しようと、英語のフレーズをリスト化して表示するアプリを作りました。
せっかくなので、興味がありつつも手を出せていなかった React Native に挑戦しました。

初めて使用するため手探り状態でしたが、その中で『やりたかったこと』と『それを実現させた方法』をいくつかピックアップしてお伝えします。
React Native でアプリ作りを始めてみようとしている方に、何かの参考になれば幸いです。

対象バージョン

  • react-native: 0.49.3
  • react-native-swipeout: ^2.2.2
  • react-native-google-signin: ^0.12.0
  • react-navigation: ^1.0.0-beta.13

本記事のソースコード

アプリの概要

AlpacaFlashcards_demo.gif

このアプリでできることは、大体以下の通りです。

  1. Google Sheets にデータを登録
  2. アプリの画面を下に引くと、Google Sheets のデータを取得
  3. 取得したデータをリスト表示
  4. リストを各行スワイプするとステータス変更のボタンが出現

内部的にはアプリケーションの状態管理や非同期処理管理で Redux や Redux-Saga を使用しています。
こちらは長くなるので別の形で投稿しようと思います。

それでは、アプリをどのように作っていったのか、ご紹介していきます。

実装内容

トップ画面

リストビューを作りたい

リストビュー.png

作りたかったのは英語のフレーズをリストで表示させるアプリです。
ですのでリストビューがなければお話しになりません/(^O^)\

これまで React Native でリストを作成する時は、コアコンポーネントの ListView が使われていたようです。
しかし現在では Deprecated となり、FlatList 及び SectionList が推奨されています。
今回セクショニングは必要ないため FlatList を使用しました。

components/Phrases.js
<!-- ソースコードは簡略化しています -->
<!-- そしてJSXのシンタックスハイライトがなかったので、仕方なくHTMLを設定しています。。 -->

export default class Phrases extends React.Component {
  import { FlatList, View, Text } from 'react-native';

  render() {
    return (
      <View style={styles.container} >
        <FlatList
          data={this.props.phrases.data}
          renderItem={this._renderItem.bind(this)}
          keyExtractor={(item, index) => item.key}
          ItemSeparatorComponent={() => <View style={styles.separator} />}
        />
      </View>
    );
  }

  _renderItem({ item, index }) {
    return (
      <View style={styles.phraseSentenceBodyView} >
        <Text
          style={styles.phraseText}
          ellipsizeMode='tail'
          numberOfLines={1} >
          {item.key}
        </Text>
      </View>
    );
  }
}

FlatList の基本的なプロパティは以下の通りです。

プロパティ 必須 初期値 説明
data array Yes リスト表示するデータの配列
renderItem function Yes リストの各行を描画するための関数
keyExtractor function No 各行の識別子を返す関数。React内部でデータのキャッシュ等に使われる
ItemSeparatorComponent component No 行間を描画をするコンポーネントを設定する
refreshControl component No プルリフレッシュの定義。RefreshControlコンポーネントを設定する(詳しくは後述)

プルリフレッシュを実装したい

リフレッシュ.png

データは外部(Google Sheets)で管理しています。
同期するために、アプリでおなじみのプルリフレッシュ(下に引っ張って更新するやつ)を実装します。

使用するのはコアコンポーネントのRefreshControlです。
FlatList など ScrollView 系のコンポーネントが持っているrefreshControlプロパティに設定します。

components/Phrases.js
export default class Phrases extends React.Component {
  import { FlatList, View, Text } from 'react-native';

  render() {
    return (
      <View style={styles.container} >
        <FlatList
          data={this.props.phrases.data}
          renderItem={this._renderItem.bind(this)}
          keyExtractor={(item, index) => item.key}
          ItemSeparatorComponent={() => <View style={styles.separator} />}
          refreshControl={
            <RefreshControl
              refreshing={this.props.phrases.refreshing}
              onRefresh={this.props.onRefreshPhrases}
            />
          }
        />
      </View>
    );
  }
}

必要なプロパティはこちらです。

プロパティ 必須 初期値 説明
refreshing bool Yes リフレッシュ中の🌀の表示・非表示を判定するBool値
onRefresh function No リフレッシュ時に実行する関数

リストでスワイプボタンを表示させたい

スワイプ.png

アプリのリストって、大体左にスワイプできますよね。そして大体削除ボタンが出てきます。
このアプリではリストにある英語を実際に使った時に、スワイプして『Done!』させるためのボタンを実装しました。

使用したのは、react-native-swipeout というプラグインです。
スワイプを実現するための Swipeout コンポーネントを提供してくれます。

初めににスワイプした時のボタンを定義します(下の例だと swipeBtns)。
それを表示したい場所に応じて、 Swipeout の right もしくは left プロパティに登録します。

components/Phrases.js
export default class Phrases extends React.Component {
  import { FlatList, TouchableHighlight, View, Text } from 'react-native';
  import Swipeout from 'react-native-swipeout';

  _renderItem({ item, index }) {
    const swipeBtns = [{
      text: (item.isCompleted()) ? 'Revert' : 'Complete',
      backgroundColor: 'blue',
      underlayColor: 'rgba(0,0,0,1)',
      onPress: () => { this._completePhrase({ item, index }) },
    }];

    return (
      <Swipeout
        right={swipeBtns}
        autoClose={true}
        backgroundColor='transparent' >
        <TouchableHighlight
          underlayColor='rgba(192,192,192,1)'
          onPress={() => { this._showPhraseFor(item) }} >
          <View style={styles.phraseSentenceBodyView} >
            <Text
              style={styles.phraseText}
              ellipsizeMode='tail'
              numberOfLines={1} >
              {item.key}
            </Text>
          </View>
        </TouchableHighlight>
      </Swipeout>
    );
  }
}

Swipeout のプロパティはこちらです。

プロパティ 必須 初期値 説明
autoClose bool No false ボタンタップ時にスワイプを閉じるかどうか
right array No [] リスト右側に表示させるボタン
left array No [] リストの左側に表示させるボタン
backgroundColor string No '#dbddde' 背景色

そしてボタンのプロパティはこちらです。

プロパティ 必須 初期値 説明
text string No 'Click Me' ボタンの文言
backgroundColor string No '#b6bec0' 背景色
underlayColor string No ボタンをタップした時の色
onPress function No タップ時に実行する関数

ログイン画面

Google認証を通してユーザー情報を取得したい

ログイン.png

データをどこに保存するか、すごく悩みました。
DBライクなクラウドストレージで最もお手軽なのは何だろうかと色々考えた結果、行き着いた先は Google Sheets でした。

ただ、それにはアプリから Google の認証を通してアクセストークンを取得する必要があります。
少々骨が折れるだろうと、覚悟していました。
しかし救世主現る。

react-native-google-signin

素晴らしいプラグインですです。
簡単に Google のアカウント情報をアプリから取得することができます。
これが開発を加速させてくれました。

使い方はいたって簡単。
ログインしてアカウントのオブジェクトを取得するまでを見ていきましょう。

import GoogleSignin from 'react-native-google-signin';

class Signin extends React.Component {
  signIn() {
    // GooglePlayService がインストールされているか確認 for Android
    GoogleSignin.hasPlayServices({ autoResolve: true }).then(() => {
      // クライアントIDの設定など
      GoogleSignin.configure(config).then(() => {
        // ログインページを立ち上げる
        GoogleSignin.signIn().then((user) => {
          this.setState({ user });
        })
      })
    })
  }
}

まず初めに GoogleSignin.hasPlayServices をコールします。
これは Android 専用の処理で、アプリに GooglePlayService がインストールされているかをチェックします。
iOS端末の場合は常に true を返します。
autoResolve: true を渡すと、インストールされていない場合に『インストールしてね』という旨のプロンプトが表示されます。

次に GoogleSignin.configure で、Google の Auth 認証のクライアントIDや、クラウド上のファイルへのアクセスコントロール(読み込み専用か、書き込みも出来るかなど)を設定します。

そして GoogleSignin.signIn をコールすると、ログインページが立ち上がります。
メールアドレスとパスワードを入力して認証を通すと、Google アカウントのユーザーオブジェクトを取得できます。

取得できるアカウントのオブジェクトのフォーマットはこちらです。

  {
    id: <user id. do not use on the backend>
    name: <user name>
    givenName: <user given name> (Android only)
    familyName: <user family name> (Android only)
    email: <user email>
    photo: <user picture profile>
    idToken: <token to authenticate the user on the backend>
    serverAuthCode: <one-time token to access Google API from the backend on behalf of the user>
    scopes: <list of authorized scopes>
    accessToken: <needed to access google API from the application>
  }

このオブジェクトがアクセストークンを持っているため、それを使って Google の API を叩くことができるようになります!
余談ですが、上のサンプルソースだとコールバックがネストされて扱いづらくなっています。
それを避けたい場合は async 及び await を使うと、通常の同期処理と同じように書けるためおすすめです。

その他

ページ間のナビゲーションを作りたい

ナビゲーション.png

表示させたい画面をいくつか作成したら、あとはそれらをメニューから行き来できるようにする必要があります。

ナビゲーションの実装には react-navigation を使用しました。
公式ページのドキュメントでも、こちらのプラグインでの実装例が紹介されています。

ドロワーメニューを作りたかったので、DrawerNavigator を使用しました。
英語フレーズの一覧ページ(Phrases.js)と Google へのログインページ(Signin.js)をドロワーに追加しています。

App.js
import React from 'react';
import { StackNavigator, DrawerNavigator } from 'react-navigation';
import Icon from 'react-native-vector-icons/FontAwesome';

import Phrases from './containers/Phrases';
import Signin from './containers/Signin';
import SettingsList from './containers/SettingsList';
import SettingsListChild from './containers/SettingsListChild';

const Drawer = DrawerNavigator({
  Phrases: {
    screen: Phrases
  },
  Signin: {
    screen: Signin
  },
});

ドロワーメニューは画面の左端から右にスワイプするだけで表示されます。
しかしわかりやすいよう、ヘッダーの左上にハンバーガーメニューを載せてみます。

App.js
Drawer.navigationOptions = ({ navigation }) => {
  return {
    headerLeft: (
      <Icon.Button
        name='bars'
        color='blue'
        backgroundColor='transparent'
        onPress={() => navigation.navigate('DrawerToggle')}
      />
    ),
  }
};

Icon コンポーネントは react-native-vector-icons というプラグインが提供してくれているものです。
あらゆるアイコンをアプリ上で簡単に表示させることができます。
タップイベントにはドロワーの表示・非表示のトグルアクションを設定しています。

これでメニューから画面を行き来できるようになりました!

因みにナビゲーションのプラグインだと、React Native に特化した react-native-navigation もあります。
使ったことはありませんが、デモやドキュメントを見たところ、とても柔軟な設定ができそうな印象を受けました。
もし使ったことがある方がいらっしゃいましたら、ご感想等いただけますと幸いです!

おわりに

React Native は HTML, CSS, JavaScript の知識をベースにクロスプラットフォームのアプリを開発することができます。
Webエンジニアにはとても親和性が高いと感じました。
クロスプラットフォームアプリ開発には Xamarin もありますので、それぞれの違いを理解し、プロダクトやプロジェクトに最適な選択ができると良いと考えています!

ちなみに作ったアプリ名は AlpacaFlashcards ですが、アルパカもフラッシュカードも特に関係ありません。
当初の想定とは全然違う形に仕上がりました。
リネームしようにも変更箇所が膨大なため、そのままにしています。

エイチームライフスタイルアドベントカレンダー2017、明日は寿司言語の開発者 @suzuki_sh さんが数学について書いてくれるようです。お見逃しなく!


株式会社エイチームライフスタイルでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。
http://www.a-tm.co.jp/recruit/

35
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?