• トップ
  • ブログ一覧
  • 【React Native】特定の画面で Android の SoftInputMode を無効にする方法
  • 【React Native】特定の画面で Android の SoftInputMode を無効にする方法

    はじめに

    React Native でアプリを開発する際、デフォルトでは Android の windowSoftInputMode に adjustResize が設定されます。この設定により、キーボードが表示された際に画面レイアウトが自動調整され、入力フィールドやボタンがキーボードで隠れないようになっています。

    しかし、プロジェクトによっては特定の画面でこの自動調整を無効化したいケースがあります。Android のネイティブコードでは setSoftInputMode を使用して動的にこの設定を変更できますが、React Native にはそのような仕組みがデフォルトで提供されていません。

    本記事では、React Native 0.76 から導入された Turbo Native Module を活用し、特定の画面で windowSoftInputMode を動的に変更する方法を紹介します。

    環境

    • macOS 13.6.7
    • Node 20.12.1
    • 主要ライブラリ
      • @react-native-community/cli 15.0.1
      • react 18.3.1
      • react-native 0.76.4
    • 開発ツール
      • Android Studio 2024.1
      • Xcode 15.2

    ReactNative 側の実装

    1. 型定義の作成

    まず、Turbo Native Module 用の型定義ファイルを作成します。これにより、React Native と Android ネイティブコードの間でデータのやり取りを安全に行えます。

    1. プロジェクトルートに specs フォルダを作成
    2. 型定義を以下のように記述した specs/NativeSoftInputMode.ts を作成

    ※ ファイル名の先頭には Native をつける必要があります。 参考:Using Codegen(#configuring-codegen)

    1// specs/NativeSoftInputMode.ts
    2
    3import type {TurboModule} from 'react-native';
    4import {TurboModuleRegistry} from 'react-native';
    5
    6export interface Spec extends TurboModule {
    7  setSoftInputMode(mode: string): void;
    8}
    9
    10export default TurboModuleRegistry.getEnforcing<Spec>('NativeSoftInputMode');

    TurboModuleRegistry には、Turbo Native Module を取得するための 2 つのメソッドがあります。

    • get<T>(name: string): T | null: TurboNativeModule が利用できない場合は NULL を返します。
    • getEnforcing<T>(name: string): T: TurboNativeModule が利用できない場合は例外をスローします。モジュールが常に利用可能であることを前提としています。

    2. Codegen 設定の追加

    package.json に以下の設定を追加して、型定義からネイティブコードを自動生成できるようにします。

    1// package.json
    2
    3{
    4  // ...
    5
    6  "codegenConfig": {
    7    "name": "NativeModuleSpec",
    8    "type": "modules",
    9    "jsSrcsDir": "specs",
    10    "android": {
    11      "javaPackageName": "com.nativemodule"
    12    }
    13  }
    14}
    • name: ファイル作成時に使用する名前。慣例として接尾辞 Spec を付けます。
    • type: パッケージに含まれるモジュールのタイプ。TurboNativeModule を使用する場合、modules を指定します。
    • jsSrcsDir: Codegen によって解析される型定義にアクセスするための相対パス。specs フォルダのパスを指定します。
    • android.javaPackageName: Codegen が生成する Java ファイルで使用するパッケージ

    参考:

    3. モジュールを使用

    以下のコードで、adjustNothing を設定してキーボードの自動調整を無効化します。

    1// App.tsx
    2
    3import './src/assets/styles/global.css';
    4import React from 'react';
    5import {Platform, Text, TextInput, TouchableOpacity, View} from 'react-native';
    6import {SafeAreaView} from 'react-native-safe-area-context';
    7
    8import NativeSoftInputMode from './specs/NativeSoftInputMode';
    9
    10function App(): React.JSX.Element {
    11  React.useEffect(() => {
    12    if (Platform.OS === 'android') {
    13      NativeSoftInputMode?.setSoftInputMode('adjustNothing'); // Androidのキーボード自動調整をOFFにする
    14      return () => NativeSoftInputMode?.setSoftInputMode('adjustResize'); // 元に戻す
    15    }
    16  }, []);
    17
    18  return (
    19    <SafeAreaView className="flex-1">
    20      <View className="justify-center items-center flex-1 p-6">
    21        <TextInput className="h-[48px] w-full border-[1px] rounded-full p-3" />
    22      </View>
    23      <View className="w-full p-6 bottom-0">
    24        <TouchableOpacity className="h-[48px] justify-center items-center bg-orange-500">
    25          <Text className="color-white">Bottom Button</Text>
    26        </TouchableOpacity>
    27      </View>
    28    </SafeAreaView>
    29  );
    30}
    31
    32export default App;

    Android 側の実装

    1. コードの自動生成

    以下のコマンドを実行し、型定義に基づいてコードを生成します。

    1cd android
    2./gradlew generateCodegenArtifactsFromSchema

    生成されたファイルは、android/app/build/generated/source/codegen/java 配下に置かれます。

    2. モジュールの実装

    生成されたクラスを継承し、SoftInputMode を動的に変更するモジュールを実装します。

    activity.window.setSoftInputMode は UI スレッドで実行する必要があるため、runOnUiThred で処理を実行します。 SOFT_INPUT_ADJUST_RESIZE は API level 30 から非推奨になっていたのですが、ここではそのまま使用しています。

    1// android/app/src/main/java/com/softinputmodesample/NativeSoftInputModeModule.kt
    2
    3package com.softinputmodesample
    4
    5import android.view.WindowManager
    6import com.facebook.react.bridge.ReactApplicationContext
    7import com.nativesoftinputmode.NativeSoftInputModeSpec
    8
    9class NativeSoftInputModeModule(reactContext: ReactApplicationContext) : NativeSoftInputModeSpec(reactContext) {
    10    override fun setSoftInputMode(mode: String) {
    11        val activity = currentActivity;
    12
    13        activity?.runOnUiThread {
    14            val softInputMode = when (mode) {
    15                "adjustNothing" -> WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
    16                else -> WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
    17            }
    18            activity.window.setSoftInputMode(softInputMode)
    19        }
    20    }
    21}

    3. パッケージの実装

    次に、TurboNativePackage を継承して、NativeSoftInputModePackage を作成します。 後の手順でこのパッケージを登録し、React Native でモジュールとして使用できるようにします。

    1// android/app/src/main/java/com/softinputmodesample/NativeSoftInputModePackage.kt
    2
    3package com.softinputmodesample
    4
    5import com.facebook.react.TurboReactPackage
    6import com.facebook.react.bridge.NativeModule
    7import com.facebook.react.bridge.ReactApplicationContext
    8import com.facebook.react.module.model.ReactModuleInfo
    9import com.facebook.react.module.model.ReactModuleInfoProvider
    10import com.nativesoftinputmode.NativeSoftInputModeSpec
    11
    12class NativeSoftInputModePackage : TurboReactPackage() {
    13    override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
    14        return if (name == NativeSoftInputModeSpec.NAME) {
    15            NativeSoftInputModeModule(reactContext)
    16        } else {
    17            null
    18        }
    19    }
    20
    21    override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
    22        mapOf(
    23            NativeSoftInputModeSpec.NAME to ReactModuleInfo(
    24                _name = NativeSoftInputModeSpec.NAME,
    25                _className = NativeSoftInputModeSpec.NAME,
    26                _canOverrideExistingModule = false,
    27                _needsEagerInit = false,
    28                isCxxModule = false,
    29                isTurboModule = true
    30            )
    31        )
    32    }
    33}

    4. パッケージの登録

    React Native でモジュールとして使用できるようにするため、パッケージを登録します。 MainApplication.kt の getPackages でパッケージを追加します。

    1// android/app/src/main/java/com/softinputmodesample/MainApplication.kt
    2
    3package com.softinputmodesample
    4
    5import android.app.Application
    6import com.facebook.react.PackageList
    7import com.facebook.react.ReactApplication
    8import com.facebook.react.ReactHost
    9import com.facebook.react.ReactNativeHost
    10import com.facebook.react.ReactPackage
    11import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
    12import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
    13import com.facebook.react.defaults.DefaultReactNativeHost
    14import com.facebook.react.soloader.OpenSourceMergedSoMapping
    15import com.facebook.soloader.SoLoader
    16
    17class MainApplication : Application(), ReactApplication {
    18
    19  override val reactNativeHost: ReactNativeHost =
    20      object : DefaultReactNativeHost(this) {
    21        override fun getPackages(): List =
    22            PackageList(this).packages.apply {
    23              add(NativeSoftInputModePackage())
    24            }
    25
    26        override fun getJSMainModuleName(): String = "index"
    27
    28        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
    29
    30        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
    31        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
    32      }
    33
    34  override val reactHost: ReactHost
    35    get() = getDefaultReactHost(applicationContext, reactNativeHost)
    36
    37  override fun onCreate() {
    38    super.onCreate()
    39    SoLoader.init(this, OpenSourceMergedSoMapping)
    40    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
    41      // If you opted-in for the New Architecture, we load the native entry point for this app.
    42      load()
    43    }
    44  }
    45}

    以上の実装で、React Native からモジュールを呼び出すことが可能になり、動的に windowSoftInputMode を変更できるようになります。

    コード生成する Platform を限定

    Turbo Native Module では型定義から iOS と Android のコード生成ができます。 しかし、片方の Platform でしか使用しないモジュールを実装する場合もあり、今回作成した NativeSoftInputMode も iOS では使用しません。

    そこで、今回の場合のようにコード生成する Platform を限定したい場合にはどうするのか調べてみました。

    限定する方法を検索しても見つけることができず、コードを見てみたところそれらしき実装があったので、その方法をご紹介したいと思います。

    限定する方法は簡単でモジュール名の末尾に Platform 名を付けるだけでできます。

    iOS だけに限定したい場合は IOS, Android だけに限定したい場合は Android を末尾に付けます。

    今回の場合で言うと、以下のようにすることで iOS ではコード生成をしないようにすることができました。

    1// specs/NativeSoftInputMode.ts
    2
    3//...
    4
    5export default TurboModuleRegistry.getEnforcing(
    6  'NativeSoftInputModeAndroid',
    7);

    ※ 上記の設定を行なっても、iOS のファイルは生成されてしまうため分かりづらいのですが、ios/build/generated/ios/NativeModuleSpec/NativeModuleSpec.h を見るとメソッドが定義されていないことが分かるかと思います。

    こちらの方法ですが、ドキュメントでの記載はないため、使用する場合は注意していきたいと思います。

    また、実装箇所ですが、react-native/scripts/codegen/generate-artifacts-executor.js のコード生成処理を追っていき、@react-native/codegen/lib/parsers/parsers-commons.js で該当箇所を見つけることができました。

    おわりに

    今回、特定の画面で Android の windowSoftInputMode を無効にする機能を Turbo Native Module を用いて実装できました。

    この方法を使うことで、キーボード表示時の画面レイアウト調整を変更することができ、KeyboardAvoidingView を使用した時の挙動を iOS と Android で合わせることができました。

    こちらの記事が何か参考になれば幸いです。 最後までお読みいただき、ありがとうございました!

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    いまむー(エンジニア)
    いまむー(エンジニア)
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background