~/muhammad-taha_

Flutter · Android · iOS · Based in the UAE

I build small apps that behave like large ones.

I'm Muhammad Taha, a Flutter developer based in the UAE. Twenty-eight of my apps are live on Google Play under the Tahatec name. They include developer tools, PDF and email viewers, calculators and fuel-price trackers. Four of them are on their way to the App Store.

Each app is single-purpose, and none of them is simple inside. A JWT decoder has to agree with OpenSSL. A chmod calculator has to agree with chmod. A JSON formatter must not quietly rewrite your numbers. So I test each one against the real tool it replaces, then run it on a device.

28Apps on Google Play
22,000+Installs
4,300+Automated tests
22Languages, RTL included

Installs are the sum of Google Play's install bands. Tests are the passing unit, widget and differential tests across the 28 published apps. Both checked October 2026.

Selected work

Twelve apps and the problem inside each one

Each card shows the live version, the test count, and the decision that mattered more than the feature list. They're all Flutter and Android-first, and they work offline wherever the job allows.

Hash Generator

v1.3.2

MD5, SHA and HMAC digests for text and multi-gigabyte files, with a history you can undo. It's the most-installed app in the catalogue.

The hard part: keeping a pure, dependency-free hashing engine testable while the app grew file hashing, undo, a GDPR consent flow, push and an iOS port. The App Store screenshot pass found a real bug: a long hash ran under the previous file's name. It was fixed for both platforms the same evening.
170 tests6 languages 10,000+ installsiOS soon

JWT Decoder

v1.0.0

Decode, inspect and verify JSON Web Tokens without the token ever leaving the phone.

The hard part: offline signature verification for 13 algorithms (HS, RS, PS, ES and ES256K), with keys from PEM, JWK or a whole JWKS. The crypto is tested against OpenSSL-generated vectors rather than against itself. That's how a PSS salt-length mismatch was caught before release.
315 tests10 languagesOffline crypto

JSON Formatter

v1.0.0

Format, validate and explore JSON without silently rewriting the document you pasted in.

The hard part: it does not use json.decode, which turns 1e5 into 100000.0, drops digits from big integers and loses duplicate keys. Instead it ships a hand-written parser. That parser keeps every number as literal text, reports duplicate keys, and returns error codes with line and column.
266 tests10 languagesOwn parser

Chmod Calculator

v1.0.0

Octal, symbolic and ls -l permissions as views of one 12-bit value.

The hard part: a 45,824-case differential test against both GNU coreutils and macOS chmod, with zero mismatches. Probing the real tools first turned up five commands where the two disagree. One is chmod 755 dir, which does not clear setgid.
244 testsDifferentialGNU + BSD

APK Analyzer

v1.0.2

Permissions, signing, components, DEX, native libraries and 16 KB readiness for any APK or AAB.

The hard part: every format is parsed in pure Dart: ZIP, binary XML, resources.arsc, protobuf manifests, DEX and ELF. The parsers are gated by a differential against Google's aapt2 and the NDK's llvm-readelf. That gate passed on its first run.
70 testsaapt2 differentialKotlin bridge

PDF Toolkit

v1.0.1

Merge, split, reorder, rotate, stamp, lock and unlock PDFs on the device.

The hard part: a Kotlin engine over PdfBox, checked by a 1,107-check gate. The gate runs every operation on a 14-file corpus and verifies the output with qpdf, poppler and mutool. The corpus includes AES and RC4 encryption, object streams and broken files. Rendering the outputs caught watermarks drawn in the wrong corner on rotated pages.
110 tests+1,107 output checks15 languages

Winmail Viewer

v1.0.2

Open winmail.dat, Outlook MSG, EML and MHT files, parsed entirely on the device.

The hard part: four parsers written from scratch: TNEF, Compound File/MSG, MIME and LZFu-compressed RTF. Each is tested against a reference implementation using fixtures from hand-written writers. Remote content is blocked by default. The app has its own Kotlin and Swift file bridges.
79 tests4 owned parsersiOS soon

Petrol Price Pakistan

v2.1.3

Today's fuel prices, price history, a fill-up log, trip costs and a home-screen widget.

The hard part: telling the truth about freshness. Firestore answers from its own offline cache, so the app said "Updated just now" in airplane mode. The fix gave the app's own cache ownership of offline. Prices now come from an hourly automated publisher. It refuses zero prices and suspicious jumps before anything reaches users.
108 testsEnglish + Urdu 1,000+ installsiOS soon

BMI Calculator

v2.0.0

Adult BMI, plus CDC BMI-for-age percentiles for children from 2 to 20.

The hard part: the old version asked for age and sex, then ignored both. It scored a healthy 8-year-old as "severe thinness". The rewrite uses the CDC's extended growth data. The CDC's own published percentile columns serve as the test oracle, and all 438 rows are rebuilt to the printed digit.
109 tests15 languagesCDC data

Qibla Compass

v1.1.0

Qibla direction, prayer times, manual location and a map, accurate without a network.

The hard part: no compass plugin exposes magnetic declination or sensor accuracy. So the app ships its own heading plugin in Kotlin and Swift. A device pass also caught every prayer time rendering in UTC. The timezone package never detects the device zone, and 121 passing tests hadn't noticed.
151 tests15 languagesSensors

Regex Tester

v1.0.0

Test, explain and debug regular expressions, with group positions and error carets.

The hard part: Dart's RegExp exposes no group names, no error offsets and no group positions. Those facts were measured, not assumed. So the app parses patterns itself. A pattern with runaway backtracking runs in an isolate that can be killed, so it can't freeze the app.
306 testsOwn parserIsolates

Shade Shift

v1.0.0

Spot the one tile with a different shade, against the clock, with a daily challenge.

The hard part: difficulty is a target CIE76 ΔE per level, reached by bisection, so level 40 is harder than level 20 on any screen. The daily challenge needs no server. The date seeds a PRNG that is pinned in the app, so it can't drift between releases.
195 testsColour scienceOffline

Everything on Google Play

All 28 apps

Every live listing on the Tahatec developer page, with its current version, install band and language count. Search by name or by what you need.

28 apps

In progress

What's coming next

How these get built

Test against the real thing, then run it on a device

The apps are unrelated, but they're all built the same way. They share services, release checks and the same project structure. A bug found in one app gets looked for in all the others.

Differential testing

When an app reimplements a real tool, the real tool becomes the answer key. Expected results come from measuring the reference, never from the code under test.

A device pass on every release

Release builds run on an emulator or phone before upload. Device passes have caught bugs that thousands of green tests missed, including prayer times in UTC, Morse code rendered backwards in Arabic, a keyboard hiding the field you were typing in, and a home-screen widget that would not load.

Own the code when the package is wrong

Several apps replace a popular package with their own code because the convenient option was quietly wrong. Examples include a JSON parser, a UUID generator, a compass plugin, a ZIP reader and intent bridges.

Localised from the first screen

Apps ship in up to 15 languages, and the catalogue covers 22. Arabic, Urdu and Persian mean right-to-left layout and bidi are design constraints from day one, not bug reports after launch.

Release gates that actually run

Release builds go through check.sh and a 16 KB page-size check before upload. Newer apps also inspect the release build to confirm R8 kept the constructors they need, and fail their tests if a service-account key is left in the tree. Store screenshots are generated by scripts and checked against the store's size rules.

Private by design

Hashing, JWT verification, PDF edits and email parsing all happen on the device, and the files you open are never uploaded. Each app's privacy policy is published here.

The reference each app is checked against

AppAnswer keyScale
Chmod CalculatorGNU coreutils and macOS chmod45,824 cases, zero mismatches
PDF Toolkitqpdf, poppler and mutool1,107 checks on a 14-file corpus
APK Analyzeraapt2 and llvm-readelf1,000+ resource ids resolved identically
JWT DecoderOpenSSL-generated test vectors13 signature algorithms
Cron Job CalculatorPython's croniter, in both directionsIncluding daylight-saving edges
Winmail Viewertnefparse, extract_msg and compressed_rtf4 formats, fixtures from own writers
BMI CalculatorCDC's published percentile columnsAll 438 rows reproduced

Toolchain

Stack

Every app shares this baseline. Apps started this year use the newer Kotlin-DSL Android config and target SDK 37.

~/apps — toolchain --baseline
# framework
flutter        3.47            # dart 3.13
languages      Dart, Kotlin, Swift  # python for tooling and gates

# android
gradle         9.x / agp 9.x   # kotlin dsl on new apps
sdk            target 36–37, min 24
release        r8 full mode, 16 KB aligned, symbol upload

# ios
deployment     iOS 15+, universal iPhone + iPad
native         own Swift channels where plugins fall short

# platform services
firebase       analytics, crashlytics, messaging, firestore, hosting
ads            google_mobile_ads + UMP consent, Meta mediation
automation     GitHub Actions price publisher, hourly

# quality
tests          unit, widget, integration, differential
gates          check.sh, check_16kb.sh, check_r8_keeps.sh, store-asset checks
lints          flutter_lints 6

Contact

Let's talk

I take on Flutter and Android work, whether that's a new app or rescuing one that has drifted a few toolchain versions behind. Email is the fastest way to reach me, about work or about any of the apps.