DEV Community

yramstech
yramstech

Posted on

How I Built an Android Keyboard in Kotlin for Two Ghanaian Languages

In late 2022 I got tired of watching friends type Ewe and Twi messages with English keyboards, missing all the letters that make those languages what they are. The diacritics — ɛ, ɔ, ŋ, ɖ, ƒ, ʋ, ɣ — were either auto-corrected out of existence or typed with workarounds like "3" for ɛ or ")","or" and "o" for ɔ.

So I gave myself two months to ship an Android Input Method Editor (IME) that would let Ewe and Twi speakers type natively, with every diacritic one tap away.

On February 25, 2023, Ewe Twi Keyboard went live on the Google Play Store. Three years and 10,000+ installs later, it's on v4 with a premium tier and a steady install rate.

Here's what I learned.

The problem — deeper than I thought

Before writing the first line of code, I tried every existing "African language keyboard" on Play Store. The results fell into three buckets:

  1. English keyboards with an emoji-style diacritic panel. Users had to tap twice for every accented character. Nobody would use that for a sentence, let alone a conversation.

  2. Keyboards that treated Ewe and Twi as afterthoughts. The interesting insight here is linguistic: every Twi letter is also an Ewe letter. Ewe's alphabet is a superset. So one well-designed keyboard can serve both languages natively — but most of what I tried bolted the extra letters onto an English layout without thinking about how people actually type mixed-language messages.

  3. Abandoned apps. Beautiful ideas from 2016–2019, no updates since, broken on Android 10+.

The design goal became clear: one keyboard, both languages, no language-switching dance, every diacritic accessible in ≤ 1 extra tap.

The stack

The v4 app today is Kotlin end-to-end, with a clean separation between the keyboard service (which Android treats specially as an IME) and the rest of the app. It wasn't always this way — the 2023 release was Java — but more on that below. Here's the current stack:

  • Kotlin + InputMethodService — every Android IME extends this system service class. The OS decides when to show/hide the keyboard; I just provide the View.
  • Jetpack Compose + Material 3 — used for the settings screen, onboarding, and premium-upgrade flow. The actual keyboard layout stayed on the classic Android View system for performance reasons (you cannot afford Compose recomposition latency on every keystroke).
  • Clean architectureservice/, domain/, data/, ui/. A KeyboardLayoutProvider caches 200+ key objects so I'm not rebuilding them on every keystroke; a WordPredictor handles suggestions; an EmojiProvider serves the emoji panel; a LocaleHelper toggles English, French, and Spanish for the settings UI.
  • DataStore Preferences — for user settings (theme, sound, vibration, auto-capitalise). Async-first, avoids the pitfalls of SharedPreferences.
  • Coroutines throughout — for billing checks, update prompts, and anything that could block the IME thread.
  • Firebase Crashlytics + Analytics — an IME can crash the whole user experience across every app, so telemetry is non-optional.
  • Google Play Billing (BillingClient) — a premium tier removes ads. A coupon validator lets beta users and early supporters unlock it for free.
  • Google Play In-App Updates — if a user is on an old version, the app nudges them to update inline.
  • Google Mobile Ads SDK (AdMob) — banner ads in the settings screen only (never in the keyboard view itself — that would violate Play policy and user trust).
  • Lottie Compose — animated onboarding illustrations that show users how to enable the keyboard from Android settings.

No backend. No accounts. No text logging. Zero network calls from the keyboard view itself.

A note on 2023 vs 2026: the Java → Kotlin rewrite

The stack above describes the app as it exists today. The honest 2023 version was different: I originally shipped Ewe Twi Keyboard in Java. It worked, it passed Play Store review in 24 hours, and it had everything the first release needed.

In January and February 2026 I migrated the whole codebase to Kotlin, file by file, with Java and Kotlin coexisting in the repo during the transition. The trigger was Jetpack Compose: the new settings screens, onboarding flow, and premium-upgrade UI all wanted Compose — which is Kotlin-only. Rather than bolt Compose onto a Java app with a thin Kotlin surface, I committed to the full migration.

Deliberate ordering: the UI layer migrated first, the IME service last. Compose forced the settings, theme, and premium screens into Kotlin early. The CustomKeyboardService — the thing that actually runs inside every other app while you type — I saved for the end. A bug in an IME doesn't just break your own app; it makes every other app feel broken because users suddenly can't type. I wanted the surrounding data, domain, and UI layers stable and in idiomatic Kotlin before touching the keyboard surface itself.

Android Studio's "Convert Java File to Kotlin" action was a starting point, not an endpoint. Every converted file needed a second pass for idiomatic Kotlin — nullability, extension functions, coroutines where callbacks used to live. But the interop layer Google ships is better than its reputation: the app stayed buildable and shippable throughout the migration, with mixed-language modules compiling cleanly.

The diacritic design

The core interaction pattern: long-press a base letter, a popup appears with every diacritic variant, tap the one you want.

  • Tap e → you get e
  • Long-press e → popup shows e, ɛ, and the tone marks ɛ́, ɛ̀, ɛ̃, ɛ̂, ɛ̌
  • Same pattern for o (→ ɔ and its tonal variants), n (→ ŋ), d (→ ɖ), f (→ ƒ), v (→ ʋ), g (→ ɣ)

A fluent user never leaves the home row. Every diacritic is one long-press away — not a separate layer, not a separate keyboard, not an emoji panel.

Publishing to Play Store — the paperwork, not the code

Keyboards get extra scrutiny from Google. Because IMEs see every keystroke a user types — passwords, credit card numbers, private messages — Play Console requires explicit answers to Data Safety questions about what the app stores, logs, or transmits.

My answers were simple because the architecture was simple: nothing leaves the device. No keystroke logging, no typed-text analytics, no cloud sync of any kind. The review passed in 24 hours.

The manifest still needs the standard IME plumbing — a service declaration with the BIND_INPUT_METHOD permission and the android.view.InputMethod intent filter, plus a method.xml that declares the subtypes Android shows in the system keyboard picker. Standard Android IME boilerplate; nothing exotic.

Things that surprised me

  • Views beat Compose for the keyboard itself. I tried building the key grid in Compose early on. Recomposition latency on every keystroke was visible even on a Pixel. I fell back to a classic View subclass for the keyboard surface and haven't regretted it. Compose is fantastic — just not inside the hot path.
  • Localisation is a first-class design constraint, not a feature. Once I accepted every keypress had to be optimal for mixed-language use, design decisions got easier. The "one keyboard, both languages" insight flowed naturally from watching how my friends actually type.
  • Crashlytics pays for itself in week one. An IME crash doesn't just break your app — it makes every other app feel broken because users can't type. Telemetry caught three OEM-specific bugs I never saw on my own device.
  • The narrowest-market projects have the most loyal users. Ewe Twi Keyboard will never compete with Gboard. But the users who install it use it every day. Three years after launch, the app has 10,000+ installs on Google Play, is on v4 with a premium tier, and keeps growing — proof that shipping small into a specific community compounds.

Try it

If you're a Kotlin developer wondering what to build for your community — look at the little frictions your friends complain about. The audience is narrower than "everyone," but it's real, and they notice when you ship something that works.

Top comments (0)