React Nativeを利用してチャート、グラフをスマホの画面上に描きたいというReact Native入門者向けのチュートリアルになっています。チャートを描くために利用するデータはなんでも構いませんが仮想通貨の変動価格を利用します。仮想通貨の情報、価格の変動の情報は外部から取得する必要があり本文書ではCoingeckoのAPIを利用します。Callの回数の制限などありますがAPIを利用するためにアカウント登録も必要なく無料で利用できるため学習向けには最適なAPIのサービスです。

画面上に仮想通貨のチャートを描くためReact Native Animated Chartsライブラリを利用します。

本文書ではReact Nativeを利用したアプリ開発にExpoを利用しており、Expoが利用できる環境が事前に構築済みであることを前提に進めています。macOSでのExpo環境の構築とReact Nativeの基礎を学習したい場合は下記の文書が参考になります。

本文書に沿っていくと最終的には下記の画面のようなアプリケーションを作成することができます。チャートの表示のみの実装ですがCoin Checkのモバイルアプリの初期画面を参考にさせていただいています。

表示直後の画面
最終的な画面

React Nativeプロジェクトの作成

expoコマンドを利用してReact Nativeプロジェクトの作成を行います。expo initコマンドを実行するとテンプレートの選択を行うことができますが本文書では”blank”を選択します。プロジェクト名は任意なので好きな名前をつけてください。ここではreact-native-chartという名前をつけています。


 % expo init react-native-chart

インストールが完了したらreact-native-chartフォルダに移動してexpo startコマンドを実行して開発サーバを起動してください。

本文書ではiOSシュミレーターを利用して動作確認を行なっていきます。

CoinGecko APIを利用したデータ取得

最初にCoinGecko APIを利用した仮想通貨に関連するデータの取得方法を理解する必要があります。CoinGeckoのAPIのページ(https://www.coingecko.com/en/api)にアクセスします。APIの利用方法を確認するためにページ中央にある”Explore Docs”をクリックします。

CoinGeckoのAPIのページ
View PlansをクリックするとFreeプランを確認することができます。Freeプランは1分間に50コールという制限があります。
fukidashi

Explore Docsをクリックして移動したページをスクロールするとCoinGecko API V3のエンドポイントのURLを確認することができます。React Nativeを利用しなくてもこのページ内でどのような情報が取得できるか確認することができます。

APIのendopointの一覧
APIのendopointの一覧

仮想通貨のリストを取得したい場合はcoinsにある/coins/listのメニューを開きます。開いたら”Try it out”ボタンをクリックしてください。

仮想通貨の一覧を取得
仮想通貨の一覧を取得

“Try it out”ボタンをクリックすると画面の横一杯に広がっている”Excute”ボタンをクリックすることで実行することができます。ボタンをクリックしてください。

/coins/listからの情報取得の実行
/coins/listからの情報取得の実行

実行されるまでに時間がかかりますがResponsesにはcurlコマンドを利用した実行方法、Request URLとJSONで戻された仮想通貨の一覧が表示されます。ダウンロードボタンを押すと実行結果が含まれるJSONファイルをダウンロードすることができます。かなりの数の仮想通貨があることがわかります。

仮想通貨一覧の実行結果
仮想通貨一覧の実行結果

仮想通貨の個別の情報を取得する際にidの情報が必要になります。idは表示されている仮想通貨のリスト一覧のidから確認することができます。例えば最も有名なビットコインであればidは”bitcoin”です。最初はbitcoinを利用します。


  {
    "id": "bitcoin",
    "symbol": "btc",
    "name": "Bitcoin"
  },

RequestのURLからhttps://api.coingecko.com/api/v3/までは共通でその後ろに/coins/listや/coins/marketsなどのURLが続くことがわかります。

仮想通貨の一覧表示

画面上に仮想通貨のアイコン、名前、現在の価格、24hでの変動率を表示させるためにURLの/coins/marketsを利用します。情報を取得するために利用するURLは下記となります。


https://api.coingecko.com/api/v3/coins/markets?vs_currency=jpy&order=market_cap_desc&per_page=10

vs_currencyをjpyに設定することで日本円で金額を表示してくれます。orderにmarket_cap_descを設定することで時価総額の高い順に取得する設定にしています。取得する仮想通貨の情報の数を10に設定しています。10に特に利用はないので好きな数を設定してください。

App.jsファイルにgetCoins関数を追加して/coins/marketsから仮想通貨の情報を取得します。getCoins関数はuserEffect Hookの中で実行します。どのような情報が取得できるか確認するために取得した情報はconsole.logでコンソールに表示されます。


import React, { useState, useEffect } from 'react';
//略
export default function App() {
  const getCoins = async () => {
    const response = await fetch(
      `https://api.coingecko.com/api/v3/coins/markets?vs_currency=jpy&order=market_cap_desc&per_page=10`
    );
    const data = await response.json();
    console.log(data);
  };

  useEffect(() => {
    getCoins();
  }, []);

  return (
//略

App.jsを更新して保存するとexpo startを実行したコンソールに配列が表示されます。配列にはオブジェクトとして10件分の仮想通貨の情報が含まれています。

これまでで一番高かかった価格(ath)や現在の価格(current_price)などが含まれていますがidまたはnameを見るとBitcoinに関する情報であることがわかります。画面上に仮想通貨の情報を表示させるためname, image, current_price, symbol, price_change_percentage_24hプロパティを利用します。imageはpng画像のURLで仮想通貨のアイコンに利用します。結果にはidも含まれています。


Array [
  Object {
    "ath": 7828814,
    "ath_change_percentage": -26.01177,
    "ath_date": "2021-11-10T14:24:11.849Z",
    "atl": 6641.83,
    "atl_change_percentage": 87110.89213,
    "atl_date": "2013-07-05T00:00:00.000Z",
    "circulating_supply": 18893800,
    "current_price": 5794881,
    "fully_diluted_valuation": 121692502751489,
    "high_24h": 5831721,
    "id": "bitcoin",
    "image": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579",
    "last_updated": "2021-12-07T08:08:33.105Z",
    "low_24h": 5349909,
    "market_cap": 109487324213623,
    "market_cap_change_24h": 5263838213708,
    "market_cap_change_percentage_24h": 5.05053,
    "market_cap_rank": 1,
    "max_supply": 21000000,
    "name": "Bitcoin",
    "price_change_24h": 248691,
    "price_change_percentage_24h": 4.48399,
    "roi": null,
    "symbol": "btc",
    "total_supply": 21000000,
    "total_volume": 4120399672776,
  },
  Object {
//略

画面に表示さるためにuseState Hookでcoins変数を定義します。取得したデータをcoinsに保存してFlatListコンポーネントを利用してリスト表示します。


import React, { useState, useEffect } from 'react';
import { FlatList, SafeAreaView, Text, View } from 'react-native';

export default function App() {
  const [coins, setCoins] = useState('');
  const getCoins = async () => {
    const response = await fetch(
      `https://api.coingecko.com/api/v3/coins/markets?vs_currency=jpy&order=market_cap_desc&per_page=10`
    );
    const data = await response.json();
    setCoins(data);
  };

  useEffect(() => {
    getCoins();
  }, []);

  return (
    <SafeAreaView>
      <FlatList
        data={coins}
        renderItem={({ item }) => (
          <View>
            <Text>{item.name}</Text>
          </View>
        )}
        keyExtractor={(item) => item.id}
      />
    </SafeAreaView>
  );
}

画面上に10件分の仮想通貨の名前を表示させることができました。

仮想通貨の名前をリスト表示
仮想通貨の名前をリスト表示

仮想通貨のアイコンを表示

imageプロパティに入っていたURLを利用して仮想通貨の画像が表示されるか確認します。画像を表示するためにFlatListの中でImageコンポーネントを利用します。


<FlatList
  data={coins}
  renderItem={({ item }) => (
    <View>
      <Image
        source={{ uri: item.image }}
        style={{ width: 25, height: 25 }}
      />
      <Text>{item.name}</Text>
    </View>
  )}
  keyExtractor={(item) => item.id}
/>

画面上にはリストとして仮想通貨の画像と名前が表示されるようになりました。

仮想通貨の画像を表示
仮想通貨の画像を表示

仮想通貨情報の表示の整形

flexDirection, alignItems, padding, fontSize, fontWeightを利用して表示を変更します。特別難しい設定はしていないのでstyleを見れば何をしているかがわかるかと思います。


<FlatList
  data={coins}
  renderItem={({ item }) => (
    <View
      style={{
        flexDirection: 'row',
        justifyContent: 'space-between',
        padding: 15,
      }}
    >
      <View style={{ flexDirection: 'row', alignItems: 'center' }}>
        <Image
          source={{ uri: item.image }}
          style={{ width: 25, height: 25 }}
        />
        <View style={{ paddingLeft: 10 }}>
          <Text style={{ fontSize: 18, fontWeight: 'bold' }}>
            {item.name}
          </Text>
          <Text>{item.symbol}</Text>
        </View>
      </View>
      <View>
        <Text style={{ fontSize: 18, fontWeight: 'bold' }}>
          ¥{item.current_price}
        </Text>
        <Text style={{ textAlign: 'right' }}>
          {item.price_change_percentage_24h}
        </Text>
      </View>
    </View>
  )}
  keyExtractor={(item) => item.id}
/>

10件分の仮想通貨の情報が画面上に表示できるようになりました。随時価格は変動しているので同じではありませんがネット上の別のサイトからビットコインの価格を検索すると表示されている価格とほぼ同じ価格であることが確認できるかと思います。

スタイルの設定
スタイルの設定

ここまでの設定で画面に仮想通貨の情報が表示できるようになりました。

価格情報の取得

仮想通貨の情報が取得できたのでチャートに利用する価格の履歴データを取得します。価格のデータ取得のリクエストURLは/coins/{id}/market_chartです。{id}には仮想通貨の情報を取得した際に含まれているidを利用します。動作確認のためにidにはbitcoinを設定します。

App.jsにgetCoinsと同様の方法でgetPrice関数を追加します。取得する値にはpriceとmarket capの値が含まれており今回はpriceに注目しているのでdata.pricesで価格の値のみコンソールに表示させています。


const getPrices = async () => {
  const response = await fetch(
    'https://api.coingecko.com/api/v3/coins/bitcoin/market_chart?vs_currency=jpy&days=7'
  );
  const data = await response.json();
  console.log(data.prices);
};
useEffect(() => {
  getCoins();
  getPrices();
}, []);

コンソールを見ると配列の中に2つの値が入っています。一つは価格ですが、もう一つはunixタイムスタンプで変換を行うと7日前の日付であることが確認できます。


Array [
  Array [
    1638277255734,
    6549004.934754277,
  ],
  Array [
    1638280884607,
    6575403.384803329,
  ],

コンソールで価格の確認後、価格データを保存するために変数pricesを定義します。getPrices関数の中で取得した価格データをpricesに設定します。


const [prices, setPrices] = useState('');
//略
const getPrice = async () => {
  const response = await fetch(
    'https://api.coingecko.com/api/v3/coins/bitcoin/market_chart?vs_currency=jpy&days=7'
  );
  const data = await response.json();
  setPrices(data.prices);
};

価格の保存設定を行った後、価格の値をチャートにするためにチャートライブラリのインストールを行なっていきます。

チャートライブラリのインストール

React Nativeに対応したチャートライブラリもいくつかありますが本文書ではreact-native-animated-chartsを利用します。インストールはドキュメントを参考に行います。

react-native-animated-chartsをインストールするためにreact-native-reanimatedライブラリのインストールを行います。expoコマンドでインストールを行うことができます。


 % expo install react-native-reanimated

インストール後はbabel.config.jsファイルにプラグインの追加を行います。


module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['react-native-reanimated/plugin'],
  };
};

設定後は開発サーバの再起動が必要となるためexpo startを一度停止して、キャッシュをクリアするためにexpo start –clearを実行します。


 % expo start --clear
babel.config.jsファイルを更新するとexpo startを実行しているコンソールにはDetected a change in babel.config.js. Restart the server to see the new resultsが表示されます。
fukidashi

npmコマンドでreact-native-animated-chartsのインストールを行います。yarnコマンドでもインストールは可能です。


 % npm install @rainbow-me/animated-charts

チャートの表示

チャートライブラリのインストールが完了したらCoinGeckoのAPIから取得した価格データを利用して画面にチャートを表示します。

react-native-animated-chartsの設定方法についてはサンプルのコードが非常に役に立つのでサンプルコードを元に設定を行なっています。

サンプルコードはhttps://github.com/rainbow-me/react-native-animated-charts/blob/master/Example/src/GenericExample/index.jsから確認してください。
fukidashi

インストールした@rainbow-me/animated-chartsからChartPathProviderとChartPathコンポーネントをimportします。


import { ChartPathProvider, ChartPath } from '@rainbow-me/animated-charts';

ChartPathProviderにdata propsを通して価格のデータを渡します。以下はデータ形式の例ですが配列にxとyプロパティを持つオブジェクトとなります。


export const data = [
  {x: 1453075200, y: 1.47}, {x: 1453161600, y: 1.37},
  {x: 1453248000, y: 1.53}, {x: 1453334400, y: 1.54},
  ...
];

pricesに保存されている価格のデータと形式が異なるのでmap関数を利用して上記の形に変更します。price[0]にはタイムスタンプ、price[1]には価格が入ります。


prices.map((price) => ({ x: price[0], y: price[1] })

チャートを表示させる幅を設定する必要があります。画面の幅はReact NativeのDimentionsから取得することができます。Dimensionsはreact-nativeからimportする必要があります。


Dimensions.get('window')
//
Object {
  "fontScale": 1,
  "height": 844,
  "scale": 3,
  "width": 390,
}

取得する値からwidthのみ利用します。


const { width } = Dimensions.get('window');

チャートを描くために必要な最低限の情報が揃ったのでChartPathProvider, ChartPathを利用して下記のように設定します。ChartPathProviderにはpropsのdataに価格の情報を設定しています。ChartPathにはheightとwidthと表示されるチャートのサイズとstokeでチャートの線の色を設定しています。


import React, { useState, useEffect } from 'react';
import { Dimensions, SafeAreaView, View } from 'react-native';
import { ChartPathProvider, ChartPath } from '@rainbow-me/animated-charts';

export default function App() {
  const [prices, setPrices] = useState([]);

  const getPrice = async () => {
    const response = await fetch(
      'https://api.coingecko.com/api/v3/coins/bitcoin/market_chart?vs_currency=jpy&days=7'
    );
    const data = await response.json();
    setPrices(data.prices);
  };

  useEffect(() => {
    getPrice();
  }, []);

  const { width } = Dimensions.get('window');

  return (
    <SafeAreaView>
      <ChartPathProvider
        data={{
          points: prices.map((price) => ({ x: price[0], y: price[1] })),
        }}
      >
        <View>
          <ChartPath height={width / 2} width={width} stroke="green" />
        </View>
      </ChartPathProvider>
    </SafeAreaView>
  );
}
前半に作成した仮想通貨の情報のリスト表示部分のコードはチャートに注目するため一時的に非表示にしています。後ほどチャートとリスト表示のコードを一緒に利用します。
fukidashi

iOSのシュミレーターで確認すると画面上にチャートが表示されます。

ビットコインの1週間のチャート
ビットコインの1週間のチャート

bitFlyerのサイトで同じ期間のチャートを見ると同じ形状をしていることが確認できます。正しくデータが取得できチャートとして表示されていることが確認できました。

bitFlerでのビットコインのチャート
bitFlerでのビットコインのチャート

価格表示

チャートを画面上に表示させることができましたが価格が表示されていません。react-native-animated-chartsではチャートの線をタッチするとタッチした場所の価格の情報を表示させることができます。価格の表示前にチャート上のどの場所をタッチしているのかわかるようにChartDotを使って動作確認を行います。下記ではChartDotの追加を行ないstyleで背景色をgrayに設定しているのみです。


import {
  ChartPathProvider,
  ChartPath,
  ChartDot,
} from '@rainbow-me/animated-charts';
//略

return (
  <SafeAreaView>
    <ChartPathProvider
      data={{
        points: prices.map((price) => ({ x: price[0], y: price[1] })),
      }}
    >
      <View>
        <ChartPath height={width / 2} width={width} stroke="green" />
        <ChartDot style={{ backgroundColor: 'gray' }} />
      </View>
    </ChartPathProvider>
  </SafeAreaView>
);

ChartDotを設定後チャートにタッチすることでタッチした場所にgrayの丸が表示されます。

タッチした場所が丸で表示
タッチした場所が丸で表示

ChartXLabelとChartYLabelを利用することでチャート上のタッチした場所におけるxとyの値を取得することができます。本文書ではxはタイムスタンプでyは価格になります。


import {
  ChartPathProvider,
  ChartPath,
  ChartDot,
  ChartXLabel,
  ChartYLabel,
} from '@rainbow-me/animated-charts';
//略

return (
  <SafeAreaView>
    <ChartPathProvider
      data={{
        points: prices.map((price) => ({ x: price[0], y: price[1] })),
      }}
    >
      <View>
        <ChartYLabel />
        <ChartXLabel />
      </View>
      <View>
        <ChartPath height={width / 2} width={width} stroke="green" />
        <ChartDot style={{ backgroundColor: 'gray' }} />
      </View>
    </ChartPathProvider>
  </SafeAreaView>
);

ChartXLabelとChartYLabelを追加することでチャートにタッチした場合のみ価格とタイムスタンプを上部に表示することができます。チャートから手を離すと価格とタイムスタンプの表示は消えます。

タッチした場所のx, yの値を表示
タッチした場所のx, yの値を表示

チャートをタッチしていない時には現在の価格が表示できるように設定を行なっていきます。

価格の表示の整形

ChartXLabel, ChartYLabelにはformat propsを設定することができ表示させる内容を関数によって操作することができます。

ChartYLabelにformat propsを設定してformatPriceを設定します。


<ChartYLabel format={formatPrice} />

format関数の引数にはvalueが入っておりChartYLabelのvalueにはタッチした場所の価格が含まれています。画面上のチャートをタッチする度にformatPrice関数が実行されvalueが入っていない時には何も戻されず、valueに値がある場合には日本円に整形(toLocalString関数を利用)をして表示するように設定を行います。valueがblankになるのはチャートから手を離した時です。


const formatPrice = (value) => {
  'worklet';
  if (value === '') {
    return '';
  }
  return `${Number(value).toLocaleString('ja-JP', {
    style: 'currency',
    currency: 'JPY',
  })}`;
};

workletディレクティブを関数の先頭に設定していますがもしworklet;を設定しない場合には”Tried to synchronoouly call function {formPrice} from a diffrent thread”のエラーが表示されます。React Native Reanimatedを利用しているためformatPriceを実行するためにはworkletディレクティブが必要になります。workletについてはhttps://docs.swmansion.com/react-native-reanimated/docs/fundamentals/workletsに記述されています。

formatの設定によって価格の表示が変更(価格の先頭に円マークが表示し3桁ごとにコンマがついている)になっていることが確認できます。チャートにタッチしている場合は表示されますが手を外すと何も表示されません。

価格の表示をformatPriceで変更
価格の表示をformatPriceで変更

現在の価格の設定

valueがblankの時に現在の価格が表示させるために価格の情報を保持する必要があります。react-native-reanimatedのuseSharedValue Hookに値を保存します。useSharedValueでsharedPriceを定義し、getCoins関数で仮想通貨の情報を取得した後find関数でbitcoinの情報を取得して現在の価格(current_prirce)をsharedPriceに設定しています。sharedPriceに値を設定する場合はsharedPrice.valueとなります。

最初はuseStateを利用して値を保存しようとしましたがformPriceの中ではuseStateで設定した変数の値を利用することができませんでした。
fukidashi

//略
import { useSharedValue } from 'react-native-reanimated';

export default function App() {
  const [coins, setCoins] = useState([]);
  const [prices, setPrices] = useState([]);
  const sharedPrice = useSharedValue('');

  const getCoins = async () =gt; {
    const response = await fetch(
      `https://api.coingecko.com/api/v3/coins/markets?vs_currency=jpy&order=market_cap_desc&per_page=20`
    );
    const data = await response.json();
    setCoins(data);
    const coin = data.find((coin) =gt; coin.id === 'bitcoin');
    sharedPrice.value = coin.current_price;
  };

  const getPrice = async () => {
    const response = await fetch(
      `https://api.coingecko.com/api/v3/coins/bitcoin/market_chart?vs_currency=jpy&days=7`
    );
    const data = await response.json();
    setPrices(data.prices);
  };

  useEffect(() => {
    getPrice();
    getCoins();
  }, []);
  //略

useEffect内でgetCoins関数が実行されるとsharedPriceに価格が保存されるのでformatPrice関数の中でsharedPriceの値を利用します。


const formatPrice = (value) => {
  'worklet';
  if (value === '') {
    if (sharedPrice.value) {
      return sharedPrice.value.toLocaleString('ja-JP', {
        style: 'currency',
        currency: 'JPY',
      });
    }
    return '';
  }
  return `${Number(value).toLocaleString('ja-JP', {
    style: 'currency',
    currency: 'JPY',
  })}`;
};

sharedPriceを利用することでタッチしていない時には現在の価格が表示されるようになります。タッチをするとタッチした場所の価格、タッチを外すと現在の価格が表示されます。

タイムスタンプの表示設定

タイムスタンプでは時刻がわからないのでChartXLabelもformatを利用して時刻を表示できるように設定を行います。


<ChartXLabel format={formatDatetime} />

const formatDatetime = (value) => {
  'worklet';
  if (value === '') {
    return '';
  }
  const date = new Date(Number(value));
  const s = date.getSeconds().toString().padStart(2, '0');
  const m = date.getMinutes().toString().padStart(2, '0');
  const h = date.getHours().toString().padStart(2, '0');
  const d = date.getDate();
  const n = date.getMonth();
  const y = date.getFullYear();
  return `${y}/${n}/${d} ${h}:${m}:${s}`;
};

画面を見るとタイムスタンプから時刻に変更されていることが確認できます。

タイムスタンプから時刻への表示変更

チャートとリストの表示

チャートと仮想通貨の表示を別々に行なっていましたが両方一緒に表示させるために前半に作成したFlatListの部分をコードに戻します。


<SafeAreaView>
  <ChartPathProvider
    data={{
      points: prices.map((price) => ({ x: price[0], y: price[1] })),
    }}
  >
    <View>
      <ChartYLabel format={formatPrice} />
      <ChartXLabel format={formatDatetime} />
    </View>
    <View>
      <ChartPath height={width / 2} width={width} stroke="green" />
      <ChartDot style={{ backgroundColor: 'gray' }} />
    </View>
  </ChartPathProvider>
  <FlatList
    data={coins}
    renderItem={({ item }) => (
      <View
        style={{
          flexDirection: 'row',
          justifyContent: 'space-between',
          padding: 15,
        }}
      >
        <View style={{ flexDirection: 'row', alignItems: 'center' }}>
          <Image
            source={{ uri: item.image }}
            style={{ width: 25, height: 25 }}
          />
          <View style={{ paddingLeft: 10 }}>
            <Text style={{ fontSize: 18, fontWeight: 'bold' }}>
              {item.name}
            </Text>
            <Text>{item.symbol}</Text>
          </View>
        </View>
        <View>
          <Text style={{ fontSize: 18, fontWeight: 'bold' }}>
            ¥{item.current_price}
          </Text>
          <Text
            style={{
              textAlign: 'right',
            }}
          >
            {item.price_change_percentage_24h}
          </Text>
        </View>
      </View>
    )}
    keyExtractor={(item) => item.id}
  />
</SafeAreaView>

下記のようにチャートと仮想通貨のリストが表示できるようになります。

チャートと仮想通貨リストを同時に表示
チャートと仮想通貨リストを同時に表示

仮想通貨のチャート切り替え

ここまでの設定ではbitcoinのチャートのみしか表示できませんでしたがリストにある仮想通貨をタッチするとタッチした仮想通貨のチャートが表示できるように設定を行なっていきます。

bitcoinと入力した部分を変数に変更するためuseStateでcoinIdを追加します。デフォルト値はbitcoinに設定しています。


const [coinId, setCoinId] = useState('bitcoin');

これまでbitcoinという文字列で直接設定した箇所をcoinIdへ変更を行います。getPrice関数内でリクエストしていたURLを更新します。


const getPrice = async () => {
  const response = await fetch(
    `https://api.coingecko.com/api/v3/coins/${coinId}/market_chart?vs_currency=jpy&days=7`
  );
  const data = await response.json();
  setPrices(data.prices);
};

sharedPriceの値を取得する際にmap関数で利用していたbitcoinをcoinIdに変更します。


  const getCoins = async () => {
    const response = await fetch(
      `https://api.coingecko.com/api/v3/coins/markets?vs_currency=jpy&order=market_cap_desc&per_page=20`
    );
    const data = await response.json();
    setCoins(data);
    const coin = data.find((coin) => coin.id === coinId);
    sharedPrice.value = coin.current_price;
  };

TouchableOpacityをFlatListのリストに追加してリストをタッチするとonPressイベントによりcoinIdがタッチした仮想通貨のidに更新されるようにします。


<FlatList
  data={coins}
  renderItem={({ item }) => (
    <TouchableOpacity onPress={() => setCoinId(item.id)}>
      <View
        style={{
          flexDirection: 'row',
          justifyContent: 'space-between',
          padding: 15,
        }}
      >
      //略
      </View>
    </TouchableOpacity>
  )}
  keyExtractor={(item) => item.id}
/>

TouchableOpacityの追加によりリストをタッチすると半透明になりますが何も変化はありません。coinIdの変化によりuseEffect内の関数が実行されるようにuseEffectの空だった配列にcoinIdを追加します。


  useEffect(() => {
    getPrice();
    getCoins();
  }, [coinId]);

設定が完了したらBitcoin以外の仮想通貨をタッチしてください。TetherをタッチするとBitcoinとは形の異なるチャートが表示されます。左上の価格も変更されることができます。チャートをタッチしてもタッチした仮想通貨の価格が表示されます。仮想通貨のチャートの切り替えを行えるようになりました。

仮想通貨をタッチすると異なるチャートが表示
仮想通貨をタッチすると異なるチャートが表示

現在どの仮想通貨のチャートを表示しているかわかるように選択している仮想通貨の情報を保存するためにuseStateでcurrentCoin変数を追加します。


const [currentCoin, setCurrentCoin] = useState('');

currentCoinの値はgetCoins関数の中で設定します。


const getCoins = async () => {
  const response = await fetch(
    `https://api.coingecko.com/api/v3/coins/markets?vs_currency=jpy&order=market_cap_desc&per_page=20`
  );
  const data = await response.json();
  setCoins(data);
  const coin = data.find((coin) => coin.id === coinId);
  setCurrentCoin(coin);
  sharedPrice.value = coin.current_price;
};

currentCoinの値を利用して画面左上の現在の価格と同じ場所に仮想通貨のアイコンを表示させます。


<ChartPathProvider
  data={{
    points: prices.map((price) => ({ x: price[0], y: price[1] })),
  }}
>
  <Image
    source={{ uri: currentCoin.image }}
    style={{ width: 35, height: 35 }}
  />
  <View>
    <ChartYLabel format={formatPrice} />
    <ChartXLabel format={formatDatetime} />
  </View>
  <View>
    <ChartPath height={width / 2} width={width} stroke="green" />
    <ChartDot style={{ backgroundColor: 'gray' }} />
  </View>
</ChartPathProvider>

タッチすることで選択した仮想通貨のアイコンが表示されるようになります。Etherreum(イーサリアム)をタッチした時の画面です。

仮想通貨のアイコンを表示
仮想通貨のアイコンを表示

アイコンと価格を横並びにして文字の大きさや間隔を設定します。


<ChartPathProvider
  data={{
    points: prices.map((price) => ({ x: price[0], y: price[1] })),
  }}
>
  <View
    style={{
      flexDirection: 'row',
      paddingHorizontal: 10,
      paddingVertical: 20,
    }}
  >
    <Image
      source={{ uri: currentCoin.image }}
      style={{ width: 35, height: 35 }}
    />
    <View style={{ paddingLeft: 10 }}>
      <ChartYLabel
        format={formatPrice}
        style={{ fontSize: 25, fontWeight: 'bold' }}
      />
      <ChartXLabel format={formatDatetime} />
    </View>
  </View>
  <View>
    <ChartPath height={width / 2} width={width} stroke="green" />
    <ChartDot style={{ backgroundColor: 'gray' }} />
  </View>
</ChartPathProvider>
アイコンと文字を調整
アイコンと文字を調整

チャートをタッチするとタッチした場所の価格と時刻が表示されます。タッチを外すと現在の価格のみ表示されます。

チャートをタッチしている時の表示
チャートをタッチしている時の表示

表示の調整

機能の変更はありませんがリストに表示されている価格の表示などを変更するためにgetCoinsでデータの取得が完了するまで画面上に”Loading…”を表示させます。


//略
if (!currentCoin)
  return (
    <SafeAreaView>
      <Text>Loading...</Text>
    </SafeAreaView>
  );

return (
  <SafeAreaView>
    <ChartPathProvider
      data={{
        points: prices.map((price) => ({ x: price[0], y: price[1] })),
      }}
    >
//略

btc, ethなどアルファベットの小文字のsymbolをtoUpperCase関数で大文字に設定します。


<Text>{item.symbol.toUpperCase()}</Text>

価格を3桁のカンマ区切りにするためにtoLocalString関数を設定します。


<Text style={{ fontSize: 18, fontWeight: 'bold' }}>
  ¥{item.current_price.toLocaleString()}
</Text>

価格の下に表示している期間内での変化率を小数点2桁にし、マイナスの場合は文字色を赤に設定します。


<Text
  style={{
    textAlign: 'right',
    color:
      item.price_change_percentage_24h > 0 ? 'gray' : 'red',
  }}
>
  {item.price_change_percentage_24h.toFixed(2)}%
</Text>

チャートが表示されている仮想通貨の背景の色を設定します。


<FlatList
  data={coins}
  renderItem={({ item }) => (
    <TouchableOpacity onPress={() => setCoinId(item.id)}>
      <View
        style={{
          flexDirection: 'row',
          justifyContent: 'space-between',
          padding: 15,
          backgroundColor: item.id === coinId ? '#F2F2F2' : 'white',
        }}
      >

ここまでの表示設定変更を行いシュミレーターで確認すると以下のようになります。仮想通貨の名前の下のsymbolが大文字、価格は3桁区切りでコンマが入り、変動率はがマイナスだと赤で表示され小数点の2桁まで表示されています。

表示設定の変更後の画面
表示設定の変更後の画面

表示期間の設定

ここまでの設定ではチャートの期間は7日でしたがCoin GeckoのAPIでは期間の設定も可能なので1日、1週間、30日の3つで期間変更できるように設定を行います。

useStateで変数termを定義します。デフォルト値は7を設定します。


const [term, setTerm] = useState(7);

getPrice関数のリクエストURLのパラメータdaysによって期間の設定を行っているのでdaysを7からtermに変更します。


const getPrice = async () => {
  const response = await fetch(
    `https://api.coingecko.com/api/v3/coins/${coinId}/market_chart?vs_currency=jpy&days=${term}`
  );
  const data = await response.json();
  setPrices(data.prices);
};

チャートとリストの間に期間を切り替えるためのボタンを追加します。


//略
</ChartPathProvider>
<View
style={{
  flexDirection: 'row',
  justifyContent: 'space-around',
  marginVertical: 20,
}}
>
<TouchableOpacity
  onPress={() => setTerm(1)}
  style={{
    paddingVertical: 5,
    paddingHorizontal: 20,
    borderRadius: 5,
    backgroundColor: term === 1 ? '#91B493' : 'white',
  }}
>
  <Text
    style={{
      color: term === 1 ? 'white' : '#91B493',
      fontWeight: 'bold',
    }}
  >
    1日
  </Text>
</TouchableOpacity>
<TouchableOpacity
  onPress={() => setTerm(7)}
  style={{
    paddingVertical: 5,
    paddingHorizontal: 20,
    borderRadius: 5,
    backgroundColor: term === 7 ? '#91B493' : 'white',
  }}
>
  <Text
    style={{
      color: term === 7 ? 'white' : '#91B493',
      fontWeight: 'bold',
    }}
  >
    1週
  </Text>
</TouchableOpacity>
<TouchableOpacity
  onPress={() => setTerm(30)}
  style={{
    paddingVertical: 5,
    paddingHorizontal: 20,
    borderRadius: 5,
    backgroundColor: term === 30 ? '#91B493' : 'white',
  }}
>
  <Text
    style={{
      color: term === 30 ? 'white' : '#91B493',
      fontWeight: 'bold',
    }}
  >
    1月
  </Text>
</TouchableOpacity>
</View>
<FlatList
//略

iOSのシュミレーターで確認すると画面の中央に3つのボタンが表示され画面表示直後には1週のボタンのみ色がついています。

表示直後の画面
表示直後の画面

他のボタンをタッチしても何も変化はありません。termの変更によってuseEffect内の関数が実行できるように配列にtermを追加します。


useEffect(() => {
getPrice();
getCoins();
}, [coinId, term]);

useEffectにtermを追加後再度1月をタッチするとチャートの形が変わります。この1月ではBitCoinの価格が下がる傾向にあることがわかります。

1月のチャートを表示
1月のチャートを表示

React Native上でチャートライブラリを利用して仮想通貨の価格のチャートを表示するアプリケーションを作成することができました。

React NavigationのTab Navigationを追加してページ遷移を入れることでよりCoin Checkのモバイルアプリの形にちかづいていくと思うのでさらに機能を拡張することも可能です。