m_shige1979のときどきITブログ

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

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

https://github.com/mshige1979

AWSのLambdaとAPIGatwwayを連携

作りたいもの

f:id:m_shige1979:20201230100338p:plain

要約

なんか簡単な処理をAPIを経由してアクセスしたい

lambdaでなんかできそう

postmanで再現するにはホストがいる

API Gatewayでいけそう⇦今ここ

多分、APIGateway+Lambdaの連携でできるかも

lambdaは色々なAWSサービスのトリガーで動作することができる

今回はAPI Gatewayから起動するようにする

やること

1. APIGatewayを作成

f:id:m_shige1979:20201230150947p:plain
f:id:m_shige1979:20201230150852p:plain
f:id:m_shige1979:20201230151224p:plain
f:id:m_shige1979:20201230151332p:plain

2. APIのリソースを作成

f:id:m_shige1979:20201230151456p:plain
f:id:m_shige1979:20201230151830p:plain
f:id:m_shige1979:20201230152047p:plain

3. APIのメソッドを作成

f:id:m_shige1979:20201230152232p:plainf:id:m_shige1979:20201230152344p:plainf:id:m_shige1979:20201230152456p:plain

4. lambdaを作成

f:id:m_shige1979:20201230152636p:plain
f:id:m_shige1979:20201230152939p:plain
f:id:m_shige1979:20201230153047p:plain
f:id:m_shige1979:20201230153248p:plain

添付コード

exports.handler = async (event, context, callback) => {
    
    console.log("EVENT: \n" + JSON.stringify(event, null, 2))
    
    // リクエスト
    console.log("body: ", event.body);
    
    // レスポンス
    const response = {
        statusCode: 200,
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            "aaa": 111,
            "bbb": "aaa",
        }),
    };
    callback(null, response);
};


f:id:m_shige1979:20201230153504p:plain

5. APIとLambdaを紐付けデプロイ

f:id:m_shige1979:20201230153940p:plain
f:id:m_shige1979:20201230154100p:plain
f:id:m_shige1979:20201230154217p:plain
f:id:m_shige1979:20201230154320p:plain
f:id:m_shige1979:20201230154506p:plain
f:id:m_shige1979:20201230154621p:plain

6. curlで確認

 % curl https://5ftyywmhe6.execute-api.ap-northeast-1.amazonaws.com/dev/test1
{"aaa":111,"bbb":"aaa"}%
 %

できた ^_^

おまけ

lambdaからみたAPI Gatewayからのeventの中身

{
    "resource": "/test1",
    "path": "/test1",
    "httpMethod": "GET",
    "headers": {
        "Accept": "*/*",
        "CloudFront-Forwarded-Proto": "https",
        "CloudFront-Is-Desktop-Viewer": "true",
        "CloudFront-Is-Mobile-Viewer": "false",
        "CloudFront-Is-SmartTV-Viewer": "false",
        "CloudFront-Is-Tablet-Viewer": "false",
        "CloudFront-Viewer-Country": "JP",
        "Host": "一応隠す",
        "User-Agent": "curl/7.64.1",
        "Via": "一応隠す",
        "X-Amz-Cf-Id": "一応隠す",
        "X-Amzn-Trace-Id": "一応隠す",
        "X-Forwarded-For": "一応隠す",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "multiValueHeaders": {
        "Accept": [
            "*/*"
        ],
        "CloudFront-Forwarded-Proto": [
            "https"
        ],
        "CloudFront-Is-Desktop-Viewer": [
            "true"
        ],
        "CloudFront-Is-Mobile-Viewer": [
            "false"
        ],
        "CloudFront-Is-SmartTV-Viewer": [
            "false"
        ],
        "CloudFront-Is-Tablet-Viewer": [
            "false"
        ],
        "CloudFront-Viewer-Country": [
            "JP"
        ],
        "Host": [
            "一応隠す"
        ],
        "User-Agent": [
            "curl/7.64.1"
        ],
        "Via": [
            "一応隠す"
        ],
        "X-Amz-Cf-Id": [
            "一応隠す"
        ],
        "X-Amzn-Trace-Id": [
            "一応隠す"
        ],
        "X-Forwarded-For": [
            "一応隠す"
        ],
        "X-Forwarded-Port": [
            "443"
        ],
        "X-Forwarded-Proto": [
            "https"
        ]
    },
    "queryStringParameters": null,
    "multiValueQueryStringParameters": null,
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
        "resourceId": "一応隠す",
        "resourcePath": "/test1",
        "httpMethod": "GET",
        "extendedRequestId": "一応隠す",
        "requestTime": "30/Dec/2020:06:47:19 +0000",
        "path": "/dev/test1",
        "accountId": "一応隠す",
        "protocol": "HTTP/1.1",
        "stage": "dev",
        "domainPrefix": "一応隠す",
        "requestTimeEpoch": 一応隠す,
        "requestId": "一応隠す",
        "identity": {
            "cognitoIdentityPoolId": null,
            "accountId": null,
            "cognitoIdentityId": null,
            "caller": null,
            "sourceIp": "一応隠す",
            "principalOrgId": null,
            "accessKey": null,
            "cognitoAuthenticationType": null,
            "cognitoAuthenticationProvider": null,
            "userArn": null,
            "userAgent": "curl/7.64.1",
            "user": null
        },
        "domainName": "一応隠す",
        "apiId": "一応隠す"
    },
    "body": null,
    "isBase64Encoded": false
}

リクエストパラメータ

GETの場合は「queryStringParameters」とかに、POSTの場合は「body」に入るみたい

    "queryStringParameters": null,
    "multiValueQueryStringParameters": null,
    "body": null,

Lambda単体で動作検証したい場合はここにパラメータ突っ込めば良いかな・・・

次に作りたいもの

とりあえず、 S3にファイルが置かれたら起動するやつとか作ってみたい

react-nativeでタブナビゲーションを実装

作成したやつ


react-navigationのTopTabsサンプル

参考情報

React Navigationのタブナビゲーションをカスタマイズしてみよう! - bagelee(ベーグリー) createMaterialTopTabNavigator | React Navigation

github

react-native_samples/sampleAppNavigation01 at main · mshige1979/react-native_samples · GitHub

コード全体

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

import React, {useState} from 'react';
import {View, StyleSheet, StatusBar, Platform} from 'react-native';

import {NavigationContainer} from '@react-navigation/native';
import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs';

// タブエリア
import AppTabBar from './src/components/AppTabBar';

// サンプルページ
import Home from './src/screens/home';
import Profile from './src/screens/profile';
import MyPage from './src/screens/mypage';

// タブコンポーネント
const TopTabs = createMaterialTopTabNavigator();
const TopTabsScreen = () => {
  const [swipeEnabled, setSwipeEnabled] = useState(true);

  // Profileの場合は左右にスワイプする
  // Profile以外の場合はスワイプしない
  // 参考:https://stackoverflow.com/questions/63611393/react-native-material-top-tab-navigator-swipe-disable-depending-on-screens
  const focusCheck = ({navigation, route}) => ({
    focus: () => {
      console.log('focus: ', route.name);
      setSwipeEnabled(route.name === 'Profile');
    },
  });

  return (
    <TopTabs.Navigator
      swipeEnabled={swipeEnabled} // スワイプによる移動を制御(true: スワイプ移動、false: 移動しない)
      tabBar={(props) => <AppTabBar {...props} />}
      style={{
        marginTop: Platform.select({
          ios: 50,
          android: 0,
        }),
      }}>
      <TopTabs.Screen name="Home" component={Home} listeners={focusCheck} />
      <TopTabs.Screen
        name="Profile"
        component={Profile}
        listeners={focusCheck}
      />
      <TopTabs.Screen name="MyPage" component={MyPage} listeners={focusCheck} />
    </TopTabs.Navigator>
  );
};

// メイン
const App = () => {
  return (
    <>
      <NavigationContainer>
        <TopTabsScreen />
      </NavigationContainer>
      <StatusBar barStyle="dark-content" />
    </>
  );
};

const styles = StyleSheet.create({});

export default App;

1.画面を作成

import React, {useEffect} from 'react';
import {View, Text, StyleSheet, Button} from 'react-native';

const Home = ({navigation, route}) => {
  return (
    <View style={styles.container}>
      <View>
        <Text>Home</Text>
      </View>
      <View
        style={{
          margin: 10,
        }}>
        <Text>Topタブのサンプル このタブはスワイプしない</Text>
      </View>
      <View>
        <Button
          title="Profileへ移動"
          onPress={() => {
            navigation.jumpTo('Profile');
          }}
        />
      </View>
    </View>
  );
};

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

export default Home;

こーいうものを作成する

2. タブコンポーネントを作成

1で作成したコンポーネントを設定する。 基本2〜5位がちょうどいい感じ

// タブコンポーネント
const TopTabs = createMaterialTopTabNavigator();
const TopTabsScreen = () => {
  const [swipeEnabled, setSwipeEnabled] = useState(true);

  // Profileの場合は左右にスワイプする
  // Profile以外の場合はスワイプしない
  // 参考:https://stackoverflow.com/questions/63611393/react-native-material-top-tab-navigator-swipe-disable-depending-on-screens
  const focusCheck = ({navigation, route}) => ({
    focus: () => {
      console.log('focus: ', route.name);
      setSwipeEnabled(route.name === 'Profile');
    },
  });

  return (
    <TopTabs.Navigator
      swipeEnabled={swipeEnabled} // スワイプによる移動を制御(true: スワイプ移動、false: 移動しない)
      tabBar={(props) => <AppTabBar {...props} />}
      style={{
        marginTop: Platform.select({
          ios: 50,
          android: 0,
        }),
      }}>
      <TopTabs.Screen name="Home" component={Home} listeners={focusCheck} />
      <TopTabs.Screen
        name="Profile"
        component={Profile}
        listeners={focusCheck}
      />
      <TopTabs.Screen name="MyPage" component={MyPage} listeners={focusCheck} />
    </TopTabs.Navigator>
  );
};

swipeEnabled

⇨ 左右にスワイプするための設定値、trueでスワイプする、falseでしない

フォーカスイベントで任意の画面のみスワイプするように制御可能

  // Profileの場合は左右にスワイプする
  // Profile以外の場合はスワイプしない
  // 参考:https://stackoverflow.com/questions/63611393/react-native-material-top-tab-navigator-swipe-disable-depending-on-screens
  const focusCheck = ({navigation, route}) => ({
    focus: () => {
      console.log('focus: ', route.name);
      setSwipeEnabled(route.name === 'Profile');
    },
  });

3.メイン画面に組み込む

// メイン
const App = () => {
  return (
    <>
      <NavigationContainer>
        <TopTabsScreen />
      </NavigationContainer>
      <StatusBar barStyle="dark-content" />
    </>
  );
};

おまけ

独自タブバー

標準でスタイルが適用されているが、一部独自に組み込みたい場合

import React from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
import Animated from 'react-native-reanimated';

// 独自タブ
// 参考:https://bagelee.com/programming/react-native/react-navigation-customize/
// 参考:https://reactnavigation.org/docs/material-top-tab-navigator
const AppTabBar = ({state, descriptors, navigation, position}) => {
  const {routes, index} = state;

  const {
    containerStyle,
    tabStyle,
    selectedTabStyle,
    textStyle,
    selectedTextStyle,
  } = styles;

  //console.log(position);
  //console.log(descriptors);

  return (
    <View style={containerStyle}>
      {routes.map((route, idx) => {
        //const inputRange = state.routes.map((_, i) => i);
        //console.log(inputRange);

        // 選択しているタブ
        if (index === idx) {
          return (
            <View key={idx} style={[tabStyle, selectedTabStyle]}>
              <Text style={[textStyle, selectedTextStyle]}>
                {routes[idx].name}
              </Text>
            </View>
          );
        }

        // 他のタブ
        return (
          <TouchableOpacity
            style={tabStyle}
            key={idx}
            onPress={() => {
              // タップしたら他のタブへ切り替え
              navigation.navigate(route.name);
            }}>
            <Animated.Text style={[textStyle]}>
              {routes[idx].name}
            </Animated.Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
};

const styles = {
  containerStyle: {
    //paddingTop: 30,
    borderBottomWidth: 3,
    borderBottomColor: '#5ab4bd',
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'space-between',
    backgroundColor: '#fcf6d6',
  },
  tabStyle: {
    flex: 1,
    //marginRight: 1,
    //marginLeft: 1,
    height: 40,
    //borderTopLeftRadius: 16,
    //borderTopRightRadius: 16,
    backgroundColor: '#ffffff',
  },
  selectedTabStyle: {
    backgroundColor: '#5ab4bd',
  },
  textStyle: {
    fontWeight: 'bold',
    textAlign: 'center',
    paddingTop: 14,
  },
  selectedTextStyle: {
    color: '#ffffff',
  },
};

export default AppTabBar;

とりあえず、ここまでにしておく・・・

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");

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

終わり