Yuvaraj Vodiboina
All work
(05)Case studyAndroid

Companion AI

One good quote a day, at the time you choose. Works offline, with no feed.

Platform
Android
When
Mar to Jul 2025
Role
Sole developer
Updated
19 Mar 2026
Price
Free
Status
Live on Google Play, 100+ downloads
(01)

How it works

4 steps
  1. 01

    Pick your time

    Choose when your quote arrives, daily or weekly.

    Open larger: A daily quote in your notifications
  2. 02

    Swipe through quotes

    Left for the next, right to go back, up to save, down to share.

    Open larger: Your companion, one swipe away
  3. 03

    Keep the good ones

    Build your own collection of favourites, and quotes of your own.

    Open larger: Create your own custom quotes
  4. 04

    Share it as an image

    Turn any quote into a card for friends or social media.

    Open larger: Themes that change your mindset
(02)

The problem

Most quote apps are loud: endless feeds and fifteen notifications a day. I wanted one good quote, once a day, at a time I pick, that works even with no connection.

(03)

What I built

An offline-first app that serves quotes from a Room database, so one is ready the moment you open it. WorkManager refreshes the cache in the background on Wi‑Fi, and a daily notification is rescheduled after every reboot.

Hard partA daily notification that arrives exactly once, at your time, through Doze, battery savers and reboots.

(04)

What it does

  1. 01

    30+ colour themes

    Cards with smooth, minimal animation, in light and dark.

  2. 02

    Offline first

    Quotes are stored on the phone, so there’s no waiting and no internet needed.

  3. 03

    One notification

    At the time you choose, scheduled to survive restarts.

  4. 04

    Your own quotes

    Write your own and organise them with tags.

  5. 05

    Gestures

    Every action is one swipe away.

  6. 06

    No account

    Open it and the day’s quote is there, with nothing to sign up for.

(05)

Under the hood

  1. 01REST APIA batch of quotes

    Quotes come in batches, so the app isn’t online for each one.

  2. 02RoomEvery quote served from the phone

    Every quote is served from Room on the phone, so it works offline, with no feed.

  3. 03WorkManagerTops up the cache, schedules the day’s quote

    Unique periodic work tops up the cache and schedules the day’s quote, its first run delayed to the time you chose.

  4. 04NotificationOnce, at the time you choose

    One notification a day, at your time, through Doze, battery savers and reboots.

  • Rebuilt on Hilt

    The March 2026 update moved the app onto Hilt dependency injection, for stability.

  • Cards that answer your finger

    Quote cards round their corners as you drag them, so every swipe feels physical.

  • Quotes as pictures

    Any quote can be shared as an image card, drawn on the phone.

(06)

In the code

Once a day, at your time

The daily quote is unique periodic work, its first run delayed to the next time you chose. Changing the time replaces it, so there’s never a second one.

utils/NotificationScheduler.kt, lines 50 to 81

NotificationScheduler.kt
private fun scheduleDailyNotifications(replaceExisting: Boolean) {
    val hour = preferencesHelper.getNotificationHour()
    val minute = preferencesHelper.getNotificationMinute()

    val currentTime = Calendar.getInstance()
    val scheduledTime = Calendar.getInstance().apply {
        set(Calendar.HOUR_OF_DAY, hour)
        set(Calendar.MINUTE, minute)
        set(Calendar.SECOND, 0)
        set(Calendar.MILLISECOND, 0)
    }

    // If the scheduled time has passed today, schedule for tomorrow
    if (scheduledTime.timeInMillis <= currentTime.timeInMillis) {
        scheduledTime.add(Calendar.DAY_OF_MONTH, 1)
    }

    val delay = scheduledTime.timeInMillis - currentTime.timeInMillis

    Log.d(TAG, "Scheduling daily notification for ${scheduledTime.time} (delay: ${delay}ms)")

    val dailyWorkRequest = PeriodicWorkRequestBuilder<DailyQuoteWorker>(1, TimeUnit.DAYS)
        .setInitialDelay(delay, TimeUnit.MILLISECONDS)
        .addTag(DAILY_WORK_NAME)
        .build()

    workManager.enqueueUniquePeriodicWork(
        DAILY_WORK_NAME,
        if (replaceExisting) ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE else ExistingPeriodicWorkPolicy.KEEP,
        dailyWorkRequest
    )
}
(07)

Decisions

  1. 01

    WorkManager instead of AlarmManager

    It handles Doze, battery optimisation and reboots on its own.

    Trade-offPeriodic work runs at most every 15 minutes, which is fine for a once-a-day app.

  2. 02

    Fetch in batches, always serve from Room

    The app should never show a spinner for a quote.

    Trade-offA first launch with no internet has nothing to show yet.

(08)

A bug I remember

The quote that came four times

What it did
After a reboot, the daily notification fired three or four times.
Why
The boot receiver queued a new periodic job on every boot without checking for the one already there, so each reboot stacked another.
The fix
Queue it with ExistingPeriodicWorkPolicy.KEEP: if the job is already there, it’s left alone.
(09)

What I tried first

  • AlarmManager

    The first version used it. On the emulator it was perfect; on a Samsung with aggressive battery optimisation, alarms fired hours late or not at all.

  • Fetching on every open

    Opened offline in the morning, it showed a spinner where the quote should be. Offline-first was obvious after that.

(10)

What I learned

  • Room migrations are the most common crash after an update. Test them before release.
  • Timing matters. A quote at 6:58 vs 7:02 decides whether someone sees it before or after their morning routine.
(11)

Timeline

  1. On Google Play

    One quote a day at the time you choose, favourites, your own quotes, and any quote shared as an image.

  2. Cards that bend

    Quote cards round their corners as you drag them, with new icons, a redesigned menu, and the app rebuilt on Hilt.

(12)

Built with

KotlinJetpack ComposeRoomWorkManagerHiltREST

Your dataYour quotes, favourites and settings stay on the phone, and there’s no account.

(13)

Every screen

6 from the store listing
  1. Open larger: Start your day inspired

    01Start your day inspired

  2. Open larger: Quotes that help you work towards your goals

    02Quotes that help you work towards your goals

  3. Open larger: A daily quote in your notifications

    03A daily quote in your notifications

  4. Open larger: Themes that change your mindset

    04Themes that change your mindset

  5. Open larger: Create your own custom quotes

    05Create your own custom quotes

  6. Open larger: Your companion, one swipe away

    06Your companion, one swipe away

Next06 of 16Chrome extension TabCluster
Groups every open tab by site in one click, shows you first, and saves workspaces.Read the case study