ai newsDi., 27. Okt.
EilmeldungOpenAI: Eigenes KI-Modell soll Exponential Time Hypothesis widerlegt haben

I built an Android app for remembering the little things about people you love (no AI, no internet permission)

My brain drops things. Not the big stuff, but the small stuff that makes someone feel known: her coffee order, that she stopped to look at a blue scarf, that she doesn't want to always be the one who plans everything. So I built Little Things , an Android app for people with ADHD (or just busy brai

dev.toMontag 23:01 · 26.10.5 Min. Lesezeit
Bild: dev.to

My brain drops things. Not the big stuff, but the small stuff that makes someone feel known: her coffee order, that she stopped to look at a blue scarf, that she doesn't want to always be the one who plans everything.

So I built Little Things, an Android app for people with ADHD (or just busy brains) to capture those details the moment they come up and get them resurfaced later, when they're useful.

This post is about the few design decisions that mattered, and the code behind them.

If saving a note takes more than a tap or two, I won't do it in the moment, and then the note never exists. So the home screen has one big button, the keyboard opens immediately, and there's a "Save + another" option for brain dumps.

The part that makes it feel instant is that you never pick a category. You just type, and the app guesses.

Everyone's first assumption is that this uses a model. It doesn't. It looks for key words and phrases in what you type, and nothing leaves the phone.

The whole thing is one function on the Category enum:

fun guess ( text: String ): Category? { val t = " ${text.lowercase().replace('’', '\'')} " fun has ( vararg words: String ) = words. any { t. contains ( it ) } return when { has ( "doesn't like", "dislike", "hates", "allergic", "avoid" ) -> DISLIKES has ( "doesn't want to", "needs", "tired of", "overwhelmed", "mental load" ) -> NEEDS has ( "size", " xs ", " xl ", "inseam" ) -> SIZES has ( "gift", "present", "wishlist", "for christmas" ) -> GIFT_IDEAS has ( "favorite", "favourite", " fav " ) -> FAVORITES has ( "wants", "want to", "someday", "bucket list" ) -> WANTS has ( "coffee", " tea ", "latte", "pizza", "vegan", "snack" ) -> FOOD has ( "loves", "likes", "enjoys", "obsessed", "adores" ) -> LOVES else -> null } } Enter fullscreen mode Exit fullscreen mode (Trimmed here for the post; the real lists are longer.)

Order is the whole trick. "Doesn't like cilantro" contains "like", so if LOVES were checked first, you'd file a dislike under loves, which is the worst possible bug in an app about remembering what people care about. Negations go first: "doesn't like" beats "likes", and "doesn't want to" beats "want to". There's a unit test pinned to exactly that:

@Test fun dislikeBeatsLike () = assertEquals ( Category. DISLIKES, Category. guess ( "doesn't like cilantro" )) Enter fullscreen mode Exit fullscreen mode When nothing matches, guess returns null and the user picks manually. A wrong guess is annoying; a missing guess is just one extra tap. I'd rather be conservative.

The home screen and the home-screen widget both show "a little thing for today." They have to agree, and they have to change every day, without storing anything extra.

object TodaysPick { fun order ( notes: List < Note >, epochDay: Long ): List < Note > = notes. filter {! it. done }. sortedBy { it. id }. shuffled ( Random ( epochDay )) } Enter fullscreen mode Exit fullscreen mode Sort by id first so the input order is deterministic, then shuffle with a Random seeded by the epoch day. Same day, same order, on every surface. New day, new order. No database column, no sync between the app and the widget.

A daily WorkManager job handles date reminders and the occasional nudge. For nudges I didn't want pure random, because random will happily show you the same note three times in a week while others never come up.

So each note records when it was last surfaced, and the query returns the eight least-recently-surfaced notes. The worker picks randomly from those:

val note = repo. leastRecentlySurfaced ( 8 ). randomOrNull ()?: return false Enter fullscreen mode Exit fullscreen mode Everything eventually comes up, but it doesn't feel like a queue marching in order.

Some categories also get a follow-up question that nudges toward action instead of just recall. A NEEDS note adds "Anything small you can do for Sam today?" and a GIFT_IDEAS note adds "Maybe this is the week?"

One gotcha worth sharing: WorkManager doesn't fire at the exact same minute daily. My nudge check compares elapsed time against the frequency minus three hours of slack, otherwise a "daily" nudge sometimes skips a day because the job ran a few minutes early.

For important dates the notification does one more thing: it counts your saved gift ideas.

Tomorrow: Sam's birthday You have 3 saved gift ideas. Tap to see them.

Or, if you have none: "No gift ideas saved yet. Tap to see what they love." The reminder isn't just a date, it's a prompt to use what you've already captured. A small query ( countActiveGiftIdeas ) makes it much more useful than a bare calendar alert.

The date math had a few traps, so it's covered by tests: Feb 29 falling back to Feb 28 in non-leap years, past dates rolling to next year, and optional years so "anniversary, 5 years" works but a birthday with no year doesn't invent a count.

No cloud means no sync, so backup is a single.zip the user keeps wherever they like: a data.json plus a photos/ folder.

Kotlin, Jetpack Compose (Material 3), Room, WorkManager, Navigation Compose, and Glance for the widget. Min Android 8.0 (API 26).

Little Things is an Android app: https://play.google.com/store/apps/details?id=com.blakeriding.littlethings&referrer=utm_source%3Dwebsite%26utm_medium%3Dreferral%26utm_campaign%3Dsite_hero.

If you try it, I'd love to hear which phrases it guesses wrong.

The repo is private for now, but I'm happy to answer questions about any of the code above in the comments.

Quelle: dev.to (dev.to). Original öffnen