BLOG

【動画】ReactNativeでスマホアプリ作ろう/第6回遂にアプリ完成!!!

2022-02-04

このブログはロックシステムのYoutubeチャンネル「ロックシステムアカデミーCH」の文字起こしや使用したコードの解説等をしています。
是非動画本編もご覧ください!
よろしければチャンネル登録も宜しくお願い致します!



【過去回はこちら!順番に学習するとアプリが作れます!

・【第1回】ReactNativeの環境設定
・【第2回】コンポーネント、アプリのレイアウト作り
・【第3回】APIを実装してニュース記事を表示
・【第4回】デザインの変更、画面遷移機能を作る
・【第5回】アイコンの設定、タブで画面遷移


どーもロックシステムアカデミーのろっくんです!ReactNativeを使ってスマホアプリを作ろうシリーズも今回で最終回だよ!!


ここまで長かったねー!ReactNativeは初めてだったけどなんとかここまで漕ぎつけたから最後まで頑張っちゃうよ!
前回天気予報画面のレイアウトだけ作って終わったね。今回は見本の画面にすることが目標だ!


そうだね!今回やることは5つ!


この流れは3回目の動画と同じだから、おさらいしながらアプリの完成を目指していくよ。


・【第3回】APIを実装してニュース記事を表示


3回目のおさらい箇所は今回の動画でも紹介してるよ!
最終的にはこんなニュースと天気予報が見れるアプリができるから勉強やポートフォリオに活用してみてね。





チャンネル登録よろしくお願いします!
ロックシステムアカデミー!ゆっくりプログラミング学習
大阪福島にあるプログラミングスクール「ロックシステムアカデミー」です!「プログラミング作って実践」をテーマに楽しく分かりやすいアプリ開発のレクチャー動画をアップしていきます! チャンネル登録よろしくお願いします! プログラミングスクール ▼「ロックシステムアカデミー」WEBサイト https://rocksystem.co.jp/academy/
https://www.youtube.com/channel/UC6JxNQ2QTX8Dl96V2MMNP8A

・参照したサイトリンク、完成コードは以下からお使いください!


天気API (動画8:32あたりで使用)
Сurrent weather and forecast - OpenWeatherMap
Get current weather, hourly forecast, daily forecast for 16 days, and 3-hourly forecast 5 days for your city. Historical weather data for 40 years back for any coordinate. Helpful stats, graphics, and this day in history charts are available for your reference. Interactive maps show precipitation, clouds, pressure, wind around your location.
https://openweathermap.org/

天気観測地 (動画12:40あたりで使用)
Interactive weather maps - OpenWeatherMap
https://openweathermap.org/weathermap?basemap=map&cities=true&layer=none&lat=36&lon=135&zoom=5

forEach構文 (動画15:25あたりで使用)
Array.prototype.forEach() - JavaScript | MDN
forEach() メソッドは与えられた関数を、配列の各要素に対して一度ずつ実行します。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

配列とは? (動画16:30あたりで使用)
プログラミング講座/配列ってなに?変数との違い【C#講座/visualstudio#13】
配列ってなに?変数との違いアプリ開発の基本を学ぶ【C#講座/visualstudio#13】今回使用したプログラミングコードや解説はブログにも記載してます!是非過去回も合わせて一緒にチャレンジしてみてください!https://rocksystem.co.jp/blog/page.php?entry_id=178-...
https://www.youtube.com/watch?v=l3xJNysVJxg

スプレッド構文 (動画17:00あたりで使用)
スプレッド構文 - JavaScript | MDN
スプレッド構文 (...) を使うと、配列式や文字列などの反復可能オブジェクトを、0 個以上の引数 (関数呼び出しの場合) や要素 (配列リテラルの場合) を期待された場所で展開したり、オブジェクト式を、0 個以上のキーと値の組 (オブジェクトリテラルの場合) を期待された場所で展開したりすることができます。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax


【使用したコードはこちら】
https://drive.google.com/file/d/1yTPCyLTu7IhoTZe_omSdKY49l_lGgyfc/view?usp=sharing
↑このリンクからダウンロードすればファイル構成もそのまま使えるよ。
ダウンロードしたらzipファイルを解凍してvisualstudiocodeにそのままドラッグアンドドロップだ!


自力でやりたい方は以下のコードも完成コードだから見比べて参考にしてみてね。
App.js
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { FontAwesome } from "@expo/vector-icons";
import NewsScreen from "./screens/NewsScreen";
import DetailScreen from "./screens/DetailScreen";
import WeatherScreen from "./screens/WeatherScreen";

const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();

const NewsStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="ニュース" component={NewsScreen} />
      <Stack.Screen name="詳細ページ" component={DetailScreen} />
    </Stack.Navigator>
  );
};

const WeatherStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="天気予報" component={WeatherScreen} />
    </Stack.Navigator>
  );
};

const screenOption = ({ route }) => ({
  tabBarIcon: ({ color, size }) => {
    let iconName;
    if (route.name === "ニュース") {
      iconName = "newspaper-o";
    } else if (route.name === "天気予報") {
      iconName = "sun-o";
    }

    // You can return any component that you like here!
    return <FontAwesome name={iconName} size={size} color={color} />;
  },
});

export default App = () => {
  return (
    <NavigationContainer>
      <Tab.Navigator screenOptions={screenOption}>
        <Tab.Screen name="ニュース" component={NewsStack} />
        <Tab.Screen name="天気予報" component={WeatherStack} />
      </Tab.Navigator>
    </NavigationContainer>
  );
};
NewsKizi.js
import React from "react";
import { StyleSheet, Text, View, Image, TouchableOpacity } from "react-native";

const NewsKizi = ({ imageuri, title, subtext, onPress }) => {
  var date = new Date(subtext);
  var year = date.getFullYear();
  var month = date.getMonth() + 1;
  var day = date.getDate();
  var koukaihiduke = year + "年" + month + "月" + day + "日";

  return (
    <TouchableOpacity style={styles.box} onPress={onPress}>
      <View style={styles.moziBox}>
        <Text numberOfLines={3} style={styles.text}>
          {title}
        </Text>
        <Text style={styles.subText}>{koukaihiduke}</Text>
      </View>

      <View style={styles.gazoBox}>
        <Image style={{ width: 100, height: 100 }} source={{ url: imageuri }} />
      </View>
    </TouchableOpacity>
  );
};

export default NewsKizi;

const styles = StyleSheet.create({
  box: {
    height: 100,
    width: "100%",
    borderColor: "lightblue",
    borderWidth: 1,
    flexDirection: "row",
  },

  moziBox: {
    flex: 1,
    padding: 16,
    justifyContent: "space-between",
  },

  gazoBox: {
    width: 100,
  },

  text: {
    fontSize: 16,
  },

  subText: {
    fontSize: 12,
    color: "darkblue",
  },
});
NewsScreen.js
import React, { useState, useEffect } from "react";
import { StyleSheet, FlatList, SafeAreaView } from "react-native";
import NewsKizi from "../components/NewsKizi";
import Constants from "expo-constants";
import axios from "axios";

const URI = `https://newsapi.org/v2/top-headlines?country=jp&category=entertainment&apiKey=${Constants.manifest.extra.newsApiKey}`;

export default function NewsScreen({ navigation }) {
  const [news, setNews] = useState([]);

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

  const getNews = async () => {
    const response = await axios.get(URI);
    setNews(response.data.articles);
  };

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={news}
        renderItem={({ item }) => (
          <NewsKizi
            imageuri={item.urlToImage}
            title={item.title}
            subtext={item.publishedAt}
            onPress={() => navigation.navigate("詳細ページ", { article: item })}
          />
        )}
        keyExtractor={(item, index) => index.toString()}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
  },
});
DetailScreen.js
import * as React from "react";
import { WebView } from "react-native-webview";
import { StyleSheet } from "react-native";
import Constants from "expo-constants";

export default function DetailScreen(props) {
  const { route } = props;
  const { article } = route.params;
  return <WebView source={{ uri: article.url }} />;
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: Constants.statusBarHeight,
  },
});
WeatherItem.js
import React from "react";
import { StyleSheet, Text, View, Image } from "react-native";

const WeatherItem = ({ description, icon, name }) => {
  return (
    <View style={styles.box}>
      <View style={styles.moziBox}>
        <Text style={styles.text}>{name}</Text>
      </View>

      <View style={styles.gazoBox}>
        <Image
          style={{ width: 95, height: 95 }}
          source={{ url: `http://openweathermap.org/img/wn/${icon}@2x.png` }}
        />
        <Text style={styles.subText}>{description}</Text>
      </View>
    </View>
  );
};
export default WeatherItem;

const styles = StyleSheet.create({
  box: {
    height: 100,
    width: "100%",
    borderColor: "lightblue",
    borderWidth: 1,
    flexDirection: "row",
  },

  gazoBox: {
    width: 200,
    alignItems: "center",
    justifyContent: "center",
    flexDirection: "row",
  },

  moziBox: {
    flex: 1,
    padding: 35,
    justifyContent: "center",
  },

  text: {
    fontSize: 17,
  },

  subText: {
    fontSize: 14,
    color: "darkblue",
  },
});
WeatherScreen.js
import React, { useState, useEffect } from "react";
import { StyleSheet, FlatList, SafeAreaView } from "react-native";
import WeatherItem from "../components/WeatherItem";
import Constants from "expo-constants";
import axios from "axios";

//各地域のAPI情報
const Hokkaido = {
  name: "北海道",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Asahikawa&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
const Touhoku = {
  name: "東北",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Yamagata&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
const Kantou = {
  name: "関東",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Tokyo&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
const Hokuriku = {
  name: "北陸",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Nagano&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
const Toukai = {
  name: "東海",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Nagoya&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
const Kinnki = {
  name: "近畿",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Osaka&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
const Tyugoku = {
  name: "中国",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Hiroshima&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
const sikoku = {
  name: "四国",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Matsuyama&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
const Kyusyu = {
  name: "九州",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Ozu&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
const Okinawa = {
  name: "沖縄",
  uri: `http://api.openweathermap.org/data/2.5/weather?q=Okinawa&lang=ja&exclude=hourly,minutely&units=metric&&APPID=${Constants.manifest.extra.weatherApiKey}`,
};
//各地域のAPI情報を配列に格納
const TotalUri = [
  Hokkaido,
  Touhoku,
  Kantou,
  Hokuriku,
  Toukai,
  Kinnki,
  Tyugoku,
  sikoku,
  Kyusyu,
  Okinawa,
];

export default function WeatherScreen() {
  const [weather, setWeathers] = useState([]);

  useEffect(() => {
    //それぞれの地域ごとに天気情報を取得
    TotalUri.forEach((info) => {
      getWeathers(info);
    });
  }, []);
  //天気情報を取得
  const getWeathers = async (info) => {
    //APIで非同期処理を実行
    const response = await axios.get(info.uri);
    //取得したデータから天気情報を取得
    const uriData = response.data.weather;
    //天気情報に地域名を追加
    uriData[0].name = info.name;
    //weatherに天気情報・地域名を設定する
    setWeathers((weather) => [...weather, uriData[0]]);
  };

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={weather}
        renderItem={({ item }) => (
          <WeatherItem
            description={item.description}
            icon={item.icon}
            name={item.name}
          />
        )}
        keyExtractor={(contact, index) => String(index)}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
  },
});

チャンネル登録よろしくお願いします!
ロックシステムアカデミー!ゆっくりプログラミング学習
大阪福島にあるプログラミングスクール「ロックシステムアカデミー」です!「プログラミング作って実践」をテーマに楽しく分かりやすいアプリ開発のレクチャー動画をアップしていきます! チャンネル登録よろしくお願いします! プログラミングスクール ▼「ロックシステムアカデミー」WEBサイト https://rocksystem.co.jp/academy/
https://www.youtube.com/channel/UC6JxNQ2QTX8Dl96V2MMNP8A

株式会社ロックシステム

「ブラック企業をやっつけろ!!」を企業理念にエンジニアが働きやすい環境をつきつめる大阪のシステム開発会社。2014年会社設立以来、残業時間ほぼゼロを達成し、高い従業員還元率でエンジニアファーストな会社としてIT業界に蔓延るブラックなイメージをホワイトに変えられる起爆剤となるべく日々活動中!絶賛エンジニア募集中。