話題のワードを入れてみる

お昼に何を食べよかなとめぼしいあたりをうろうろしてるうちに時間がなくなってきて結局いつも行く感じのお店になってしまったり、なんてーときでも大丈夫なように、前回作った「近場で済ますアプリ」にツイッターでおいしい話題になっているつぶやきを表示する機能を実装しました。
※配布しているアプリでなくて、個人で実装を楽しんでいるアプリです。
詰まる
はい、アプリはExpoというReact Native開発支援を使っていて、「それExpoには含まれてないんだな」なエラーが表示されちゃって、一部のライブラリが動かなかったりしたため急遽React Nativeでプロジェクトを作りなおしました。
EXPOでやっていくならその辺をぶち抜ける技術がいるかもしれません。
何もしなくてもアプリがビルドできるから便利は便利ですが、そういったこともあったので、まさにこれから始めるって人はExpoを使わないで開発を始めてみた方が良いかもしれないなあという場面もあるかも。VSCodeと対話しながらすすめるStyle。

npx react-native init APPNAME

作成はこれ一発です。基本的にはReact NativeはReact Nativeなのでほとんどそのままで大丈夫なんだけど、位置情報の取得で同じ手法がとれなかったので今回は「Google Location Services API」を使う「react-native-location」ライブラリを使用することにしました。
位置情報を取得してからHOTPEPPERさんのAPIを叩く必要があるので、自己満足でタイトル画面を作っていたのが功を奏して、タイトルで位置を取得してお店一覧にPropsとして送ることにしました。

import RNLocation from "react-native-location";

const [lat, setLat] = useState("");
const [lng, setLng] = useState("");

RNLocation.configure({
  distanceFilter: 5.0,
});

RNLocation.requestPermission({
  ios: "whenInUse",
  android: {
    detail: "fine",
    rationale: {
      title: "Location permission",
      message: "We use your location to demo the library",
      buttonPositive: "OK",
      buttonNegative: "Cancel",
    },
  },
}).then((granted) => {
  if (granted) {
    startUpLocation();
  }
});

const startUpLocation = () => {
  RNLocation.subscribeToLocationUpdates((locations) => {
    setLat(locations[0].latitude);
    setLng(locations[0].longitude);
  });
};

useEffect(() => {
  (async () => {
    await getLocation();
  })();
}, [getLocation]);

これunsubscribeする場所をまだ模索中です。

あとはツイッターのAPI取得は、「mamamama-su-daさんのtwitter-sample」のロジックを参考に一部のメソッドを使って「#おいしい」な感じのワードで検索するようにしました。
apikeyとか一式は持ってるものを使って、なんやかんやの処理はやってません。
なんやかんやについてはTwitterAPIのOAuth1.0認証について調べてみてください。
タブバー使いたかった けど
ほんとはタブバーを使いたかったんですが、ひとまずはツイッター検索を追加するという目標を達成しようということにして、テキストを追加して、Navigatorで画面遷移を追加して一覧表示の画面を作りました。またCardコンポーネントベースの画面やけども。

Twitter表示する画面サンプル

import React, {useEffect, useState} from "react";

import {StyleSheet, ScrollView, Text, View, Image} from "react-native";
import {Card, Avatar} from "react-native-elements";

import tw from "../Api/TwitterManager";

const ListView = ({image, data}) => {
  return (
    <View style={styles.container}>
      <View style={styles.subtitle}>
        <Avatar xlarge rounded source={{uri: image}} />
      </View>
      <View style={styles.information}>
        <Text>{data}</Text>
      </View>
    </View>
  );
};

const LunchTweet = ({route}) => {
  const [tweets, setTweets] = useState([]);
  useEffect(() => {
    (async () => {
      const value = await tw
        .getTwitter(
          "/search/tweets.json",
          Object.assign(
            {},
            {q: "#おいしい", lang: "ja", result_type: "resent"},
          ),
        )
        .catch((err) => {
          console.warn("search tweets", err);
        });
      setTweets(value.statuses);
    })();
  }, []);

  return (
    <ScrollView style={styles.flexOne}>
      {tweets &&
        tweets.map((data, i) => (
          <Card key={i} title={data.user.name}>
            <View style={styles.flexOne}>
              <ListView
                image={data.user.profile_image_url_https}
                data={data.text}
              />
            </View>
            {data.extended_entities &&
              data.extended_entities.media.map((m, key) => (
                <View key={key}>
                  <Image
                    style={styles.postPict}
                    source={{uri: m.media_url_https}}
                  />
                </View>
              ))}
          </Card>
        ))}
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  flexOne: {
    flex: 1,
  },
  container: {
    flex: 1,
    flexDirection: "row",
  },
  subtitle: {
    flex: 2,
    padding: 2,
  },
  information: {
    flex: 10,
    padding: 10,
  },
  postPict: {
    width: 300,
    height: 300,
  },
});

export default LunchTweet;

さぐりさぐり
LINTエラーの中にuseEffectの初期値関連のエラーが出てきて、そういえばそうだったと思い出しながらさぐりさぐりでuseCallbackを入れてみたさぐりさぐり感のある書き直したお店一覧。

import React, {useState, useEffect, useCallback} from "react";

import api from "../Api/Api";
import {
  View,
  Text,
  StyleSheet,
  Image,
  ScrollView,
  TouchableOpacity,
  TextInput,
} from "react-native";
import {Card, Button} from "react-native-elements";

import {Overlay} from "react-native-elements";

const Menu = ({navigation, route}) => {
  navigation.setOptions({
    headerRight: () => (
      <Button
        type="clear"
        onPress={() => setIsVisible(true)}
        title="Keyword"
        color="#fff"
      />
    ),
  });
  const [shops, setShops] = useState([]);
  const [isVisible, setIsVisible] = useState(false);
  const [keyword, setKeyword] = useState("");

  const getShops = useCallback(async () => {
    const res = await api.getShops(
      "",
      route.params.Data.Lat,
      route.params.Data.Lng,
    );
    return res.data.results;
  }, [route.params.Data]);

  const setKeywordSearch = (word) => {
    (async () => {
      const res = await api.getShops(
        word,
        route.params.Data.Lat,
        route.params.Data.Lng,
      );
      const data = res.data.results;
      setShops({data});
    })();
    setIsVisible(false);
  };

  const toTweetLunch = () => {
    setIsVisible(false);
    navigation.navigate("lunchTweet");
  };

  useEffect(() => {
    (async () => {
      const data = await getShops();
      await setShops({
        data,
      });
    })();
  }, [getShops]);

  return (
    <ScrollView style={styles.overlay}>
      <Overlay
        height="auto"
        isVisible={isVisible}
        onBackdropPress={() => setIsVisible(false)}>
        <View>
          <Text>検索に含ませたいキーワードがあれば入力してください。</Text>
          <TextInput
            style={styles.searchBox}
            onChangeText={(v) => setKeyword(v)}
            value={keyword}
          />
          <Button
            type="clear"
            color="#009488"
            title="検索する"
            onPress={() => setKeywordSearch(keyword)}
          />
          <TouchableOpacity onPress={() => toTweetLunch()}>
            <View style={styles.twView}>
              <Image
                resizeMode="contain"
                style={styles.pict}
                source={require("../assets/twbird.png")}
              />
              <Text style={styles.tweet}>Twitterで話題のランチを調べる</Text>
            </View>
          </TouchableOpacity>
        </View>
      </Overlay>
      <View style={styles.container}>
        {shops.data &&
          shops.data.shop.map((data, i) => (
            <Card key={i}>
              <View style={styles.shopBox}>
                <View style={styles.shopBoxRow}>
                  <View style={styles.shopBoxCol}>
                    <Text>{data.name}</Text>
                    <Text>{data.genre.name}</Text>
                  </View>
                  <View style={styles.shopImage}>
                    <TouchableOpacity
                      onPress={() =>
                        navigation.navigate("shop", {
                          Data: {shop: data.id},
                        })
                      }>
                      <Image
                        style={styles.shopPict}
                        source={{uri: data.photo.mobile.l}}
                      />
                    </TouchableOpacity>
                  </View>
                </View>
              </View>
            </Card>
          ))}
      </View>
    </ScrollView>
  );
};

const black = "#000";
const styles = StyleSheet.create({
  overlay: {
    flex: 1,
  },
  shopPict: {
    width: 100,
    height: 100,
  },
  shopImage: {
    flex: 2,
    justifyContent: "flex-end",
    alignItems: "flex-end",
  },
  searchBox: {
    marginTop: 20,
    marginBottom: 10,
    height: 50,
    borderColor: black,
    borderWidth: 1,
  },
  shopBox: {
    width: 280,
    flex: 1,
  },
  shopBoxRow: {
    flex: 1,
    flexDirection: "row",
  },
  shopBoxCol: {
    flex: 3,
    flexDirection: "column",
  },
  tweet: {
    margin: 1,
    marginTop: 5,
    fontSize: 15,
    borderStyle: "solid",
  },
  pict: {
    width: 30,
    height: 30,
  },
  twView: {
    marginTop: 10,
    flexDirection: "row",
  },
});

export default Menu;

さぐりさぐりでした。

参考サイトさま
mamamama-su-da/twitter-sample
Twitter

次か、次の次か、またいつかの回かでタブバーやるか。

Similar Posts:


Author: Takeken

インターネット利用者のITリテラシーを向上しようという目的で生まれた、2次元キャラのたけけんです。サーバー弄りからプログラミングまで幅広く書いています。自称エッセイスト。

Leave a Reply

Your email address will not be published. Required fields are marked *