m_shige1979のささやかな抵抗と欲望の日々

プログラムの勉強をしながら学習したことや経験したことをぼそぼそと書いていきます

Github(変なおっさんの顔でるので気をつけてね)

https://github.com/mshige1979

react-nativeでモーダル表示

react-nativeでモーダル表示

作成したもの


react-nativeでモーダル表示

ソースコード

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {useState} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  Button,
  View,
  Text,
  StatusBar,
  Modal,
  Alert,
  TouchableOpacity,
  Image,
  Dimensions,
} from 'react-native';

const height = Dimensions.get('window').height;
const width = Dimensions.get('window').width;

const App = () => {
  const [modalVisible, setModalVisible] = useState(false);

  console.log(height);
  console.log(width);

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <Button
          title="モーダルを開く"
          onPress={() => {
            setModalVisible(true);
          }}
        />
        <Modal
          animationType="slide"
          transparent={true}
          visible={modalVisible}
          //onRequestClose={() => {
          //  Alert.alert('Modal has been closed.');
          //}}
        >
          <TouchableOpacity
            activeOpacity={1}
            style={[styles.centeredView]}
            onPress={(event) => {
              console.log('おや');
              setModalVisible(false);
            }}></TouchableOpacity>
          <View style={[styles.modalView]}>
            <Text>sample</Text>
            <Image
              source={require('./assets/img/kaisya_woman_bad.png')}
              style={{
                width: width * 0.4,
                height: 200,
                //borderWidth: 1,
                borderColor: '#000000',
              }}
              resizeMode="contain"
            />
            <Button
              title="閉じる"
              onPress={() => {
                console.log('こ');
                setModalVisible(false);
              }}
            />
          </View>
        </Modal>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    //marginTop: 22,
    backgroundColor: '#CCC',
    opacity: 0.7,
  },
  modalView: {
    position: 'absolute',
    alignSelf: 'center',
    top: height / 5,
    margin: 20,
    backgroundColor: 'white',
    borderRadius: 20,
    padding: 35,
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
});

export default App;

使い方

モーダルコンポーネントを画面のどこかに配置とし、propのvisibleをfalseの状態にしておく

        <Modal
          animationType="slide"
          transparent={true}
          visible={modalVisible}
          //onRequestClose={() => {
          //  Alert.alert('Modal has been closed.');
          //}}
        >
          <TouchableOpacity
            activeOpacity={1}
            style={[styles.centeredView]}
            onPress={(event) => {
              console.log('おや');
              setModalVisible(false);
            }}></TouchableOpacity>
          <View style={[styles.modalView]}>
            <Text>sample</Text>
            <Image
              source={require('./assets/img/kaisya_woman_bad.png')}
              style={{
                width: width * 0.4,
                height: 200,
                //borderWidth: 1,
                borderColor: '#000000',
              }}
              resizeMode="contain"
            />
            <Button
              title="閉じる"
              onPress={() => {
                console.log('こ');
                setModalVisible(false);
              }}
            />
          </View>
        </Modal>

Buttonコンポーネントなどのタップイベントで展開開くようにする

        <Button
          title="モーダルを開く"
          onPress={() => {
            setModalVisible(true);
          }}
        />

まあ、ボタン出なくても条件を満たして値をtrueにすれば良い

react-nativeで画面上引っ張ってロードするあれ

作ったやつはこれ


react-nativeで画面上引っ張ってロードするあれ

iOSandroidで挙動違うのね・・・

スクロールができるコンポーネントを使わないといけないみたい

ScrollViewとかFlatList、VirtualizedListとかにrefreshControlがある場合は使用可能 あとはRefreshControlも必要

サンプルコード

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

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

const App = () => {
  const [refreshFlag, setRefreshFlag] = useState();
  const [list, setList] = useState();

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

  const fetch = () => {
    const num = Math.round(Math.random() * 100);

    const _list = [];
    for (let i = 0; i < num; i++) {
      _list.push({
        name: 'hoge' + String(i),
      });
    }
    setList(_list);
  };

  return (
    <View
      style={{
        flex: 1,
        paddingTop: Platform.select({
          ios: 50,
          android: 0,
        }),
      }}>
      <FlatList
        data={list}
        renderItem={({item}) => {
          return (
            <View style={[styles.row]}>
              <Text>{item.name}</Text>
            </View>
          );
        }}
        keyExtractor={(item, index) => index.toString()}
        refreshControl={
          <RefreshControl
            refreshing={refreshFlag}
            onRefresh={() => {
              console.log('aaaa');
              setRefreshFlag(true);
              setTimeout(() => {
                fetch();
                setRefreshFlag(false);
              }, 3000);
            }}
          />
        }
      />
    </View>
  );
};

const styles = StyleSheet.create({
  row: {
    height: 50,
    borderWidth: 1,
    borderColor: '#000000',
    marginBottom: 5,
  },
});

export default App;

補足

データをDBとか作って取り込むのは面倒だったので乱数生成

  const fetch = () => {
    const num = Math.round(Math.random() * 100);

    const _list = [];
    for (let i = 0; i < num; i++) {
      _list.push({
        name: 'hoge' + String(i),
      });
    }
    setList(_list);
  };

refreshControlで更新を呼び出す

        refreshControl={
          <RefreshControl
            refreshing={refreshFlag}
            onRefresh={() => {
              console.log('aaaa');
              setRefreshFlag(true);
              setTimeout(() => {
                fetch();
                setRefreshFlag(false);
              }, 3000);
            }}
          />
        }

RefreshControlを独自のコンポーネントで制御しようと思えばできるのかも・・・

react-nativeでgoogle mapを使用

react-nativeでgoogle mapを使用

環境

  • 開発マシンはmac mini
  • android側のみ
  • とりあえずやってみた

参考情報

【React Native】Googleマップを利用する(Android) - Ren's blog
React Nativeへ地図を表示する方法 - react-native-mapsライブラリを使ってReact Nativeへ地図を使う方法について調べてみます。
Get an API Key  |  Maps SDK for Android  |  Google Developers

google mapキーを取得

Google Cloud Platformへアクセスし、プロジェクトを新規に作成

f:id:m_shige1979:20201012210807p:plain

認証情報画面へ遷移

f:id:m_shige1979:20201012211114p:plain

認証情報を作成

f:id:m_shige1979:20201012211520p:plain

APIキーを取得

f:id:m_shige1979:20201012211624p:plain

確認

f:id:m_shige1979:20201013004146p:plain

※有料です

手順

プロジェクトの新規作成

npx react-native init SampleMap1
cd SampleMap1

react-native-mapsコンポーネントを追加

npm install --save react-native-maps
npm install @react-native-community/geolocation --save

APIキーを設定

app配下のAndroidManifest.xmlを編集

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.samplemap1">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme">
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
      <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

      <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="もらったAPIキー" />

    </application>

</manifest>

App.jsを改修

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React from 'react';
import {
  Platform,
  StyleSheet,
  Button,
  Text,
  View,
  Dimensions,
} from 'react-native';
import MapView, { PROVIDER_GOOGLE, Region, Marker } from 'react-native-maps';
//import Geolocation, { GeolocationResponse } from '@react-native-community/geolocation';

const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE_DELTA = 0.0922/4;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const places = {
  disneyland: {
    label: 'Disneyland',
    region: {
      latitude: 33.8120918,
      longitude: -117.9189742,
      latitudeDelta: LATITUDE_DELTA,
      longitudeDelta: LONGITUDE_DELTA,
    },
    marker: {
      latlng: {
        latitude: 33.8120918,
        longitude: -117.9189742,
      },
      title: 'Disneyland',
      description: 'Theme park',
    },
  },
  universalstudio: {
    label: 'Universal Studio Hollywood',
    region: {
      latitude: 34.1381168,
      longitude: -118.3533783,
      latitudeDelta: LATITUDE_DELTA,
      longitudeDelta: LONGITUDE_DELTA,
    },
    marker: {
      latlng: {
        latitude: 34.1381168,
        longitude: -118.3533783,
      },
      title: 'Universal Studio Hollywood',
      description: 'Film studio and theme park',
    },
  }
}

export default class App extends React.Component {

  inPlace2 = false;
  placeName = '';
  marker1;

  constructor(props){
    super(props);
    this.placeName = places.universalstudio.label;
    this.state = {
      region: places.universalstudio.region,
      marker: places.universalstudio.marker,
    };
  }

  movePlace(){
    this.marker1.hideCallout();
    if(this.inPlace2){
      this.placeName = places.universalstudio.label;
      this.setState({
        region: places.universalstudio.region,
        marker: places.universalstudio.marker,
      });
    }
    else{
      this.placeName = places.disneyland.label;
      this.setState({
        region: places.disneyland.region,
        marker: places.disneyland.marker,
      });
    }
    this.inPlace2 = !this.inPlace2;
  }

  render() {
    return (
      <View style={{flex:1}}>
        <MapView
          style={{flex:1}}
          region={this.state.region}
          provider={PROVIDER_GOOGLE} >
          <MapView.Marker
            ref={(ref)=>{this.marker1 = ref;}}
            coordinate={this.state.marker.latlng}
            title={this.state.marker.title}
            description={this.state.marker.description}
          />
        </MapView>
        <View style={{height:100,padding:16}}>
          <Text>{this.placeName}</Text>
          <Button title="Move" onPress={()=>this.movePlace()}/>
        </View>
      </View>
    );
  }
}

起動

npx react-native run-android

f:id:m_shige1979:20201013005940p:plain

気をつけること

  • MAP APIが有効でないと動かない

react-nativeをexpoで作成

react-nativeをexpoで作成

概要

m-shige1979.hatenablog.comCLIを使用した環境設定はできた感じとしてexpoの場合はどんな感じなのか確認する。

expo.io

expo.io

アカウント作成

f:id:m_shige1979:20201004102451p:plain

アカウント作成後の画面

f:id:m_shige1979:20201004102816p:plain

ツールインストール

android studioxcodeをインストール

xcodeapple storeより android studioDownload Android Studio and SDK tools  |  Android Developersよりインストール ※一応いるのね・・・expo側でなんかいい感じにしてくれると思ってましたよ( ;∀;)

anyenvインストール

anyenv install --init
mkdir -p $(anyenv root)/plugin
git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
anyenv update
anyenv install nodenv
exec $SHELL -l

nodenvインストール

nodenv install 14.11.0
nodenv global 14.11.0
node --version

npmよりexpoインストール

npm install expo-cli --global

アプリ作成

プロジェクト作成

npx expo init myapp1

※私の環境はzshなのでパスがうまく通らないのでnpx経由で起動

起動

cd myapp1
yarn start

f:id:m_shige1979:20201004105152p:plain

iosを選択

f:id:m_shige1979:20201004110736p:plain

※eset入れていたら19000〜19002あたり開けること

ソース改修

App.js

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Text>hello world</Text>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

f:id:m_shige1979:20201004112208p:plain

基本的にソースを修正後に自動的に変更が反映される仕組みだが、 ポートを閉じていたりいたらうまく動作しないため、最初のうちはシミュレータを再度起動し直す必要があるかも

実機に入れるのは今後にする スマホアプリのexpo clientとかでどうにかするらしいが・・・

終わり

herokuでCORSを確認

herokuでCORSを確認

CORS

developer.mozilla.org

要はajaxとかでホストの異なるリクエストを取得できないこと

環境

大元(リクエスト先)

mighty-taiga-69931.herokuapp.com

index.php

<?php
session_start();
if ($_SESSION["auth"] === true) {
    // redirect
    header('Location: ./top.php');
    exit;
}
?>
<!DOCTYpe html>
<html>
<head>
    <script type="text/javascript" src="./jquery-3.5.1.min.js"></script>
</head>
<body>
    <div>
    <label>
            userid: <input type="text" class="id" ame="userid" />
        </label>
        <label>
            password: <input type="password" class="pw" name="password" />
        </label>
        <button type="button" class="login-btn">
            login
        </button>
    </div>
<script>
$(function() {
    $(".login-btn").on("click", function() {
        console.log("login");

        let params = {
            "userid": $(".id").val(),
            "password": $(".pw").val()
        };

        $.ajax({
            type: "POST",
            url: "./login.php",
            contentType: 'application/json',
            dataType: "json",
            data: JSON.stringify(params),
            success: function(msg){
                console.log(msg);
                location.href = "/";
            },
            error: function(msg){
                console.log(msg);
            }
        });
    });
});
</script>
</body>
</html>

login.php

<?php
session_start();

$json = file_get_contents("php://input");
$contents = json_decode($json, true);
//var_dump($contents);

$new_sessionid = session_id();
$auth = "NG";
if ($contents["userid"] == "admin"  && $contents["password"] == "pass") {
    $auth = "OK";

    // 
    session_regenerate_id(true);
    $new_sessionid = session_id();
    $_SESSION["auth"] = true;
}

$result = [
    "status" => 200,
    "auth" => $auth,
    "session_id" => $new_sessionid,
];

echo json_encode($result);
?>

logout.php

<?php
session_start();

$_SESSION = array();

if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
}

session_destroy();
session_regenerate_id(true);
?>

検証用(大元へアクセスする方)

pacific-harbor-45480.herokuapp.com

index.php

<!DOCTYpe html>
<html>
<head>
    <script type="text/javascript" src="./jquery-3.5.1.min.js"></script>
</head>
<body>
    <div>
    <label>
            userid: <input type="text" class="id" ame="userid" />
        </label>
        <label>
            password: <input type="password" class="pw" name="password" />
        </label>
        <button type="button" class="login-btn">
            login
        </button>
    </div>
<script>
$(function() {
    $(".login-btn").on("click", function() {
        console.log("login");

        let params = {
            "userid": $(".id").val(),
            "password": $(".pw").val()
        };

        $.ajax({
            type: "POST",
            url: "https://mighty-taiga-69931.herokuapp.com//login.php",
            contentType: 'application/json',
            dataType: "json",
            data: JSON.stringify(params),
            success: function(msg){
                console.log(msg);
                location.href = "/";
            },
            error: function(msg){
                console.log(msg);
            }
        });
    });
});
</script>
</body>
</html>

確認

異なるホストへajaxへリクエストを送信

        $.ajax({
            type: "POST",
            url: "https://mighty-taiga-69931.herokuapp.com//login.php",
            contentType: 'application/json',
            dataType: "json",
            data: JSON.stringify(params),
            success: function(msg){
                console.log(msg);
                location.href = "/";
            },
            error: function(msg){
                console.log(msg);
            }
        });

結果

Access to XMLHttpRequest at 'https://mighty-taiga-69931.herokuapp.com//login.php' from origin 'https://pacific-harbor-45480.herokuapp.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

f:id:m_shige1979:20201003180610p:plain

うん、出ました。 対策としては「Access-Control-Allow-Headers」を大元より返却するようにする。

app側でやるかnginxとかのwebサーバのどちらかで対応できるかも

header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET,POST,HEAD,OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
e

もし、ホストを任意のもの限定にしたい場合は

header("Access-Control-Allow-Origin: https://pacific-harbor-45480.herokuapp.com");

みたいにすることで対応可能

終わり

heroku復習

herokuの復習

すっかりやってなくて忘れたので復習がてらやり方をメモ

heroku

クラウド・アプリケーション・プラットフォーム | Heroku

やり方

とりあえず、ログインして簡単なwebアプリ作成まで DBとかはなし

手順

ログイン

f:id:m_shige1979:20201003150616p:plain

ダッシュボード画面

以前のアプリは何を入れていたか忘れたし使ってないはずなので消したw f:id:m_shige1979:20201003150855p:plain

クライアント側にツールインストール

brew tap heroku/brew && brew install heroku
heroku autocomplete

クライアントからログイン

heroku login

f:id:m_shige1979:20201003152122p:plain へえ、web経由してログインするのか・・・

アプリを名前なしで作成すると任意の名前が自動生成される

f:id:m_shige1979:20201003152354p:plain

クライアント側でgitを連携

$ git init
$ heroku git:remote -a [アプリ名]

任意のファイルを作成

index.php

<?php
    echo "test";
?>

push

git push heroku master

heroku open

heroku open

f:id:m_shige1979:20201003153428p:plain

終わり

なんで今更

ちょっと CORSの確認したくて手頃に使えそうなサービスないかと考えたら herokuでなんとか実現できそうかなって思って・・・

でも忘れたので再度メモ程度として残す

react-native環境設定

環境

mac(catalina)

入れるもの

react-native

セキュリティソフト

eset

作業ディレクト
$HOME/work/react-native

てじゅん

brewインストール*1
/usr/bin/ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)”

※入っている場合はupdateとかしとけば良いはず

anyenvセットアップ*2
brew install anyenv
anyenv init
anyenv install --init
echo 'eval "$(anyenv init -)"' >>  ~/.zshrc

※”~/.zshrc”の箇所は環境に合わせて書き換える(私の環境はcatalinaだったのかbach_profileが使えなかった)
anyenvを使う - Qiita

※手動?

anyenv install --init
mkdir -p $(anyenv root)/plugin
git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
anyenv update
anyenv install nodenv
exec $SHELL -l
nodenv install
nodenv install 14.11.0
nodenv global 14.11.0
node --version
watchman
brew install watchman
yarn install
brew install yarn
react native
mkdir -p $HOME/work/react-native
cd $HOME/work/react-native
yarn add --exact react-native
yarn add --dev --exact @types/react-native
java
brew tap AdoptOpenJDK/openjdk
brew cask install adoptopenjdk8
java -version
android sdk
brew install cask android-studio
brew cask install intel-haxm
環境変数(~/.zshrc)に追記
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools
xcode(環境によっては既存のものを削除する必要あり)
app storeよりインストール
CocoaPods
sudo gem install cocoapods
pod --version
雛形作成
cd $HOME/work/react-native
npx react-native init MyApp --template react-native-template-typescript@6.3.16
実行
cd MyApp
npx react-native run-android


f:id:m_shige1979:20200921093543p:plain

esetの弊害

gladleがネットワークでダウンロードできないため、ビルドが終わらない

→ ファイアウォールを解除するか、ポートを開ける必要がある
f:id:m_shige1979:20200921093210p:plain

f:id:m_shige1979:20200921093247p:plain

f:id:m_shige1979:20200921093317p:plain

あと他にもandroidエミュレータとの通信も通らないので状況に合わせてポートを開ける必要がある。

所感

  1. react-nativeのセットアップよりgradleの対応の方に時間を取られた
  2. セキュリティソフト変えた方がいいかな?
  3. windows10でandoriidを動かす方法も調べるけど、PC持ってないからどうしようかな・・・

*1:パッケージソフトのツール

*2:nodeとか入れる際に利用する