2018年7月10日火曜日

React Native(Expo)を使って、Twitterのいいねを検索するためのアプリを作ったので諸々晒す的なお話

  • このエントリーをはてなブックマークに追加

Twitterの公式検索の機能ではいいねを検索する機能はないわけで。
過去に自分がどういういいねをしていたのか、またいいねしたんだけどいつのツイートをいいねしたんだっけ?みたいなことってあるわけで。
そんなときに検索ができるアプリがあったらいいな〜と思って作ってみた。

ちなみにReact NativeでExpoを使って実装。
さらに自分が作ったreact-native-simple-twitterを使っている。
ということで今日はExpoのどういうコンポーネントやらを使ってたり、npmのパッケージを使っているかどうか的なお話をば。

作ったアプリはこちらからダウンロードできる的な。
・iOS:いいね検索
・Android:いいね検索

■アプリの概要

・いいねを検索できる
・マルチアカウントに対応
・オフラインでも検索できる
こんな感じのアプリ

■package.json(2018/07/10現在)

{
  "name": "app",
  "main": "node_modules/expo/AppEntry.js",
  "private": true,
  "scripts": {
    "test": "node ./node_modules/jest/bin/jest.js --watch",
    "dev": "npm-run-all -s dev:*",
    "dev:run": "exp publish --release-channel development",
    "dev:finish": "npm run switch:development",
    "publish": "npm-run-all -s publish:*",
    "publish:run": "exp publish --release-channel production",
    "publish:finish": "npm run switch:development",
    "ios": "npm-run-all -s ios:*",
    "ios:run": "exp build:ios --release-channel production",
    "ios:file": "npm run get:ipa",
    "android": "npm-run-all -s android:*",
    "android:run": "exp build:android --release-channel production",
    "android:finish": "npm run get:apk",
    "fastlane:ios": "fastlane deliver --verbose --ipa ${PWD}/.build/app.ipa --skip_screenshots --skip_metadata",
    "get:ipa": "mkdir -p .build && wget \"$(exp url:ipa)\" -O .build/app.ipa",
    "get:apk": "mkdir -p .build && wget \"$(exp url:apk)\" -O .build/app.apk"
  },
  "dependencies": {
    "@expo/vector-icons": "6.3.1",
    "ex-react-native-i18n": "0.0.4",
    "expo": "^28.0.0",
    "expo-analytics": "^1.0.7",
    "linkify-it": "^2.0.3",
    "react": "16.4.1",
    "react-native": "https://github.com/expo/react-native/archive/sdk-28.0.0.tar.gz",
    "react-native-expo-image-cache": "^3.1.1",
    "react-native-hyperlink": "0.0.14",
    "react-native-image-pan-zoom": "^2.1.6",
    "react-native-local-mongodb": "^2.1.1",
    "react-native-simple-twitter": "^2.0.2",
    "react-native-swiper": "^1.5.13",
    "react-navigation": "^2.5.3",
    "react-navigation-redux-helpers": "^2.0.2",
    "react-redux": "^5.0.7",
    "redux": "^4.0.0",
    "sentry-expo": "^1.9.0"
  },
  "devDependencies": {
    "babel-eslint": "^8.2.5",
    "eslint": "^5.0.1",
    "eslint-config-airbnb": "^17.0.0",
    "eslint-plugin-import": "^2.13.0",
    "eslint-plugin-jsx-a11y": "^6.0.3",
    "eslint-plugin-react": "^7.10.0",
    "npm-run-all": "^4.1.3"
  }
}

■app.json

{
  "expo": {
    "name": "LikedSearch",
    "description": "No description",
    "slug": "xxxxxxxxxxxxx",
    "privacy": "unlisted",
    "sdkVersion": "28.0.0",
    "platforms": [
      "ios",
      "android"
    ],
    "version": "1.0.1",
    "orientation": "portrait",
    "primaryColor": "#FF9800",
    "extra": {
      "white": "#fafafa",
      "backgroundColor": "#efefef",
      "linkColor": "#003569",
      "textColor": "#333",
      "twitter": {
        "consumerKey": "xxxxxxxxxxxxx",
        "consumerKeySecret": "xxxxxxxxxxxxx"
      },
      "ga": "UA-xxxxxxxxxxxxx-x",
      "admob": {
        "ios": "ca-app-pub-xxxxxxxxxxxxx/xxxxxxxxxxxxx",
        "android": "ca-app-pub-xxxxxxxxxxxxx/xxxxxxxxxxxxx"
      }
    },
    "locales": {
      "ja": "./languages/ja.json"
    },
    "icon": "./assets/images/icon-circle.png",
    "splash": {
      "image": "./assets/images/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#fafafa"
    },
    "ios": {
      "buildNumber": "3",
      "icon": "./assets/images/icon.png",
      "bundleIdentifier": "xxxxxxxxxxxxx",
      "appStoreUrl": "https://itunes.apple.com/app/id1404261438?mt=8",
      "infoPlist": {
        "CFBundleDisplayName": "LikedSearch",
        "CFBundleLocalizations": [
          "en",
          "ja"
        ]
      }
    },
    "android": {
      "versionCode": 3,
      "icon": "./assets/images/icon-circle.png",
      "package": "xxxxxxxxxxxxx",
      "playStoreUrl": "https://play.google.com/store/apps/details?id=jp.ewaf.likedsearch.android",
      "permissions": []
    },
    "updates": {
      "enabled": false
    },
    "hooks": {
      "postPublish": [
        {
          "file": "sentry-expo/upload-sourcemaps",
          "config": {
            "organization": "xxxxxxxxxxxxx",
            "project": "xxxxxxxxxxxxx",
            "authToken": "xxxxxxxxxxxxx"
          }
        }
      ]
    }
  }
}

ios.CFBundleLocalizationslocalesが重要。
このアプリではあまり効果を発揮しないがこれらを設定することで、
カメラを使う/位置情報を使うなどパーミッションの文言を言語ごとに設定することができる。
完全なる言語対応がこれにてようやくできるようになる的な。

■使っているExpo Component

・Font(カスタムフォントの読み込み)
・Video(動画の再生)
・KeepAwake(ツイードダウンロード時のスリープ防止)
・WebBrowser(リンククリック時に開くアプリ内ブラウザ)
・StoreReview(SKStoreReviewControllerの表示)
・FileSystem(動画キャッシュ) ・Sentry(jsのクラッシュレポーティング)
・Admob(広告表示)
28.0.0からStoreReviewに対応したので、これが地味に便利。

■使っているnpmパッケージ

・@expo/vector-icons(アイコン)
・ex-react-native-i18n(多言語対応)
・expo-analytics(Google Analytics)
・react-native-hyperlink(文字列をHyperLinkにする)
・linkify-it(react-native-hyperlinkの拡張のため)
・react-native-expo-image-cache(画像キャッシュ)
・react-native-image-pan-zoom(画像の拡大縮小)
・react-native-local-mongodb(ツイート/アカウントデータの保存)
・react-native-simple-twitter(jsのみでTwitterにアクセスできるクライアントライブラリ)
・react-native-swiper(ウォークスルー)
・sentry-expo(Sentryを走らせるため)
reduxとかeslintは当然のものなので説明は無し。
linkify-itの設定が地味に辛かった。

ってな感じで、これらで作りましたよ的な。
難しいところとか諸々あったわけだけど、それらをブログに書くのは面倒なので今回はパス。
細かいコードなどを知りたい人がいたら個別にご飯とかでもしながら教えたい的なみたいな。

ちなみにこちらのイベントで「事例で見るExpoプロダクションアプリ」というお題目で発表させてもらったときに、このようにExpoのComponentなどを晒すのが公表だったので書いてみた的な。

アプリを作るのは諸々面倒なことが多いしどういうアプリでどういうものを使っているのかがわかりづらかったりするわけで。
iOSやAndroidのネイティブの開発よりもReact NativeおよびExpoの日本語情報ははるかに少ないので参考になればと思って的なみたいな感じ。

Adsense