Skip to content
Rory Walker
← All work

MyGarden

A plant care app whose reminders read the weather

In beta · May 2026 – present · iOS · Android

MyGarden is a plant care app for iOS and Android. You add plants by searching the catalogue or photographing something you can't name, and each one gets its own watering, feeding and pruning schedule. Point the camera at a sickly plant and it'll tell you what's likely wrong with it. A journal keeps the photos so you can see how something's done over a season.

My role

Solo. Design, app, backend, release.

So far

15
Releases
195
Commits
33
Migrations
10
Edge Functions

The screens

The growing calendar for July, listing jobs grouped by task
The growing calendar. Every window is worked out from the user's own frost dates, so a mild coastal garden and a colder inland one see different months. It all inverts in the southern hemisphere.

How it’s built

  • ClientReact Native · Expo · TypeScript · Expo Router · NativeWind
  • BackendPostgres · Row-level security · Supabase Auth · Storage · Realtime · pg_cron
  • ServicesDeno Edge Functions · Claude Haiku · RevenueCat · Expo Push
MyGarden architectureThe React Native app talks to Supabase, which holds Postgres with row-level security, auth, photo storage and Realtime. Two pg_cron jobs run on separate ticks, the weather pass on the hour and reminders on the half hour. Deno Edge Functions call Claude, Open-Meteo and iNaturalist, and push notifications go out through Expo.iOS + Android appExpo · React Native · TypeScripttyped clientSUPABASEPostgresrow-level securityAuthemail · Apple · GoogleStorageplant photosRealtimestreams guide extraspg_cron · :00 weather → :30 reminders10 Edge Functions · DenoExpo PushnotificationsClaude Haikuidentify · diagnoseguides · searchOpen-Meteoforecast + archiveiNaturalisttaxonomy · photosToxicity warnings skip all of this. They come out of a published reference book, the HTA guide.The two cron jobs share state, so they run 30 minutes apart: weather first, then reminders.Everything above runs on free tiers.
Two TypeScript runtimes: strict-TS React Native on the device, Deno in the Edge Functions. They share no imports.

The hard parts

The catalogue grows itself

The problem
A plant app needs hundreds of thousands of species. Paying a horticultural API per request doesn't survive contact with a free tier, and generating care guides for every species up front means writing a lot that nobody opens.
What I did
The catalogue is seeded from open taxonomy data and fills in as people use it. A species enters Postgres the first time someone searches for it, with photos from iNaturalist and Wikipedia as a backup. Its care guide is generated once on first open and cached from then on, so the second person to look at that plant waits for nothing. The core guide comes back fast; the extras (flowering windows, deadheading, common problems) arrive a moment later over Realtime.
What it cost
The first person to open an obscure species waits a few seconds. After that it's instant for everyone. I run a script before each release that pre-generates guides for the popular plants, so most people never hit the wait at all.

Reminders that read the weather

The problem
v1 turned a catalogue field into a fixed interval: water every 2, 5 or 10 days. That interval is blind to conditions. It tells you to water during a downpour and leaves a plant dry through a heatwave. Reminders that are visibly wrong teach people to ignore the rest.
What I did
A daily job reads the local forecast from Open-Meteo and moves each outdoor plant's next watering: sooner in heat, later or skipped after rain. Rain only counts if it actually fell in the last 24 hours or is forecast within 48, so a skip never rests on a forecast alone. A hot day is worked out per location, from the 90th percentile of that garden's own daily highs over the past year. 22°C is a hot day in Scotland and it isn't in Spain.
What it cost
Every adjustment is capped at a day or two and has to be explainable in one line. An evapotranspiration model would schedule better, but it can't tell you why your reminder moved. Open-Meteo is free and needs no key, so the whole thing costs nothing per user to run.

Two cron jobs that must not overtake each other

The problem
The weather pass and the notification pass share state. If reminders go out before the weather has adjusted them, someone gets told to water their garden in the rain.
What I did
Both jobs run hourly and handle each user in their own notification hour, on deliberately different ticks: the weather pass on the hour, notifications half an hour behind it. A skipped watering gets flagged so the user hears about it once and is then left alone, and the flag clears when the next watering is scheduled.
What it cost
Nothing in the code enforces that ordering, so it's written down in the repo next to the migrations that set it. The first version ran the weather job once a day at a fixed UTC hour, which was simpler but meant a user in New Zealand got yesterday's forecast.

Knowing where not to put an LLM

The problem
Four features in the app use Claude: identifying a plant from a photo, diagnosing a sick one, writing care guides, and search. The obvious next step is having it write the toxicity warnings too, since the model plainly knows which plants are poisonous.
What I did
Toxicity warnings come from the HTA Guide to Potentially Harmful Plants. A plant carries a warning if that book lists it, and stays quiet if it doesn't.
What it cost
Coverage is narrower than a model would give, and some genuinely harmful plants carry no warning because the book doesn't list them. I'm fine with that. A model will tell you a plant is safe in exactly the same confident tone it uses when it's right, and under-warning is the mistake I can live with.

What’s next

  • Evapotranspiration-based watering, scheduling on how much water a plant is actually losing. The data model was built for it.
  • Per-plant microclimate tuning, so a sheltered corner and an exposed bed stop sharing a schedule.
  • Reading push receipts back from Expo to prune dead notification tokens.