# How the Dynamic Key State System Updates Keyboard Keys at Runtime in JapaneseKeyboard

> Discover how the dynamic key state system in JapaneseKeyboard updates key labels actions and icons at runtime without rebuilding layouts Learn more about this efficient approach

- Repository: [Kazu/japanesekeyboard](https://github.com/kazumaproject/japanesekeyboard)
- Tags: internals
- Published: 2026-03-05

---

**The dynamic key state system in JapaneseKeyboard uses a runtime map of key IDs to state indices that updates individual key labels, actions, and icons without rebuilding the entire layout.**

The Japanese Keyboard (`kazumaproject/japanesekeyboard`) implements a sophisticated **dynamic key state system** that enables individual keys to change their appearance and behavior while the keyboard remains visible. This mechanism powers features like toggling Enter key functions, switching between Dakuten and Katakana modes, and modifying spacebar behavior on demand. Understanding this system reveals how the repository achieves fluid UI updates without expensive full-layout reconstructions.

## Architecture of the Dynamic Key State System

The system operates through three coordinated layers: static layout definition, runtime state mapping, and view-level mutation.

### Static Layout Definition with Dynamic States

Each key that supports runtime changes is defined in [`KeyboardDefaultLayouts.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/KeyboardDefaultLayouts.kt) with a list of possible **dynamic states** stored as `FlickAction` objects. When `createFinalLayout` generates the base layout, keys include a `dynamicStates` property containing all potential variations.

For example, the Enter key defines multiple behavioral states:

```kotlin
private val enterKeyStates = listOf(
    FlickAction.Action(KeyAction.NewLine, "改行"),
    FlickAction.Action(KeyAction.Confirm, drawableResId = R.drawable.baseline_arrow_right_alt_24),
    FlickAction.Action(KeyAction.Enter, drawableResId = R.drawable.baseline_keyboard_return_24),
    // Additional variations...
)

```

The `applyKeyState` helper method in [`KeyboardDefaultLayouts.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/KeyboardDefaultLayouts.kt) handles the actual substitution:

```kotlin
private fun applyKeyState(baseLayout: KeyboardLayout, keyId: String, stateIndex: Int): KeyboardLayout

```

This function locates the target key by `keyId`, selects the `FlickAction` at `stateIndex` (defaulting to the first state if invalid), and constructs a new `KeyData` instance with the updated label, action, and drawable resource.

### Runtime State Selection via dynamicKeyStates Map

When users interact with mode toggles, [`IMEService.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/IMEService.kt) constructs a `dynamicKeyStates` map that tracks which variation each mutable key should display. This map uses string key IDs as identifiers and zero-based integers as state indices.

A typical runtime configuration looks like:

```kotlin
dynamicKeyStates = mapOf(
    "enter_key" to currentEnterKeyIndex,
    "dakuten_toggle_key" to currentDakutenKeyIndex,
    "katakana_toggle_key" to currentKatakanaKeyIndex,
    "space_convert_key" to currentSpaceKeyIndex
)

```

The system passes this map to `KeyboardDefaultLayouts.createFinalLayout`, which iterates through the entries and applies each state change via `applyKeyState`. Alternatively, for incremental updates, the map can be stored and applied directly to the view layer.

### View-Level Application with updateDynamicKey

[`FlickKeyboardView.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/FlickKeyboardView.kt) implements `updateDynamicKey(keyId, stateIndex)` to mutate the visible keyboard without regenerating the entire layout. This method performs a live lookup of the key in the current `KeyboardLayout`, extracts the corresponding `FlickAction` from `dynamicStates`, and swaps the `KeyData` instance in the layout's key list.

The view then triggers a targeted redraw using `notifyItemChanged` on the underlying adapter, ensuring only the affected key refreshes while neighboring keys remain stable.

## Core Implementation Files

The dynamic key state system spans three critical files:

- **[`custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/layout/KeyboardDefaultLayouts.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/layout/KeyboardDefaultLayouts.kt)** – Generates base layouts, maintains `dynamicStates` lists for each mutable key, and implements `applyKeyState` for state substitution.

- **[`custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/view/FlickKeyboardView.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/view/FlickKeyboardView.kt)** – Hosts the visual keyboard component and implements `updateDynamicKey` to perform granular UI updates.

- **[`app/src/main/java/com/kazumaproject/markdownhelperkeyboard/ime_service/IMEService.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/ime_service/IMEService.kt)** – Coordinates runtime state changes, constructs the `dynamicKeyStates` map based on user input, and triggers layout updates.

## Practical Code Examples

### Creating a Layout with Predefined States

Initialize the keyboard with specific key variations using the `dynamicKeyStates` parameter:

```kotlin
val dynamicStates = mapOf(
    "enter_key" to 2,               // Select the third Enter key variant
    "dakuten_toggle_key" to 1       // Select the second Dakuten option
)

val layout = KeyboardDefaultLayouts.createFinalLayout(
    mode = KeyboardInputMode.HIRAGANA,
    dynamicKeyStates = dynamicStates,
    inputLayoutType = "toggle",
    inputStyle = "default",
    isDeleteFlickEnabled = true
)

```

This produces a `KeyboardLayout` instance with the specified variants already applied to the Enter and Dakuten keys.

### Updating a Single Key at Runtime

When users toggle the Enter key mode, update only that specific key:

```kotlin
// Inside IMEService or UI controller
customLayoutDefault.updateDynamicKey(
    keyId = "enter_key",
    stateIndex = newEnterKeyIndex   // 0 = NewLine, 1 = Confirm, 2 = Enter, etc.
)

```

The view immediately refreshes the key's label and drawable without disrupting other keys.

### Performing a Full Layout Refresh

After multiple state changes, regenerate the complete layout with updated states:

```kotlin
val newDynamicMap = mapOf(
    "enter_key" to currentEnterKeyIndex,
    "dakuten_toggle_key" to currentDakutenKeyIndex,
    "space_convert_key" to currentSpaceKeyIndex,
    "katakana_toggle_key" to currentKatakanaKeyIndex
)

val refreshedLayout = KeyboardDefaultLayouts.createFinalLayout(
    mode = customKeyboardMode,
    dynamicKeyStates = newDynamicMap,
    inputLayoutType = sumireInputKeyLayoutType ?: "toggle",
    inputStyle = sumireInputStyle ?: "default",
    isDeleteFlickEnabled = isDeleteLeftFlickPreference ?: true
)

customLayoutDefault.setKeyboard(refreshedLayout)

```

## Summary

- The **dynamic key state system** uses a `Map<String, Int>` structure (`dynamicKeyStates`) to track which variation each key should display.
- `KeyboardDefaultLayouts.createFinalLayout` accepts this map and applies states via `applyKeyState`, generating a complete layout with substituted `KeyData` objects.
- `FlickKeyboardView.updateDynamicKey` enables surgical updates to individual keys, triggering targeted adapter refreshes rather than full layout rebuilds.
- Keys define their mutable states as lists of `FlickAction` objects in `dynamicStates`, allowing extensible behavior modification without structural changes.
- The separation between layout generation (`KeyboardDefaultLayouts`) and view mutation (`FlickKeyboardView`) facilitates testing and maintenance.

## Frequently Asked Questions

### How does the system know which key variant to display?

The system looks up the key ID in the `dynamicKeyStates` map, retrieves the integer state index, and selects the corresponding `FlickAction` from the key's `dynamicStates` list. If the index is out of bounds, it defaults to the first state in the list.

### Can I add new dynamic states to existing keys without modifying core files?

Yes. Since `dynamicStates` are defined as mutable lists within [`KeyboardDefaultLayouts.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/KeyboardDefaultLayouts.kt), you can append new `FlickAction` entries to any key's state list. The `applyKeyState` method automatically handles any valid index within the expanded list.

### What is the performance impact of calling updateDynamicKey frequently?

The impact is minimal because `updateDynamicKey` in [`FlickKeyboardView.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/FlickKeyboardView.kt) calls `notifyItemChanged` on the underlying RecyclerView adapter, which triggers a partial bind for only the affected key view. This avoids the layout inflation and measurement costs associated with full keyboard rebuilds.

### How does IMEService coordinate state changes across different input modes?

[`IMEService.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/IMEService.kt) maintains current state indices as instance variables (such as `currentEnterKeyIndex` and `currentDakutenKeyIndex`), updates these values when users activate toggles, and reconstructs the `dynamicKeyStates` map before passing it to either `createFinalLayout` or `updateDynamicKey` depending on whether a full refresh or incremental update is required.