add android keyboard language change detection

uses ContentObserver on Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE
to detect when the user switches keyboard language on Android.
deduplicates events like the iOS side.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-03-01 10:15:28 +02:00
parent e8793e49c8
commit b1389cdf27
@@ -1,31 +1,68 @@
package expo.modules.keyboardlanguage package expo.modules.keyboardlanguage
import android.database.ContentObserver
import android.os.Build import android.os.Build
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition import expo.modules.kotlin.modules.ModuleDefinition
class ExpoKeyboardLanguageModule : Module() { class ExpoKeyboardLanguageModule : Module() {
private var subtypeObserver: ContentObserver? = null
private var lastLanguage: String? = null
override fun definition() = override fun definition() =
ModuleDefinition { ModuleDefinition {
Name("ExpoKeyboardLanguage") Name("ExpoKeyboardLanguage")
Function("getCurrentKeyboardLanguage") { Events("onKeyboardLanguageChange")
val context = appContext.reactContext ?: return@Function null
val imm = context.getSystemService<InputMethodManager>() ?: return@Function null
val subtype = imm.currentInputMethodSubtype ?: return@Function null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Function("getCurrentKeyboardLanguage") {
val tag = subtype.languageTag return@Function getCurrentLanguage()
if (tag.isNotEmpty()) return@Function tag }
OnStartObserving {
val context = appContext.reactContext ?: return@OnStartObserving
val uri = Settings.Secure.getUriFor("selected_input_method_subtype")
subtypeObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
override fun onChange(selfChange: Boolean) {
val language = getCurrentLanguage()
if (language != lastLanguage) {
lastLanguage = language
sendEvent("onKeyboardLanguageChange", mapOf("language" to language))
}
}
} }
@Suppress("DEPRECATION") context.contentResolver.registerContentObserver(uri, false, subtypeObserver!!)
val locale = subtype.locale }
if (locale.isNotEmpty()) return@Function locale
return@Function null OnStopObserving {
subtypeObserver?.let { observer ->
appContext.reactContext?.contentResolver?.unregisterContentObserver(observer)
}
subtypeObserver = null
} }
} }
private fun getCurrentLanguage(): String? {
val context = appContext.reactContext ?: return null
val imm = context.getSystemService<InputMethodManager>() ?: return null
val subtype = imm.currentInputMethodSubtype ?: return null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val tag = subtype.languageTag
if (tag.isNotEmpty()) return tag
}
@Suppress("DEPRECATION")
val locale = subtype.locale
if (locale.isNotEmpty()) return locale
return null
}
} }