【React Native】特定の画面で Android の SoftInputMode を無効にする方法
IT技術
はじめに
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 ネイティブコードの間でデータのやり取りを安全に行えます。
- プロジェクトルートに specs フォルダを作成
- 型定義を以下のように記述した 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 で合わせることができました。
こちらの記事が何か参考になれば幸いです。 最後までお読みいただき、ありがとうございました!
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
業務ではiOS開発に携わらせていただいています。 まだまだ分からないことだらけで、日々分からないことと戦いながら仕事をしている者です。 ブログ記事は暖かい目で見ていただけるとありがたいです。