u/Character_Oven_1511

I created a 126K - line Android app with AI - here is the workflow that actually worked for me

I really wanted to see how far I can go. Can I create a meaningful and complex application, big enough, but without knowing the language.

Yes, I have 18+ years of experience as software developer, using Java, GoLang, Scala. But I have no experience with Kotlin. And to learn Kotlin, to learn the Android libraries, it is not an easy job. I may need at least of year of active learning and trying things, before having the confidence to start something that will work.

So, I asked myself, how far can I go with AI tools? And I went far!

I created an application for monitoring elderly people. If they are not moving, if they are lost, if their daily habits are changing too much, someone will get notified. I won't post the link here to the app, because the post might get banned, even though I desperately search for a way to make it more publicly recognizable

The boring statistics:

  • 126,000 lines of Kotlin across 398 files
  • 45,000 lines of tests across 130 files
  • 3000+ unit tests
  • 3 languages (English, Bulgarian, German)
  • 50+ production lessons captured in CRITICAL_DONTS.md
  • 4 months from zero Kotlin experience to production app on Google Play
  • 0 lines of Kotlin written manually by me

Below are the main things that I did and helped me do the product!

Good framework

Vibecoding something that big is not an option. You can't write anything in one session. Vibecoding works best for my daughter. If you need something big, you need a way to keep the context somewhere, to define the rules. You need specification. To maintain my specifications I use BMAD framework. It helped me a lot to clear the requirements, prepare the architecture, UI, find any gaps. I used it also a lot for brainstorming session and for marketing. For the latter, it failed 😉

Follow the project rules

I use Claude Code with Opus 4.6 (yes, I prefer the old version) and keep all my important rules save in CLAUDE.md file.

Every rule in that file exists because I violated it once and something broke. The file grows with the project.

This is the key insight: CLAUDE.md turns one-time lessons into permanent constraints. The AI never forgets a rule I put there. I forget constantly.

Keep your documentation up to date

I start my session with a custom command that loads all important lessons learned, release notes, rules, architecture. All needed, enough context, so that when I start prompting, the AI has the basis to make best decisions possible.

I end my session with custom command to save all new rules, lessons learned, release notes, and architectural updates. This way each subsequent session is built based on the gain knowledge from the previous. I have a history, I have an AI that gets smarter with each subsequent session.

Be as descriptive as possible

When you prompt, be as descriptive as possible. I use BMAD to create descriptive technical specifications out of my prompts. If the idea is not clear in my head, I do brainstorming session. All possible to minimize the guessing and wrong interpretations during the development phases.

Automate boring things

My app has to survive Android OEM battery killers (Samsung, Xiaomi, Honor, OPPO. They all try to kill applications that run 24/7 to save battery. And this is a never ending fight. New version add new restrictions. Undocumented behavior gets detected by fellow developers and shared in forums. If I need to monitor manually for anything that can break my app, this will be the only thing that I will have time to do... And this is boring!

That's why I created custom slash commands that do the analysis and the search for me. I run them weekly. Everything found I turn in to technical specifications (via BMAD ) and afterwards, to real implementation

Do you all do code reviews?

I do. Usually, with a different LLM. This gives a 'different point of view'. Improves greatly the code quality!

What this is not

  • It is a no-code, but If you know the language, it is worth checking and correcting if needed. With time, the needed small fixes will become less. It is always good to understand the architecture and to make the design decisions yourself.
  • It is not effortless. The workflow took months to build. The documentation is extensive.
  • It is not magic. The AI makes mistakes. The difference is that mistakes are caught by the process (tests, reviews, rules, audits) instead of by users;

The takeaway

AI coding tools are not magic code generators. They are force multipliers for engineering process. If your process is "open chat, type prompt, hope for the best," you will be disappointed.

If your process is "document the architecture, define the rules, automate the lifecycle, capture every lesson, review everything critically", the AI becomes unreasonably effective.

The investment is not in better prompts. It is in better engineering.

The app is available in Google Play and I already have a few people using it. Quite happy with the results. And I will continue to extend it with additional functionalities, following the same approach I described above.

reddit.com
u/Character_Oven_1511 — 3 days ago
▲ 9 r/dementiaresearch+3 crossposts

Is he OK?! Senior safety monitoring app

My grandmother felt on the ground one day, there was nobody around her.

My wife's grandfather had his health issues and all were concerned about him.

And now my parents. What if something happens to them? How will I know? How can I help, when I am not around, with two kids. What can I do about it?

And I know my father. He will not carry a smart watch, he will not call me, he will not like being watched by a camera.

What if he can't pick up the phone and call me?

And I don't see such software on the market, that you install on his device, and it stays passively there, not bothering him for anything. Just monitoring there, quietly.

I tried to make this application up and running. With modern technologies, this is possible. The application monitors and updated regularly the patterns of sleep, activeness, and rests. Searches frequently visited places, and if detects something might be wrong, it sends me notification.

For example:

* My father is usually has a daily walk between 7AM and 11AM. If he does not do it, he might have fall, and I will receive a notification;

* He usually have a rest between 14:00 PM and 16:00PM. If the rest goes too long, I will be notified;

* If he is still on an unknown place for a long time, I will get notified.

There are still cases that I follow and resolve, but this is how it is, with software. It requires dedication and support. And I really want to support this and make the product better and better!

It is free the first 21 days, so that people can see if this works for them. Afterwards, I wanted to keep it cheap: $49 annual subscription for the first year, and $5 subscription for each next one. I have some expenses that I need to cover.

I don't do it about the money, I just want to help people.

Why I write this post:

I am writing this post with a request for help. I am in the 'chicken vs egg' scenario. I am desperately searching for people who will trust me. To install it, to try it out, to give me honest and direct feedback. For people to trust me, I need to show that it works.

How can I show that this app works?

If I was producing cups, once someone sees it, I can get instant feedback.

But for my application, this needs time. Something really serious has to happen to a senior person, so that I can show that it can safe lives. But, the good news is that such things does not happen daily. And to wait for something bad to happen to someone, so that I can get back a feedback is just not right.

Please, install it, try to break it, test it, suggest anything. I am opened for discussions.

Let's help well deserved elderly people together, and do good!

Thank you!

https://play.google.com/store/apps/details?id=com.howareyou

u/Character_Oven_1511 — 5 days ago

I delayed the launch on purposes. I wanted to be sure that I covered more edge cases.

https://www.producthunt.com/products/how-are-you-senior-safety?launch=how-are-you-senior-safety-2

I did the application, trying to help my parents, and other elderly people who live alone. My goal is to make it publicly visible, so that more people can benefit from it.

I will highly appreciate any feedback, support and share, so that I can detect more cases and do a good and stable product

Will return a honest feedback for your product as well

Thank you all!

u/Character_Oven_1511 — 16 days ago

I ship a continuous-background Android app and have spent forty-odd releases building up an eleven-layer service-recovery chain — every layer added because the previous ones were not enough on some specific OEM. Eleven layers feels excessive until you ship to a Xiaomi user, after which it feels like a starting point.

A diagnostic export from a fresh-install Honor MagicOS 9.0 (Android 15) device showed a failure mode I had not seen before. Healthy hour-one snapshot - battery exemption granted, foreground service running, IMPORTANCE_MIN notification visible. Six hours later the runtime health check fired the family-recovery email: battery optimisation was now blocking the app, with no user action in between. The phone had been on a counter the entire time. The app should have been bored.

What follows is the timeline, the four signals MagicOS graded me down on, and the four minimal surfaces that fixed it. I have only tested this on Honor - I suspect the same pattern hits Xiaomi MIUI Power Keeper and Samsung Smart Manager, and would like to hear from anyone who has seen it.

The timeline

The diagnostic export, when I opened it, was a small horror movie.

At approximately 17:24:30 UTC, MagicOS killed the foreground service. Honor does not emit a reason - the process simply dies. (Honor PowerGenie is a genie in roughly the way a parking enforcement officer is a genie. It does not grant wishes. It revokes battery exemptions.)

In the same wall-clock second, three independent layers of my recovery chain fired:

17:24:31  [Application] Recovery: process started, service not running - starting service
17:24:31  [Application] Network recovery: service not running - starting service
17:24:31  [MonitoringService] onStartCommand startId=2
17:24:31  [MonitoringService] onStartCommand startId=3

Application.onCreate, a network-availability callback, and a SyncAdapter trigger all called startForegroundService in the same instant. The framework happily delivered all three to onStartCommand. My init body ran three times, each invocation re-firing startForeground, re-scheduling the watchdog, and re-launching the monitoring coroutines. I was, from the OS's point of view, aggressively visible. The kind of visible that gets you a follow-up call from your battery manager.

By 19:24 UTC - exactly two hours after the kill — the watchdog reported a missing JobScheduler registration and the next health check confirmed the battery exemption was gone. From "this app got killed once" to "this app's exemption is hereby revoked" in under two hours. PowerGenie has opinions and acts on them quickly.

Why MagicOS read this as suspicious

PowerGenie runs a heuristic that explicitly looks for the pattern my recovery chain emits: a process that gets killed and immediately tries to come back via multiple unrelated paths in a tight window. To the OEM analyser, that pattern is shaped either like malware (rootkit-style autostart, ad-stuck zombies, miners that will not quit) or like a "stubborn" application that refuses to take the hint. Either way, demerits. Each concurrent recovery path that lights up the service in the same second is one tick on a process-resurrection counter. Hit the threshold and the OEM revokes trust.

Defence-in-depth and process-resurrection look identical from outside. The OEM cannot distinguish "this app legitimately needs eleven layers because I built it for grandma's phone" from "this is a rogue cryptocurrency miner that will not die." The eleven layers all stay - each is genuinely necessary on a different device family. The mistake was that on a process restart they all fired on the same trigger in the same second, the equivalent of eleven people stepping forward to answer the door at once and somehow this making the door less likely to open.

Four signals the OEM observed

Signal 1 - multi-path concurrent recovery. Three onStartCommand calls in the same second, two of them firing startForeground redundantly. Three increments on the resurrection counter instead of one.

Signal 2 - JobScheduler frequency. My periodic watchdog ran every fifteen minutes - about ninety-six runs per day, which AOSP BatteryStatsService telemetry puts in the top one percent of UIDs by job count. Pixel App Standby Bucket demotion and Samsung Sleeping Apps read this directly. I picked fifteen minutes because fifteen is the floor for PeriodicWorkRequest. I picked the floor for the same reason people pick the airplane meal — it was what was on offer.

Signal 3 - time-based location request with no explicit distance hint. My GPS wake probe — a five-second requestLocationUpdates call that fires at most once every four hours — built its LocationRequest without setMinUpdateDistanceMeters(0f). The default is implementation-defined, undocumented, and on some Play Services versions non-zero. OEM static analysers flag time-only location requests harder than equivalent ones that explicitly declare their intent. Two other call sites in my own code already had the line. The wake probe did not, because I had written it earlier and never came back. The bug was in the past, watching me, waiting.

Signal 4 - motion-triggered GPS at micro-intervals. Scrolling earlier in the same export, before the kill, I found a different kind of OEM-readable noise - GPS captures every few minutes through the previous night.

04:40:08  ACTION_SCREEN_ON, sleep callback fired, GPS requested
04:40:32  ACTION_SCREEN_ON, sleep callback fired, GPS requested
(six screen-on events inside ninety seconds, each one a fix)
05:17:51  SleepMonitoringCallback.onEnter, stillness, GPS requested
05:18:14  SleepMonitoringCallback.onExit, movement, GPS requested
(twenty-three seconds between two captures, because the phone shifted on the bed)

The app uses SleepMonitoringCallback.onEnter and onExit - the activity-recognition still-or-moving transition — as a hint that the user just got up or settled down, and each transition was kicking off a fresh GPS fix. Useful in principle. Costly in aggregate. The screen flickers a couple of times overnight, the phone shifts an inch on the bed, the recogniser flips, the callback fires, GPS goes out. On a quiet night the UID was averaging well above four fixes per hour, sustained, for the whole first week - exactly the window where Honor's classifier is forming a first-impression. This was not the kill at 17:24. It was the climate from 04:00.

Each signal in isolation is small. The four together added up to "this UID is suspicious" and PowerGenie cleared its trust.

Fix 1 - anti-thrash gate at the convergence point

Five seconds, monotonic clock, one volatile field on the Service.

private companion object {
    const val CONCURRENT_START_WINDOW_MS = 5_000L
}

@Volatile private var lastStartCommandHandledMs: Long = 0L

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    super.onStartCommand(intent, flags, startId)

    val now = SystemClock.elapsedRealtime()
    val sinceLast = now - lastStartCommandHandledMs
    if (lastStartCommandHandledMs > 0L && sinceLast < CONCURRENT_START_WINDOW_MS) {
        return START_STICKY
    }
    lastStartCommandHandledMs = now

    // existing init body unchanged from here
}

SystemClock.elapsedRealtime, not System.currentTimeMillis, because wall clock can jump backwards (NTP sync, DST, manual time change). The gate sits at the convergence point, not at every caller — I tried gating each trigger and within an hour every gate I added created the next OEM-specific gap, because each layer is the only thing that works on some specific device. onStartCommand is the chokepoint where every layer's effort eventually arrives.

The framework is fine with this — AOSP ActivityManagerService.ServiceRecord tracks one deadline per service, and a single successful startForeground call clears every pending deadline regardless of how many startForegroundService calls are stacked behind it. The duplicates were doing redundant work — work PowerGenie watched and the OS would have been just as happy without. Doing the right thing twice does not, on Android, count as doing the right thing.

Fix 2 - declarative intent on the GPS wake probe

One line.

LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000L)
    .setDurationMillis(GPS_WAKE_PROBE_DURATION_MS)
    .setMaxUpdates(5)
    .setMinUpdateDistanceMeters(0f)   // declarative intent for OEM analysers
    .build()

Behaviour identical. 0f says "deliver every fix regardless of distance," which is what a five-second wake probe wants anyway. The change hands the OEM static analyser something explicit to read instead of letting it infer "time-based, no distance hint, suspicious." When a LocationRequest, JobInfo, or WorkRequest has an explicit field and you leave it unset, you are letting an OEM-specific default fill in for you — undocumented, varies between platform versions, biases against you.

 

Fix 3 - watchdog cadence: 15 minutes to 30 minutes

One constant. The watchdog is the backup, not the primary FGS-survival mechanism — that is the AlarmManager 5-minute chain plus the 8-hour setAlarmClock safety net. Halving the cadence (96 runs per day to 48) cuts the per-UID job count in half. On Android 12+ the periodic worker is Doze-deferred anyway, so "fifteen is more responsive than thirty" does not hold during the exact moments — deep sleep, OEM kill cycle — when revocation actually happens. The watchdog was, in effect, anxiously checking on the dog every fifteen minutes while the dog was already inside.

I also flipped ExistingPeriodicWorkPolicy.KEEP to UPDATE for this release only, so existing installs migrate to the new cadence on first app open.

 

Fix 4 - gate the motion callback through a shouldFireMotionGps decision

Three small policies, one pure function, one internal sealed class.

internal sealed class MotionGpsGateDecision {
    data object Fire : MotionGpsGateDecision()
    data object SkipScreenOn : MotionGpsGateDecision()
    data object SkipDedup : MotionGpsGateDecision()
}

internal fun evaluateMotionGpsGate(
    nowMs: Long,
    lastMotionGpsMs: Long,
    isScreenInteractive: Boolean,
    isLearningPhaseActive: Boolean,
): MotionGpsGateDecision {
    if (isScreenInteractive) return MotionGpsGateDecision.SkipScreenOn
    val window = if (isLearningPhaseActive) {
        LEARNING_MOTION_GPS_MIN_INTERVAL_MS  // 5 min
    } else {
        MOTION_GPS_DEDUP_WINDOW_MS           // 60 s
    }
    return if (nowMs - lastMotionGpsMs < window) {
        MotionGpsGateDecision.SkipDedup
    } else {
        MotionGpsGateDecision.Fire
    }
}

Three rules. Screen-on short-circuit: if the screen is interactive, skip the motion-driven GPS — the user is awake and the foreground UI will request its own location. The screen-on bursts (six in ninety seconds in this export) were the densest GPS thrash in the file. 60-second dedup outside learning collapses the onEnter/onExit flip-flop class into one capture per minute — the family does not benefit from two fixes when the only thing that changed was the bedsheet. Five-minute window during the learning phase tightens the dedup specifically through the first-week classifier window, then relaxes back. The pure function is unit-testable without standing up Hilt and Robolectric; instance state is a @Volatile field on the Service updated by the existing learningPhaseRepository collector. Two const vals, two volatile fields, no new flows, no new permissions.

Four fixes. Four small surfaces. Total diff was under a hundred lines including KDoc.

What generalises

Some recovery patterns look exactly like malware to a battery manager. Defence-in-depth and process-resurrection look identical from outside the process - the fix is not to remove layers, it is to keep them and converge their effect.

The convergence point is the right place for deduplication. Whenever you have multiple correct triggers for the same effect, dedupe where the effect happens, not where the triggers fire. Two concrete chokepoints in this story: onStartCommand for service restarts (Fix 1) and a shouldFireMotionGps gate ahead of every motion-driven location request (Fix 4).

Sustained-rate signals are read separately from event-burst signals. The 17:24 kill was the dramatic moment but PowerGenie was also grading the per-hour rate of the previous night's location requests. A FGS-survival fix that only addresses kill-and-recovery dynamics leaves the calm-day rate untouched, and the calm-day rate is what shapes the OEM's first-week opinion. For every callback that requests a system resource, ask both "what triggers it" and "how often does it fire on a quiet day, in the first week."

The floor is not a free choice. Every "minimum allowed" comment in your codebase is a candidate for the question: did anyone actually pick this number, or did they just take what was on the bottom shelf. (My code had three of those comments. None of them had been chosen, exactly. They had been accepted.)

Declarative intent is cheap and load-bearing. The OEM analyser reads what is set, not what is intended.

The uncomfortable part

Good engineering can be the wrong choice on Android. The eleven recovery layers are individually correct and load-bearing on some device. Together they are the trigger for "this is a zombie, lower its trust." There is no platform contract that says "thou shalt not recover thy service via more than two paths at once," and there cannot be, because the OEMs do not write contracts. They write heuristics. The heuristic this week is "concurrent recovery is suspicious." Next week it will be something else, and we will all find out about it from a single user's diagnostic export at 9pm on a Sunday.

So the eleven layers stay. They just learned to whisper.

Question for the sub

I have only seen this on a single Honor MagicOS 9 / Android 15 export. The reasoning generalises in theory but I have not personally caught the exemption-revocation failure on any other OEM yet.

If you ship continuous-background work, three questions I would like answers to:

  1. Have you observed a battery exemption being revoked some hours after a single foreground-service kill, on any OEM other than Honor? What did the diagnostic show in the gap between the kill and the revocation?
  2. If you have a multi-layer recovery chain, where do you do deduplication - at the trigger sites, at the convergence point in onStartCommand, or somewhere else?
  3. Has anyone correlated exemption revocation with a sustained per-hour rate of motion-callback or activity-recognition-driven location requests, rather than (or in addition to) a discrete kill-and-recovery event?

Happy to share more from the export if anyone wants to dig into a specific layer.

reddit.com
u/Character_Oven_1511 — 21 days ago