How Todorant's native Android app reached Google Play in four days, and lost 4.5 MB on the way
Todorant's Android rewrite went from test build to Google Play in four releases: a deleted Google sign-in client, an R8 crash in QR login, and an unlicensed font that made the APK 7.7 MB instead of 3.3 MB.
On this page
Todorant’s native Android app went from a test build to a live Google Play release in four days, October 13 to 16, 2026, and the version that shipped is 3.26 MB instead of 7.74 MB. It took four releases to get there (2.2.0 to 2.2.3), and each fixed something that only showed up once the build was a real release: a deleted Google Cloud project, a crash that R8 caused, and a 4.5 MB font I was not allowed to ship. This is the list, with the commits.
What was Todorant on Android before October 2026?
Todorant is a to-do manager that shows one task at a time and makes you plan every overdue one again before you continue. The Android app on Google Play was the old React Native one. In October I replaced it with a native Kotlin and Jetpack Compose app that syncs through the new /v2 backend over one WebSocket. The 2.0.0 release on October 13 was the first build of it, and it was only a test build: Android refuses to update an app signed with a different key, so anyone moving from the old Play build had to open it once to finish syncing, uninstall it, and install the new APK. Everything came back from the server after sign-in.
Why did Google sign-in fail in 2.2.1?
Version 2.1.0 shipped with a known issue: Google sign-in did not work. The reason was boring. The Google Cloud project behind the sign-in client had been deleted, so every attempt ended in a deleted_client error. The fix in 2.2.1 on October 14 was a new Google Cloud project with a web client (redirect https://todorant.com/google_login_result) and Android clients for both the upload key and the Play signing key. That second part matters: Play re-signs your app, so sign-in needs to know both fingerprints or it works in your test build and fails for every user who installed from the store.
The same release removed Google Calendar sync. It had stopped working, and the Integrations screen that hosted it is gone.
What did R8 break in 2.2.2?
Google Play’s pre-launch report ran the 2.2.1 build on test devices and found that scanning a QR code closed the app. QR login is how you sign in on a new device by scanning the code the web app shows, so this was not a corner of the app.
The cause was R8 in full mode. ML Kit’s barcode scanner finds its internal components through registrar classes that it creates by reflection, using their no-argument constructors. R8 saw no code calling those constructors and removed them. ComponentDiscovery then could not build the registrars, one shared-preferences component was never registered, and GmsBarcodeScanning.getClient() threw a NullPointerException. The fix in 2.2.2, merged on October 15, is a few lines in proguard-rules.pro that keep those constructors. It is the sort of bug a debug build never shows, which is the argument for running the pre-launch report before every promotion rather than once.
How did the APK lose 4.5 MB in 2.2.3?
The Android app reused the iOS app’s typography, which is Apple’s SF Pro in six font files. SF Pro is licensed for Apple platforms only, so it should never have been in an Android build. It was also large: the commit that removed it records 4.5 MB of the bundle. I switched to Android’s system font and kept the font families and weights as they were, so no call site changed.
Here are the release assets on Forgejo, measured on November 2, 2026:
| Release | Date | APK size |
|---|---|---|
| 2.2.0 | Oct 14 | 7,739,907 bytes |
| 2.2.2 | Oct 16 | 7,738,839 bytes |
| 2.2.3 | Oct 16 | 3,256,419 bytes |
| 2.2.4 F-Droid build | Oct 16 | 2,754,408 bytes |
The drop from 2.2.2 to 2.2.3 is 4,482,420 bytes. Version 2.2.3 is the build that went to Google Play, and the README stopped pointing Android users at a manual APK on the same day.
Why is there a separate F-Droid build?
The 2.2.4 release (October 16) split the app into two flavors. The play flavor keeps Google sign-in and Google’s code-scanner. The fdroid flavor has no Google code at all: it scans QR codes with the open-source ZXing library, which needs the camera permission, and it has no Google sign-in button. You can still sign in with an email and password, Apple, Telegram or a QR code. F-Droid also rejects the dependency-info block that Gradle adds to APKs, so the release build leaves it out, and CI now builds both flavors with the upload key when a release is published and attaches both files so F-Droid can verify its own build against ours. The F-Droid APK is about 490 KB smaller than the Play one.
What happened to the 28 permissions?
On October 17 the 2.2.6 release removed a pile of permissions that Todorant never used. A library for launcher badges, ShortcutBadger, declares one permission for every launcher vendor it knows about, and Credential Manager adds two biometric permissions for passkeys. The manifest merger put all of them in the app. Reading the commit’s diff, I count 22 tools:node="remove" lines for them, and VIBRATE went too, because swipe feedback now uses Compose haptics, which need no permission. The commit message counts the app’s permissions as 28 before and 5 after: internet, network state, the boot receiver and notifications for the planning reminder (asked for only when you set a reminder time), and, in the F-Droid build only, the camera. The release notes say it plainly: Todorant now asks only for the permissions it actually uses.
I did not build the app to read the merged manifest myself, so the 28 and the 5 are the numbers from the commit, and the 22 is my own count of the lines removed.
What did the first real users find?
Two things, and both were mine. On October 16 I fixed the first sign-in on large accounts. A fresh sign-in pulls the whole account, and four places in the app each started their own full download, so a test account with 40,000 to-dos came down two or three times at once and ran out of memory. The server’s heartbeat reply queued behind that download, the 15-second client ping closed the socket, and the 30-second sync timeout counted the download time too. Meanwhile the app showed only the Settings tab. The fix makes a pull with the same cursor join the one already in flight, gives sync 30 seconds to connect and 120 seconds to answer, and raises the ping to 60 seconds. On October 23, 2.2.7 restored tasks that people had stored only on the device in the old app, which the update had hidden.
What I would do the same way next time
- Run the Play pre-launch report on every build that is headed for production. It found the one crash that mattered.
- Check the license of every asset you carry over from another platform. An APK that is twice the size it should be is a good reason to look.
- Keep a second Google sign-in client for Play’s signing key from day one.
- Treat every permission in the merged manifest as something you have to be able to explain.
Todorant is on Google Play, on the App Store, on F-Droid and at todorant.com. If you would rather run your own server, the self-hosting guide takes one compose file. The source is on GitHub.
Written November 2, 2026 from the repository at backmeupplz/todorant (tags v2.2.12 and earlier), its release assets on Forgejo and the commits cited above.