Inspiration
Most affirmation apps give you a collection of positive quotes and expect you to find one that fits.
I wanted to try something different.
When you're anxious, burnt out, frustrated, or having a good day, you probably don't need the same affirmation. That became the idea behind Affirmi: affirmations should meet you where you are.
I wanted to combine that idea with the things I've been learning about AI, Kotlin Multiplatform, recommendation systems, and mobile development to build something that feels personal rather than like a collection of static quotes.
What it does
Affirmi creates personalized affirmations based on how you're feeling.
You can log your mood and emotional state, and Affirmi uses that information to recommend affirmations that are more relevant to your current situation.
The app also includes AI-generated affirmations, voice-guided affirmations, mood tracking, personalized recommendations, and premium features.
The goal isn't to tell everyone the same thing.
It's to give you something that makes sense for this moment.
How we built it
Affirmi is built with Kotlin Multiplatform, allowing us to share core business logic across Android, iOS, Desktop, and Web while still keeping platform-specific functionality where it makes sense.
The mobile applications use Kotlin and Compose Multiplatform, with Koin for dependency injection and StateFlow/MVVM for state management.
The backend is built with Kotlin and Ktor, with Firebase/Firestore handling parts of the application's data layer.
AI-generated content is handled through a backend-controlled AI integration rather than exposing AI credentials directly to clients.
We also built the subscription system around RevenueCat. Android and iOS use RevenueCat for purchases, while the backend maintains its own entitlement state through verified RevenueCat webhooks.
That last part became particularly important because I didn't want a client-side isPremium = true to be enough to access something that costs the backend money to run.
Challenges we ran into
The biggest challenge wasn't getting any individual feature working. It was making all the pieces work together without creating a mess.
Kotlin Multiplatform was one example. RevenueCat works well for Android and iOS, but Desktop and Web don't have the same purchasing capabilities. That meant resisting the temptation to force every platform through the same implementation.
Subscriptions created another interesting problem.
At first, checking RevenueCat's CustomerInfo on the client seemed like enough. Then I started thinking about stale subscription state, modified clients, webhook retries, duplicate events, and events arriving out of order.
That led to a much more robust architecture where the client uses subscription information for UI, while the backend owns the actual authorization decision.
We also had to think carefully about AI costs, caching, offline behavior, recommendation quality, and keeping sensitive operations behind the backend.
Accomplishments that we're proud of
I'm particularly proud that Affirmi has grown beyond being just another AI wrapper.
The project has a proper multiplatform architecture, a backend that controls sensitive operations, offline-first considerations, personalized recommendation logic, AI-generated content, voice features, widgets, notifications, and a production-oriented subscription system.
I'm also proud of the decision to build the architecture around boundaries rather than shortcuts.
For example, platform SDKs are wrapped instead of leaking into shared business logic, and subscription state is treated as an authorization concern rather than simply a UI boolean.
A lot of the work has been about making sure the app can grow without having to rewrite everything later.
What we learned
The biggest lesson has been that building a product is very different from getting a feature to work.
It's easy to make something that works on your phone.
It's much harder to make something that behaves correctly when the network disappears, a subscription changes, a webhook is retried, an event arrives late, an API call costs money, or a user opens the application on a completely different platform.
We also learned that Kotlin Multiplatform doesn't mean pretending every platform is identical.
The best shared code is the code that actually benefits from being shared.
And perhaps the most important lesson from the subscription work was simple:
Never let the client be the final authority for something you need to secure.
What's next for Affirmi
The next phase is about taking Affirmi from a technically capable project into a product people genuinely want to keep using.
That means improving the personalization and recommendation system, learning from user feedback, expanding the voice experience, improving the cross-platform experience, and continuing to refine the AI-generated content.
I'm also interested in exploring how Affirmi can better understand changes in someone's emotional patterns over time without turning the experience into a clinical or overly analytical dashboard.
The long-term goal is still the same as when I started:
Build an affirmation experience that feels like it understands the moment you're in, rather than handing you another random quote.
Built With
- android
- compose-multiplatform
- firebase
- firestore
- generative-ai
- ios
- koin
- kotlin
- kotlin-multiplatform
- ktor
- machine-learning
- rest-api
- revenuecat
- stateflow
Log in or sign up for Devpost to join the conversation.