【LINE】LIFFアプリを試してみる~ユーザ情報の取得とトークへの送信~

## 概要

- LIFFを使ってLINEのユーザ情報の取得とLINEへのメッセージ投稿を試してみます
- [前回書いたセットアップ](https://qiita.com/ozaki25/items/5b3aedb80ab7c07618d2)が完了している前提で進めます
- [公式のスターター](https://github.com/line/line-liff-v2-starter)は特にフレームワークを使っていませんが今回は私が使い慣れたReactで試してみます

## Reactアプリの雛形生成

- create-react-appでさくっと作成します

```sh
npx create-react-app liff-sample
```

## LIFF SDKのセットアップ

- LIFF SDKはnpmからインストールする形式では提供されていないので使いやすいように一手間入れておきます
- `public/index.html`にLIFF SDKの読み込み処理を追加
    - しれっとそれ以外も手を加えてますがそのままでも影響ありません

```html:public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>LIFF App</title>
  </head>
  <body>
    <div id="root"></div>
    <!-- LIFF SDKの読み込みを追加  -->
    <script src="https://static.line-scdn.net/liff/edge/2.1/sdk.js"></script>
  </body>
</html>
```

- グローバルに定義された`liff`をexportするファイルを追加
    - そのままグローバルな`liff`を使ってもいいですがimportして使えるようにします
- `src/lib/liff.js`を作成

```js:src/lib/liff.js
export const liff = window.liff;
```

- これで`import { liff } from './lib/liff'`のような形でimportできるようになりました

## LIFF APIへのアクセス処理を追加
### 実装

- まずはliffのinitialize処理を書きます
- 使いやすいようにCustomHooks化してしまいます
- `src/hooks/useLiff.js`を作成します
    - `initLiff`メソッドがちょっと長いですが例外処理を書いているだけでtryの中に注目してください

```js:src/hooks/useLiff.js
import { useState, useEffect } from 'react';
import { liff } from '../lib/liff';

function useLiff({ liffId }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const initLiff = async ({ liffId }) => {
    setLoading(true);
    try {
      // LIFF APIのinitを呼び出して初期化
      await liff.init({ liffId });
      alert('success liff init');
    } catch (error) {
      alert({ error });
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  // useLiffが呼ばれたらinitialize処理を実行する
  useEffect(() => {
    initLiff({ liffId });
  }, [liffId]);

  return { loading, error };
}

export default useLiff;
```

- `useLiff`をReactアプリのエントリーポイントである`src/App.js`に適用します

```jsx:src/App.js
import React from 'react';
import useLiff from './hooks/useLiff';

// LIFF IDを設定(後述)
const liffId = '1234567890-abcedfgh';

function App() {
  const { loading, error } = useLiff({ liffId });

  if (loading) return <p>...loading</p>;
  if (error) return <p>{error.message}</p>;

  return (
    <div>
      <h1>Hello LIFF</h1>
    </div>
  );
}

export default App;
```

- LIFF IDはURLスキームの値です
    - `line://app/1234567890-abcedfgh`の`1234567890-abcedfgh`の部分
    - 環境変数で埋め込んでもよいですが説明の簡略化のためここでは直接書いてしまいます

### 動作確認

- いったんここまでで動きを確認してみます
- 確認するためにはhttpsの環境にデプロイする必要があるのでお好きなホスティングサービスにデプロイしてください
    - [Now](https://now.sh)や[Netlify](https://www.netlify.com/)であれば無料であっという間にデプロイできます
- デプロイできたら[LINE DeveloperのWebコンソール](https://developers.line.biz/console/profile)でURLを設定します
    - 前回の記事で暫定でQiitaのURLを設定してたやつを差し替えます
- LIFFの設定までたどって「エンドポイントURL」を変更します

<img width="592" alt="スクリーンショット 2020-03-22 3.52.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/b4c9971f-fc1d-a481-d76f-560e40dd501d.png">

- 設定を反映できたらLINEからアクセスしてみましょう
- うまくいけば動画のようにinitがsuccessするはずです

![init.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/c643f562-1c9f-4511-997b-367305d4fd98.gif)



## ユーザ情報の取得
### 実装

- ログインまで確認できたので次はユーザ情報を取得してみます
- `src/hooks/useLiff.js`に処理を追加します

```js:src/hooks/useLiff.js
import { useState, useEffect } from 'react';
import { liff } from '../lib/liff';

function useLiff({ liffId }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  // 追加
  const [profile, setProfile] = useState(null);

  const initLiff = async ({ liffId }) => {
    // 省略
  };

  // 追加
  const fetchProfile = async () => {
    setLoading(true);
    try {
      // LIFF APIのgetProfileを実行し結果をセット
      setProfile(await liff.getProfile());
    } catch (error) {
      console.log({ error });
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    initLiff({ liffId });
  }, [liffId]);

  // 修正
  return { loading, error, fetchProfile, profile };
}

export default useLiff;
```

- `src/App.js`に適用します
    - ボタンを押すと通信処理が走り情報を取得します
    - 取得できる情報は以下の4つです
        - ユーザID
        - 表示名
        - プロフィール画像のURL
        - ステータスメッセージ（設定されてなければ何も返されない）
    - 今回は画面に出しておしまいですがユーザIDをサーバに送ってキー情報として使うイメージですね

```jsx:src/App.js
import React from 'react';
import useLiff from './hooks/useLiff';

// 自身のLIFF IDを設定
const liffId = '1234567890-abcedfgh';

function App() {
  // 修正
  const { loading, error, profile, fetchProfile } = useLiff({ liffId });

  if (loading) return <p>...loading</p>;
  if (error) return <p>{error.message}</p>;

  return (
    <div>
      <h1>Hello LIFF</h1>
      {/* 追加 */}
      <section>
        {/* ボタンをクリックしたらfetchProfileを実行 */}
        <button onClick={() => fetchProfile()}>Get Profile</button>
        {/* 取得したProfileを表示 */}
        {profile && (
          <div>
            <p>UserID: {profile.userId}</p>
            <p>DisplayName: {profile.displayName}</p>
            <p>
              Picture: <br />
              <img src={profile.pictureUrl} height="300" width="300" />
            </p>
            {profile.statusMessage && <p>StatusMessage: {profile.statusMessage}</p>}
          </div>
        )}
      </section>
    </div>
  );
}

export default App;
```

### 動作確認

- ここまでできたらデプロイして取得できることを確認します
- うまくいけば取得した情報が表示されるはずです

![profile.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/4e7611d5-0c83-100d-8c19-61d41383c148.gif)


## メッセージの送信

### 実装

- 最後にLINEのトークへのメッセージの送信を実装します
- `src/hooks/useLiff.js`に関数を追加します

```js:src/hooks/useLiff.js
import { useState, useEffect } from 'react';
import { liff } from '../lib/liff';

function useLiff({ liffId }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [profile, setProfile] = useState(null);

  const initLiff = async ({ liffId }) => {
    // 省略
  };

  const fetchProfile = async () => {
    // 省略
  };

  // 送信する内容を引数で受け取る
  const sendMessage = async ({ text }) => {
    setLoading(true);
    try {
      // LIFF APIのsendMessagesを実行
      liff.sendMessages([{ type: 'text', text }]);
      console.log(`success send message: ${text}`);
    } catch (error) {
      console.log({ error });
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    initLiff({ liffId });
  }, [liffId]);

  // 修正
  return { loading, error, fetchProfile, profile, sendMessage };
}

export default useLiff;
```

- `src/App.js`に適用します
- 送信内容入力するフォームも合わせて設置しておきます

```jsx:src/App.js
// useStateを追加
import React, { useState } from 'react';
import useLiff from './hooks/useLiff';

// 自身のLIFF IDを設定
const liffId = '1234567890-abcedfgh';

function App() {
  // 修正
  const { loading, error, profile, fetchProfile, sendMessage } = useLiff({ liffId });
  // メッセージのstateを追加
  const [text, setText] = useState('');

  if (loading) return <p>...loading</p>;
  if (error) return <p>{error.message}</p>;

  return (
    <div>
      <h1>Hello LIFF</h1>
      <section>
        <button onClick={() => fetchProfile()}>Get Profile</button>
        {profile && (
          <div>
            <p>UserID: {profile.userId}</p>
            <p>DisplayName: {profile.displayName}</p>
            <p>
              Picture: <br />
              <img src={profile.pictureUrl} height="300" width="300" />
            </p>
            {profile.statusMessage && (
              <p>StatusMessage: {profile.statusMessage}</p>
            )}
          </div>
        )}
      </section>
      {/* メッセージの入力域と送信ボタンを追加 */}
      <section>
        <input onChange={e => setText(e.target.value)} />
        <button onClick={() => sendMessage({ text })}>Send</button>
      </section>
    </div>
  );
}

export default App;
```

### 動作確認

- 再度デプロイして試してみますよう
- うまくいけばメッセージが送信されているはずです！

![send.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/cc8e91f2-e001-c49c-3c88-fff31c57f066.gif)


## まとめ

- 今回はLIFFアプリを作ってユーザ情報の取得とトークへのメッセージ送信を実装しました
- この2つができればLIFFの恩恵は受けられそうですね
- どんなユースケースで活用できそうか考えてみようと思います

## コード

- この記事の完成系のコードはこちらにも置いてあります
- https://github.com/ozaki25/liff-sample/tree/send-message