Creating custom keyboards for text input
An Android keyboard is not just a grid of buttons inside an app. Android treats it as an input method editor (IME), running in a service that connects to whichever text field has focus. That architecture determines its lifecycle, permissions and testing needs.
A custom implementation can provide a purpose-built layout, shared access across apps, better handling of particular fields or another typing language. It also creates real responsibilities: privacy, battery use, accessibility and compatibility.
This guide builds a native Latin IME rather than a keyboard Activity. The sample covers character keys, shift, backspace, enter and a candidate strip, while leaving swipe prediction, handwriting and broad language support for later development.
The examples use Kotlin and an en_AU subtype. They suit developers familiar with Android views and XML and provide a foundation for testing on real handsets before a Play Store release.
Understand the Android input method lifecycle
Start with the service lifecycle. InputMethodService creates the input view, receives the current EditorInfo, and exposes an InputConnection for inserting or deleting characters. The service may exist before an editor appears, so every operation must tolerate a missing connection.
Use onCreateInputView to inflate the keyboard and onStartInputView to apply settings for the new editor. Reset shift and composing text when the field changes, but retain harmless preferences such as the selected layout. Release references to the previous editor when input finishes.
Android can move between fields, split-screen apps and physical keyboards without destroying the IME. Handle those transitions deliberately; a keyboard that keeps stale editor state often inserts text into the wrong place.
Register the service and keyboard resources
Register the keyboard as a bound input method in the manifest. The BIND_INPUT_METHOD permission prevents other applications from starting it as an ordinary service.
<service
android:name=".ExampleImeService"
android:exported="true"
android:label="Example Keyboard"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data
android:name="android.view.im"
android:resource="@xml/method" />
</service>
Create res/xml/method.xml with the Australian English subtype, a keyboard layout resource and a separate view containing the keys and candidate bar. The metadata tells Android that the component belongs in the system keyboard picker.
A basic subtype can be declared as follows:
<input-method xmlns:android="http://schemas.android.com/apk/res/android">
<subtype
android:imeSubtypeLocale="en_AU"
android:imeSubtypeMode="keyboard" />
</input-method>
Inflate the keyboard in onCreateInputView. Each Key can use an output character, while function keys such as backspace and enter send their key codes. Keep definitions in XML for easy layout changes.
Handle keys, shifts and text commits
Map taps and physical key events to one set of actions. commitText is preferable for letters and phrases because it preserves the editor’s composing region and works with autocorrect-aware fields. A backspace can use deleteSurroundingText(1, 0) rather than synthesising a delete event.
Inside your InputMethodService, the core navigation handling can begin with:
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
val connection = currentInputConnection ?: return false
return when (keyCode) {
KeyEvent.KEYCODE_DEL -> {
connection.deleteSurroundingText(1, 0)
true
}
KeyEvent.KEYCODE_ENTER -> {
val action = currentInputEditorInfo?.imeOptions?.and(
EditorInfo.IME_MASK_ACTION
) ?: EditorInfo.IME_ACTION_NONE
if (action == EditorInfo.IME_ACTION_NONE) {
connection.commitText("\n", 1)
} else {
connection.performEditorAction(action)
}
true
}
else -> super.onKeyDown(keyCode, event)
}
}
The code above handles backspace and enter. Character buttons can call commitText directly, and shift can change the next output. For a word at a time, use commitCompletion; a prediction is not final text until the user chooses it.
Respect the editor’s imeOptions: enter may trigger search or send, while multiline fields need a newline. Use androidx.core.widget.EditorInfoCompat.isPassword(inputType) to suppress predictions and learned words for protected fields, and leave TYPE_NULL fields without normal text input.
Add candidates and local layouts
A candidate strip is a separate view above the key rows. Initially, a small bundled word list is enough to demonstrate selection and replacement; a trained language model can follow once the basic input path is reliable.
Users expect Australian English, local date and time formats, emoji access and fast switching between apps. Commuters on trains in Sydney and Melbourne often type one-handed, while regional connections favour an offline keyboard that does not depend on live lookup services.
Add other language layouts through additional subtypes, and inspect EditorInfo.inputType to choose numeric or email keys when needed. Automatic correction should offer alternatives rather than silently replacing a name, product or address.
Protect privacy and support accessibility
Treat every keystroke as potentially sensitive. A default keyboard should not log typed content, retain hidden text or send messages to a server. Keep learning data on the device, make any network feature opt-in, and explain it in the listing and Google Play data-safety form.
The default IME can read the clipboard on some Android versions, making clipboard history another privacy decision rather than a free feature. Google Play is the main Android app market in Australia, so keyboard permissions, policy enforcement and clear data disclosures affect trust and review outcomes.
If the publisher is a covered organisation under the Privacy Act 1988, the Australian Privacy Principles govern its handling of personal information. Consent, limited collection, security and transparency matter because people use keyboards for banking, health and private conversations. An app requesting ordinary suggestions has not authorised the keyboard to retain or publish them.
Use labelled controls, strong contrast, adequate touch targets and sensible TalkBack descriptions. Leave the input-method picker available so a user can return to another keyboard immediately.
Integrate, test and prepare for release
The keyboard must accept plain, multiline, numeric, URL and password fields. For date entry, keep the host Activity in charge: the guide to opening a DatePickerDialog demonstrates the standard EditText flow, while the IME supplies text.
Use adb to install and select a debug build quickly, replacing the package and service names with those in your project:
adb shell ime enable com.example.ime/.ExampleImeService
adb shell ime set com.example.ime/.ExampleImeService
Run the two lists below as release gates rather than optional extras.
Before implementation
- Create a sample Activity with plain, multiline, numeric and password fields.
- Register the IME, subtype metadata, key XML and input layout.
- Choose API 24 or later for the deletion API, then test the oldest supported release.
- Decide what the keyboard stores before adding dictionaries or diagnostics.
During device testing
- Enable the debug service with
ime enableand select it withime set. - Check cursor placement, composing text, selection and repeated backspace.
- Rotate the device, split the screen and switch keyboards between editors.
- Measure typing latency, battery use and behaviour without a network connection.
Publish after checking Play Console policy declarations and testing an offline build on several devices. Samsung, Google and budget Android handsets differ, yet every variant must remain switchable from the system’s keyboard controls.