How to Debug and Test Custom Keyboard Layouts with the FlickKeyboardView API
To debug and test custom keyboard layouts with the FlickKeyboardView API, build a KeyboardLayout with your KeyData definitions, attach it via setKeyboard(), enable verbose Logcat logging for runtime inspection, and validate behavior through JVM unit tests for layout logic and Espresso instrumentation tests for flick gestures.
The FlickKeyboardView class in the kazumaproject/japanesekeyboard repository provides the core UI component for rendering Japanese flick-style keyboards. When you debug and test custom keyboard layouts with the FlickKeyboardView API, you interact with a GridLayout-based container that dynamically constructs key views, attaches specialized flick controllers, and manages runtime state mutations through the dynamicKeyMap.
Understanding the FlickKeyboardView Architecture
Before debugging or testing, you must understand how data models convert into interactive views. The architecture cleanly separates layout definitions from view construction and gesture handling.
Core Components
The system consists of these primary classes located in the custom_keyboard module:
FlickKeyboardView: The mainGridLayoutcontainer defined inFlickKeyboardView.kt(lines 49-71) that manages key view creation, theme application, and stores the currentKeyboardLayoutinstance. It maintains adynamicKeyMap(lines 84-92) for runtime key updates.KeyboardLayout: A data class inKeyboardLayout.ktthat definesrowCount,columnCount, the list ofKeyDataobjects, and theflickKeyMapsthat map directions to actions for specific keys.KeyData: Defined inKeyData.kt, this class specifies visual properties (label, drawable), behavioralKeyType(STANDARD_FLICK, CIRCULAR_FLICK, etc.), grid position, and optionaldynamicStatesfor keys that change appearance at runtime.- Flick Controllers: Classes such as
CustomAngleFlickController.kt,StandardFlickInputController.kt,CrossFlickInputController.kt, andGridFlickInputController.ktimplement gesture detection algorithms and popup rendering. OnKeyboardActionListener: Interface defined withinFlickKeyboardView.kt(lines 53-60) that the host activity or IME implements to receive key text, action events, and flick-direction changes.
How Key Views Are Constructed
When you invoke setKeyboard(layout) in FlickKeyboardView.kt (lines 203-246), the view executes this sequence:
- Clears previous child views and controller instances via the layout clearing logic.
- Stores the new
KeyboardLayoutreference. - Iterates through
layout.keys, callingcreateKeyView(keyData)to build either anAppCompatImageButton(for icon keys) orAutoSizeButton(for text keys). - Invokes
attachKeyBehavior(keyView, keyData)to instantiate the appropriate flick controller based onKeyType, wiring its listener back to the view'sOnKeyboardActionListener. - Populates
dynamicKeyMap(lines 84-92) for any keys containing akeyId, enabling future runtime updates.
Debugging Custom Keyboard Layouts
Effective debugging requires visibility into the view's internal state and the ability to manipulate keys at runtime without full reconstruction.
Enable Verbose Logging
The FlickKeyboardView and controller classes emit debug logs at critical lifecycle points. Enable comprehensive logging in your debug build to trace layout rebuilds and controller attachments:
if (BuildConfig.DEBUG) {
Log.d("FlickKeyboardView", "Debug mode – full logging enabled")
}
Monitor Logcat for tags including FlickKeyboardView, CustomAngleFlickController, and StandardFlickInputController to trace when setKeyboard() rebuilds the grid, when controllers attach to specific keys, and when flick gestures trigger callbacks.
Inspect Runtime State with dynamicKeyMap
The dynamicKeyMap property (lines 84-92 in FlickKeyboardView.kt) stores metadata for keys that support state changes. You can inspect this map at runtime to verify key configurations and debug why updates might fail:
fun FlickKeyboardView.dumpDynamicKeys() {
dynamicKeyMap.forEach { (id, info) ->
Log.d("FlickDebug", "KeyId=$id label=${info.keyData.label} action=${info.keyData.action}")
}
}
This is particularly useful when debugging why updateDynamicKey might not affect the expected key, as it confirms whether the keyId exists in the map and what state it currently holds.
Step-Through Dynamic Key Updates
The updateDynamicKey(keyId, stateIndex) method allows runtime mutation of key appearance and behavior without rebuilding the entire keyboard. The method executes this sequence in FlickKeyboardView.kt:
- Retrieves the stored
KeyInfofromdynamicKeyMap. - Constructs a new
KeyDatainstance with the specified state index. - Determines if the view type must change (icon versus text) – lines 76-78.
- Detaches the existing controller via
detachKeyBehavior. - Attaches a new controller via
attachKeyBehaviorwith the updatedKeyData. - If the view type changed, replaces the view in the GridLayout while preserving layout parameters – lines 82-89.
Common Debugging Pitfalls
| Symptom | Likely Cause | Fix |
|---|---|---|
| Flick pop-ups never appear | Controller not attached due to missing flickKeyMaps entry |
Verify layout.flickKeyMaps[keyData.label] contains valid direction mappings for the specific key label. |
Wrong label after updateDynamicKey |
Stale dynamicKeyMap entry or wrong view instance |
Ensure you call updateDynamicKey on the same FlickKeyboardView instance that executed setKeyboard, and verify the keyId matches exactly. |
| Theme colors not applied | applyKeyboardTheme called after setKeyboard without rebuild |
Either call applyKeyboardTheme before setKeyboard, or re-invoke setKeyboard after theme changes to force view reconstruction with new theme attributes. |
Testing Custom Keyboard Layouts
Comprehensive testing validates both the data structure correctness and the interactive gesture behavior across different scenarios.
Unit Testing Layout Construction
Test layout logic on the JVM without Android runtime dependencies. The KeyboardLayout and KeyData classes are pure Kotlin data classes suitable for unit testing, while view construction requires a Robolectric or AndroidX test context.
class FlickKeyboardViewTest {
private lateinit var context: Context
private lateinit var view: FlickKeyboardView
@Before
fun setUp() {
context = ApplicationProvider.getApplicationContext()
view = FlickKeyboardView(context)
}
@Test
fun `setKeyboard builds correct number of child views`() {
val layout = KeyboardLayout(
rowCount = 2,
columnCount = 3,
keys = listOf(
KeyData(
label = "A",
keyType = KeyType.STANDARD_FLICK,
row = 0,
column = 0,
rowSpan = 1,
colSpan = 1,
keyId = null
)
),
flickKeyMaps = emptyMap()
)
view.setKeyboard(layout)
assertEquals(1, view.childCount)
val firstKey = view.getChildAt(0) as Button
assertEquals("A", firstKey.text)
}
}
Use ApplicationProvider from androidx.test:core to obtain a context without launching an activity, allowing fast JVM test execution.
Instrumentation Testing for Flick Gestures
Validate flick interactions using Espresso in an Android instrumentation test. Launch a test activity, inject a layout with defined flick maps, and simulate directional swipes.
@RunWith(AndroidJUnit4::class)
class FlickInteractionTest {
@Rule @JvmField
val activityRule = ActivityScenarioRule<TestKeyboardActivity>()
@Test
fun `circular flick triggers correct character`() {
activityRule.scenario.onActivity { activity ->
val layout = KeyboardLayout(
rowCount = 1,
columnCount = 1,
keys = listOf(
KeyData(
label = "へ",
keyType = KeyType.CIRCULAR_FLICK,
row = 0,
column = 0,
rowSpan = 1,
colSpan = 1
)
),
flickKeyMaps = mapOf(
"へ" to listOf(
mapOf(
FlickDirection.UP to FlickAction.Input("へ"),
FlickDirection.RIGHT to FlickAction.Input("べ")
)
)
)
)
activity.keyboardView.setKeyboard(layout)
}
onView(withId(R.id.flickKeyboardView))
.perform(swipeFromCenterTo(Direction.UP, 150))
onView(withId(R.id.outputTextView))
.check(matches(withText("へ")))
}
}
Implement swipeFromCenterTo using Espresso's GeneralSwipeAction to simulate precise directional movements from the center of the target view.
Validating Dynamic Key Updates
Test state transitions for keys with multiple states using updateDynamicKey to ensure the view updates without reconstruction.
@Test
fun `updateDynamicKey changes label and action`() {
val enterKey = KeyData(
label = "Enter",
keyType = KeyType.NORMAL,
row = 0,
column = 2,
keyId = "enter_key",
dynamicStates = listOf(
KeyData.State(label = "Enter", action = KeyAction.Enter),
KeyData.State(label = "↵", action = KeyAction.NewLine)
)
)
val layout = KeyboardLayout(
rowCount = 1,
columnCount = 3,
keys = listOf(enterKey),
flickKeyMaps = emptyMap()
)
view.setKeyboard(layout)
val btn = view.getChildAt(0) as Button
assertEquals("Enter", btn.text)
view.updateDynamicKey("enter_key", 1)
assertEquals("↵", btn.text)
}
Summary
- FlickKeyboardView acts as the
GridLayoutcontainer that builds key views fromKeyboardLayoutdefinitions and manages flick controllers according toKeyType. - Debug by enabling Logcat logging for
FlickKeyboardViewand controller tags, inspectingdynamicKeyMapat runtime, and stepping throughupdateDynamicKeyfor state mutations. - Test layout construction with JVM unit tests using
ApplicationProvider, validate flick gestures with Espresso instrumentation tests simulating directional swipes, and verify dynamic key state changes through assertions on view properties. - Reference specific source files like
FlickKeyboardView.kt(lines 49-71, 84-92, 203-246) and controller implementations to ground your debugging in the actual implementation.
Frequently Asked Questions
How do I enable debug logging for FlickKeyboardView?
Enable verbose logging by filtering Logcat for tags FlickKeyboardView, CustomAngleFlickController, and StandardFlickInputController. The source code in FlickKeyboardView.kt already includes Log.d calls at critical points such as layout rebuilds in setKeyboard() and controller attachments. For additional tracing, wrap your OnKeyboardActionListener implementations with log statements to capture key text and flick direction callbacks.
What is the difference between KeyboardLayout and KeyData?
KeyboardLayout is a data class defined in KeyboardLayout.kt that describes the overall keyboard structure, including rowCount, columnCount, the list of KeyData objects, and the flickKeyMaps that define flick directions for specific keys. KeyData, defined in KeyData.kt, represents individual key properties such as label, keyType (STANDARD_FLICK, CIRCULAR_FLICK, etc.), grid position coordinates, and optional dynamicStates for keys that change appearance or behavior at runtime.
How can I test flick gestures without a physical device?
Use Android instrumentation tests with Espresso to simulate flick gestures on an emulator or test device. Create a test activity that hosts FlickKeyboardView, inject a KeyboardLayout with defined flickKeyMaps, then use a custom GeneralSwipeAction to perform directional swipes from the center of specific keys. Verify the results by checking that your test activity's OnKeyboardActionListener received the expected text or action callbacks, or by asserting on the state of an output view.
Why does my dynamic key not update after calling updateDynamicKey?
The most common cause is calling updateDynamicKey on a different FlickKeyboardView instance than the one that built the layout, or providing a keyId that does not match the keyId assigned in the original KeyData. Additionally, if the KeyData lacks dynamicStates or the stateIndex exceeds the available states, the update will fail silently. Verify that the key exists in dynamicKeyMap by dumping its contents at runtime, and ensure you are using the same view instance that originally called setKeyboard.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →