Help us understand the problem. What is going on with this article?

Twitter APIをExpoでデタッチせず、かつAuth0を使わず、javascriptのみで叩く

More than 1 year has passed since last update.

こちらはReact Nativeアドベントカレンダー 17日目の記事になります。
空いていたので勝手に書いてしまってすみません。。。

自己紹介

こんにちは!
いつもは適当なブログにつらつらと仕事で使ったことなどを投稿しているのですが、年末だしアドベントカレンダーあるからと思ってQiitaに書いてみることにしました。
ハムカツおじさん🤘という名前でTwitterやってます。
g&hという会社でエンジニアをやっていて、サーバだったりフロントだったり、アプリだったりのなんでも屋です。

はじめに

自分はReact NativeというかExpoを使ってアプリを複数作っています。
ちなみに最近出した使い道がよくわからないアプリはこちらです。

Expoは便利なんですが、使っていると限界というのがそろそろ見えてきたなぁと思いつつも、割と便利なので離れづらいなぁと思っています。

で、最近の悩みがソーシャル系のAPI関連が強くないなと。
Facebookに関してはExpoのモジュールがあるんですが、Twitterなど他のものに対応をしていない状況です。
対応していないというかAuth0を使って構築してねという形であり、そうなると無料枠を超えてしまったらどうなってしまうんだ?と心配になってしまいます。
ちなみにFirebaseのAuthenticationにも現状対応していないので困りますね。
さらにいうとAPIクライアントが全然見つからない…

とりわけ今作っているアプリはTwitterのAPIを使うものなので、TwitterのAPIだけとりあえず叩ければ大丈夫だろうということで、NativeModulesも使用せず、Auth0も使用しないAPIクライアントを作ることにしました。

もちろんExpoを使っていない通常のReact Nativeプロジェクトでも動きます。

作ったもの

ちっちゃいですがプレビューはこちらです。
1513930944.gif

ソースコードはこちら。
https://github.com/watanabeyu/react-native-simple-twitter

Expo上で動くサンプルはこちら。
https://expo.io/@watanabe_yu/react-native-simple-twitter-example

どうやって設置するの?

インストール

npm install react-native-simple-twitter --save

まずはインストールしてください。

App.jsにてキーとシークレットをセットする

App.js
import React from 'react'
import { AppLoading, Asset, Font, Constants } from 'expo'
import Navigation from 'app/src'

/* npm */
import twitter from 'react-native-simple-twitter'

export default class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      isLoadingComplete: false
    }
  }

  render() {
    if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
      return (
        <AppLoading
          startAsync={this._loadResourcesAsync}
          onError={(error) => console.warn(error)}
          onFinish={() => this.setState({ isLoadingComplete: true })}
        />
      )
    }
    else {
      return <Navigation />
    }
  }

  _loadResourcesAsync = async () => {
    return Promise.all([
      Asset.loadAsync([
        require('app/assets/images/icon.png'),
        require('app/assets/images/ok_man.png')
      ]),
      twitter.setConsumerKey(Constants.manifest.extra.twitter.consumerKey, Constants.manifest.extra.twitter.consumerKeySecret)
    ])
  };
}

最初にTwitterアプリのキーとシークレットをセットしてあげます。
App.jsじゃなくても大丈夫ですが、App.jsの方がわかりやすいかと。

ログイン画面にボタンを設置する

LoginScreen.js
import React from 'react'
import {
  View,
  Text,
  Alert,
  StyleSheet
} from 'react-native'
import { NavigationActions } from 'react-navigation'
import { connect } from 'react-redux'
import { Constants } from 'expo'

/* import twitter */
import twitter, { TWLoginButton } from 'react-native-simple-twitter'

@connect(
  state => ({
    user: state.user
  })
)
export default class LoginScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    const { state, setParams } = navigation
    const { params = {} } = navigation.state

    return {
      header: null
    }
  }

  constructor(props) {
    super(props)

    this.state = {
      isVisible: false,
      authUrl: null
    }
  }

  async componentWillMount() {
    if (this.props.user.token) {
      twitter.setAccessToken(this.props.user.token, this.props.user.token_secret)

      try {
        const user = await twitter.get("account/verify_credentials.json", { include_entities: false, skip_status: true, include_email: true })
        this.props.dispatch({ type: "USER_SET", user: user })

        this.props.dispatch(NavigationActions.reset({
          index: 0,
          actions: [
            NavigationActions.navigate({ routeName: 'Home' })
          ]
        }))
      } catch (err) {
        console.log(err)
      }
    }
  }

  onGetAccessToken = ({ oauth_token, oauth_token_secret }) => {
    this.props.dispatch({ type: "TOKEN_SET", token: oauth_token, token_secret: oauth_token_secret })
  }

  onSuccess = (user) => {
    this.props.dispatch({ type: "USER_SET", user: user })

    Alert.alert(
      "Success",
      "ログインできました",
      [
        {
          text: 'Go HomeScreen',
          onPress: () => {
            this.props.dispatch(NavigationActions.reset({
              index: 0,
              actions: [
                NavigationActions.navigate({ routeName: 'Home' })
              ]
            }))
          }
        }
      ]
    )
  }

  onClose = (e) => {
    console.log("press close button")
  }

  onError = (err) => {
    console.log(err)
  }

  render() {
    return (
      <View style={styles.container}>
        <View style={styles.title}>
          <Text style={styles.titleText}>Login</Text>
        </View>
        <TWLoginButton headerColor={Constants.manifest.primaryColor}
          containerStyle={styles.loginContainer}
          style={styles.loginButton}
          textStyle={styles.loginButtonText}
          onGetAccessToken={this.onGetAccessToken}
          onSuccess={this.onSuccess}
          closeText="閉じる"
          closeTextStyle={styles.loginCloseText}
          onClose={this.onClose}
          onError={this.onError}>Twitter IDではじめる</TWLoginButton>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Constants.manifest.primaryColor
  },
  title: {
    flex: 1,
    padding: 64
  },
  titleText: {
    textAlign: "center",
    fontSize: 24,
    color: "#fff",
    fontWeight: "bold"
  },
  loginContainer: {
    paddingHorizontal: 32,
    marginBottom: 64,
    backgroundColor: "transparent"
  },
  loginButton: {
    backgroundColor: "#fff",
    paddingVertical: 16,
    borderRadius: 64,
    overflow: "hidden"
  },
  loginButtonText: {
    color: Constants.manifest.primaryColor,
    fontSize: 16,
    fontWeight: "bold",
    textAlign: "center"
  },
  loginCloseText: {
    color: "#fff",
    fontWeight: "bold"
  }
})

そしてTWLoginButtonを設置してあげれば、ツイッターログインができます。
ちなみにトークンを取得した際、およびログインが成功した際にコールバックを設定することができるので、その中でreduxに投げてあげるということもできます。

なおこのクライアント自体シングルトンで作っているので、ログインしたらトークンはアプリ自体が切れるまでは有効です。
なのでトークンを取得した際にAsyncStorageに保存しておくとかして、アプリ立ち上げ時にAsyncStorageからトークンを取得していつでもAPIを叩ける状況にしておくことも可能です。

サンプルURLはこちらです。
https://github.com/watanabeyu/react-native-simple-twitter/tree/master/example

ライブラリ構成は下記になります。
* react-navigation
* react-redux
* redux

苦労したところ

OAuth1.0のsignatureを作るのがなかなか面倒でした。
パラメーターを全てエンコードして文字列にして、hashしてbase64をし、さらに云々という形で地味に大変だなぁと。
文字列にする際にパラメーターのkeyの順番が影響してきたり、さらに半角スペースや他の文字のエンコードもちゃんとしてあげないとダメでした。

まとめ

つらつらと自分の作ったAPIクライアントおよびコンポーネントの紹介となってしまいましたが、何かを介せずにTwitterのAPIを叩けるというのは便利なんじゃないかと個人的には思っています。

使ってくれる優しい方がおりましたら、ぜひ使ってフィードバックをください。
せこせこと改善していきたいと思います。

watanabe_yu
サーバだったり、フロントだったり、アプリだったり。 Qiitaではアドベントカレンダーしか書かないおじさん。
http://www.bad-company.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした