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

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

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

https://github.com/mshige1979

react-native-configで環境変数を参照する3(android編)

前回

m-shige1979.hatenablog.com

今回やること

前回って環境変数を読むのにENVFILEとか設定していましたよね? あれだけではビルド時のアプリ名とか各環境のアプリ名などの調整が困難なので その辺の設定周り

補足

flavorとか利用しますが、厳密に理解はしていないため、 今回はこの設定でできたようなものとなります

参考

ReactNative製アプリのリリースTips(Android編) | スペースマーケットブログ

ReactNativeのAndroid側でFlavorで環境を切り分ける - Qiita

ReactNative(Android)でビルドバリアント毎にapk出力(署名つき) - Qiita

ReactNative 0.60.4、react-native-config を使って環境構築、リリースまで1 - LOCAL-C BLOG

【ReactNative】Androidリリースビルド時のメモ(2019/10/28 追記あり) - Qiita

react-nativeで初めてのnativeアプリリリース(android編)│tech1000+

【ReactNative】Androidリリースをイチからメモ - メモメモメモ

flavorを利用します

android/app/build.gradleへの設定1:defaultConfigにbuild_config_packageの設定を追加

    defaultConfig {
        applicationId "com.sampleconfig01"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
        
        // 追加
        resValue "string", "build_config_package", "com.sampleconfig01"
    }

android/app/build.gradleへの設定2:signingConfigsにrelease用の設定を追加

    signingConfigs {
        debug {
            storeFile file('debug.keystore')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }

        // 任意のキーストアファイルを利用
        release {
            if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
                storeFile file(MYAPP_RELEASE_STORE_FILE)
                storePassword MYAPP_RELEASE_STORE_PASSWORD
                keyAlias MYAPP_RELEASE_KEY_ALIAS
                keyPassword MYAPP_RELEASE_KEY_PASSWORD
            }
        }
    }

android/app/build.gradleへの設定3:buildTypesにrelease用の設定を追加

    buildTypes {
        debug {
            signingConfig signingConfigs.debug
        }
        release {
            // Caution! In production, you need to generate your own keystore file.
            // see https://reactnative.dev/docs/signed-apk-android.
            signingConfig signingConfigs.release
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        }
    }

android/app/build.gradleへの設定4:flavorの設定を追加

    // applicationVariants are e.g. debug, release
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // For each separate APK per architecture, set a unique version code as described here:
            // https://developer.android.com/studio/build/configure-apk-splits.html
            // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
            def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
            def abi = output.getFilter(OutputFile.ABI)
            if (abi != null) {  // null for the universal-debug, universal-release variants
                output.versionCodeOverride =
                        defaultConfig.versionCode * 1000 + versionCodes.get(abi)
            }

        }
    }

    // flavor追加
    flavorDimensions "default"
    productFlavors {
        dev {
            versionName = android.defaultConfig.versionName + '-dev'
            applicationIdSuffix ".dev"
        }
        stg {
            versionName = android.defaultConfig.versionName + '-staging'
            applicationIdSuffix ".stg"
        }
        prd {
            versionName = android.defaultConfig.versionName
        }
    }
}

dependencies {
・
・
・

android/gradle.propertiesにrelease用のキーストアの設定を追加

今回はセキュリティ的な面も含めてデバッグ時のものをコピー

# releaseキーストア設定
MYAPP_RELEASE_STORE_FILE=release.keystore
MYAPP_RELEASE_KEY_ALIAS=androiddebugkey
MYAPP_RELEASE_STORE_PASSWORD=android
MYAPP_RELEASE_KEY_PASSWORD=android

アプリのアイコン名などを環境別に作成

android/app/src配下のディレクトリに以下の構成を追加

※mainやdebugなどはそのままで削除しなくて良い

android/app/src
├── dev
│   └── res
│       └── values
│           └── strings.xml
├── prd
│   └── res
│       └── values
│           └── strings.xml
└── stg
    └── res
        └── values
            └── strings.xml

android/app/src/dev/res/values/strings.xml

<resources>
    <string name="app_name">アプリ開発</string>
</resources>

android/app/src/stg/res/values/strings.xml

<resources>
    <string name="app_name">アプリ受入</string>
</resources>

android/app/src/prd/res/values/strings.xml

<resources>
    <string name="app_name">アプリ本番</string>
</resources>

とりあえず設定は完了

実行

開発用

npx react-native run-android --variant=DevDebug --appIdSuffix dev

検証用

npx react-native run-android --variant=StgDebug --appIdSuffix stg

本番用

npx react-native run-android --variant=PrdDebug


環境別アプリ作成テスト

本番用ビルド

デバッグ時はデバッグアプリとして動作するため、実行時のマシンとデバッグ監視サーバとかが動作し、 また、スマホから実行する際もhttp系で証明書なしでも通信が可能となる

assetsディレクトリ準備

作成されていない場合は作成しておく

android/app/src/main/assets

バンドル作成

npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest --assets-dest android/app/build/intermediates/res/merged/release/

アプリ作成

cd android
 ./gradlew assembleDev
 ./gradlew assembleStg
 ./gradlew assemblePrd

作成場所

 % tree app/build/outputs/apk/
app/build/outputs/apk/
├── debug
│   ├── app-debug.apk
│   └── output-metadata.json
├── dev
│   ├── debug
│   │   ├── app-dev-debug.apk
│   │   └── output-metadata.json
│   └── release
│       ├── app-dev-release.apk
│       └── output-metadata.json
├── prd
│   └── debug
│       ├── app-prd-debug.apk
│       └── output-metadata.json
└── stg
    └── debug
        ├── app-stg-debug.apk
        └── output-metadata.json

アプリインストール

adb [-s デバイスID] install app/build/outputs/apk/dev/release/app-dev-release.apk

※apkファイルは環境に合わせて読み替える ※デバイスID未指定の場合は有効なデバイス全てにインストールする

package.json改修

  "scripts": {
    "android": "react-native run-android --variant=DevDebug --appIdSuffix dev",
    "android_dev": "react-native run-android --variant=DevDebug --appIdSuffix dev",
    "android_stg": "react-native run-android --variant=StgDebug --appIdSuffix stg",
    "android_prd": "react-native run-android --variant=PrdDebug",
    "android_bundle": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest --assets-dest android/app/build/intermediates/res/merged/release/",
    "android_release": "yarn android_bundle && cd android && ./gradlew assembleDev && ./gradlew assembleStg && ./gradlew assemblePrd",
    "ios": "react-native run-ios",
    "ios_dev": "echo '.env' > /tmp/envfile && yarn ios",
    "ios_stg": "echo '.env.staging' > /tmp/envfile && yarn ios",
    "ios_prd": "echo '.env.production' > /tmp/envfile && yarn ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },

react-native-configで環境変数を参照する2

前回の続き

m-shige1979.hatenablog.com

今回やること

環境別のファイルを作成して、それぞれのファイルで読み込むようにする

環境別ファイルを作成する

.env

MODE=dev
APP_NAME=TEST
VERSION=0.0.1

.env.staging

MODE=staging
APP_NAME=STAGING
VERSION=0.0.1

.env.production

MODE=production
APP_NAME=RELEASE
VERSION=0.0.1

簡単な実行方法

android

シェル変数の「ENVFILE」に環境変数ファイルを設定することで対応可能 またはENVFILEを環境変数名(export)化しても良い

ENVFILE=.env yarn android
または
ENVFILE=.env.staging yarn android
または
ENVFILE=.env.production yarn android

ios

「/tmp/envfile」に環境変数ファイルを設定することで対応可能 またはENVFILEを環境変数名(export)化しても良い

echo '.env' > /tmp/envfile && yarn ios
または
echo '.env.staging' > /tmp/envfile && yarn ios
または
echo '.env.production' > /tmp/envfile && yarn ios

iosを実機で動作させる場合はyarnコマンドではなく、Xcodeで動作させることになるため、別途対応が必要

package.json設定

簡略化したコマンドを作成

  "scripts": {
    "android": "react-native run-android",
    "android_dev": "ENVFILE=.env yarn android",
    "android_stg": "ENVFILE=.env.staging yarn android",
    "android_prd": "ENVFILE=.env.production yarn android",
    "ios": "react-native run-ios",
    "ios_dev": "echo '.env' > /tmp/envfile && yarn ios",
    "ios_stg": "echo '.env.staging' > /tmp/envfile && yarn ios",
    "ios_prd": "echo '.env.production' > /tmp/envfile && yarn ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },

補足

androidiosのリリース用時のセットアップ方法などはちょっと複雑なので次回以降にする androidの場合はbuildTypesとかproductFlavorsとかいじることになりそう iosXCode主体で対応する

react-native-configで環境変数を参照する

react-native-config

1つのプロジェクトで開発、検証、本番などを行う際、 それぞれ別のプロジェクトで運用するのはたいへんなため、環境変数などで APIの向き先などを調整する

今回やること

react-native-configで環境変数を参照するところまで

参考情報

GitHub - luggit/react-native-config: Bring some 12 factor love to your mobile apps!

GitHub - Clintal/react-native-config at ios-manual-linking

react-nativeで環境ごとに定数を切り替える(react-native-config) | I am mitsuruog

ReactNativeConfig使ってみた&使い方 - Qiita

React Nativeにおける環境別ビルド設定 | エンジニアブログ | 株式会社スクーラー

手順

プロジェクトを作成

npx react-native init sampleConfig01
cd sampleConfig01

react-native-configを導入

yarn add react-native-config

pod install(iosのみ実施)

npx pod-install

手動設定(android

/android/settings.gradleにreact-native-configを追加

rootProject.name = 'sampleConfig01'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'

// react-native-config用追加
include ':react-native-config'
project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android')

/android/app/build.gradleにreact-native-configを追加

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    //noinspection GradleDynamicVersion
    implementation "com.facebook.react:react-native:+"  // From node_modules

    // react-native-condig用追加
    implementation project(':react-native-config')

・
・
・
}

/android/app/build.gradleの先頭から2行目あたりに以下を追加

project.ext.envConfigFiles = [
    debug: ".env",
    dev: ".env.dev",
    stg: ".env.staging",
    prd: ".env.production",
    anycustombuildlowercase: ".env",
]
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"

手動設定(ios

workspaceを開く

f:id:m_shige1979:20210320121312p:plain

node_modules配下のreact-native-configのプロジェクトをドラッグして参照を紐付ける

f:id:m_shige1979:20210320121718p:plain

一旦、ビルドを行う

f:id:m_shige1979:20210320122007p:plain

プロジェクトのpod配下にコンパイルされたreact-native-configのモジュールを紐付け

f:id:m_shige1979:20210320123011p:plain

再度、ビルドを行う

f:id:m_shige1979:20210320123156p:plain

.envファイルを作成

.env

APP_NAME=TEST
VERSION=0.0.1

react-nativeへ実装

App.js

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

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

import Config from 'react-native-config';

const App = () => {
  return (
    <View
      style={{
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
      }}>
      <Text>APP_NAME: {Config.APP_NAME}</Text>
      <Text>VERSION: {Config.VERSION}</Text>
    </View>
  );
};

export default App;

結果

f:id:m_shige1979:20210320125006p:plain

補足

自動リンクではファイルからデータを取得できなかったため、手動で設定して取得するように対応 環境別の指定方法とかは今度かく

続き

m-shige1979.hatenablog.com

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を独自のコンポーネントで制御しようと思えばできるのかも・・・