![]()
I built a reminders app with Jetpack Compose and Material 3
In the ever-evolving landscape of Android development, creating a functional, aesthetically pleasing, and efficient application requires a deep understanding of modern tools and architectures. We embarked on a journey to build Reminder Mate 2.0, a completely free, ad-free Android application designed to manage reminders effectively. This project was not just about creating a utility tool; it was a deliberate effort to master Kotlin, leverage the power of Jetpack Compose, and implement the latest Material Design 3 guidelines. By focusing on a simple, offline-first approach, we aimed to address the common pain points of bloated reminder apps while providing a robust learning platform for ourselves and other developers. This article details the technical decisions, architectural patterns, and implementation strategies we employed to bring this application to life, offering a comprehensive guide for those looking to build similar modern Android applications.
The Architectural Foundation: Why We Chose Modern Android Development
Before writing a single line of code, we established a solid architectural foundation. The goal was to build an app that is scalable, maintainable, and testable. Traditional Android development often led to tight coupling between the UI and business logic, making the codebase difficult to manage as it grew. To avoid this, we adopted the Model-View-ViewModel (MVVM) architecture, a pattern officially recommended by Google. This separation of concerns allows the UI to observe data changes without directly managing them, leading to a more reactive and bug-resistant application.
Embracing Kotlin as the Primary Language
Our commitment to modern Android development began with the choice of Kotlin. As a statically typed language developed by JetBrains, Kotlin offers a concise syntax, null safety, and full interoperability with Java. For Reminder Mate 2.0, we utilized Kotlin’s coroutines for asynchronous programming. This was crucial for handling database operations and background tasks without blocking the main thread, ensuring a smooth user experience. By leveraging Kotlin’s expressive features like extension functions and higher-order functions, we were able to write clean, readable code that reduced boilerplate significantly.
The Role of Jetpack Compose in UI Development
One of the most significant shifts in our development process was the adoption of Jetpack Compose. As Android’s modern toolkit for building native UI, Compose allowed us to describe the UI declaratively. Instead of imperatively manipulating UI elements (like in the old View system), we defined what the UI should look like for a given state. This paradigm shift simplified the codebase and made it easier to debug and update the UI.
For Reminder Mate 2.0, Jetpack Compose was instrumental in creating dynamic and interactive screens. The state management in Compose is seamless; we could hoist state to the ViewModel and let the UI reactively recompose when the state changed. This eliminated the need for complex listeners and callbacks, reducing the potential for memory leaks and lifecycle-related bugs. Furthermore, Compose’s integration with Kotlin Coroutines and Flow allowed us to handle asynchronous data streams elegantly, making the app responsive even when dealing with large datasets of reminders.
Implementing Material Design 3 for a Modern Aesthetic
To ensure the app felt native and modern, we strictly adhered to Material Design 3 (Material You). This design system provided a cohesive set of principles for typography, color, and component behavior. We utilized dynamic color theming, which allowed the app’s color palette to adapt based on the user’s wallpaper, creating a personalized and immersive experience.
Implementing Material 3 components directly in Jetpack Compose was straightforward. We used standard components like Card, FloatingActionButton, NavigationRail, and ModalBottomSheet to structure the layout. The theming system in Compose allowed us to define a central Theme.kt file that standardized our color schemes, typography, and shapes. This ensured consistency across all screens of Reminder Mate 2.0, from the main list view to the detailed reminder creation dialogs. By focusing on accessibility and motion, we ensured that the app was not only beautiful but also usable for a diverse range of users.
Core Feature Implementation: Building the Reminder Management System
The primary functionality of Reminder Mate 2.0 revolves around creating, viewing, updating, and deleting reminders. We designed this feature set to be intuitive and efficient, prioritizing offline capability and performance.
Data Persistence with Room Database
Since the app is offline-first, local data storage was a critical component. We chose the Room Persistence Library, part of the Android Jetpack suite, to handle database operations. Room provides an abstraction layer over SQLite, allowing for robust database interactions while leveraging the full power of SQLite.
We defined a Reminder entity with fields such as id, title, description, timestamp, and isCompleted. The Room DAO (Data Access Object) interface contained methods for all CRUD (Create, Read, Update, Delete) operations. We used Kotlin Coroutines with Room to perform these operations asynchronously, ensuring the UI remained responsive. For instance, when the user added a new reminder, the database insertion happened in the background, and the UI was updated immediately upon success using a StateFlow emitted from the ViewModel.
State Management Using ViewModel and Flows
Effective state management is the backbone of a reactive application. In Reminder Mate 2.0, the ReminderViewModel acts as the brain of the application. It holds the state of the UI and processes user inputs.
We utilized MutableStateFlow to represent the current state of the reminder list, search queries, and filter options. The ViewModel collects data from the Room database using Flow, which emits updates whenever the underlying data changes. This reactive approach means that when a reminder is added or modified, the UI automatically reflects the change without requiring a manual refresh.
For example, the reminderList in the ViewModel is defined as:
val reminderList: StateFlow<List<Reminder>> = reminderDao.getAllReminders()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
This ensures that the UI always has access to the latest data, even if the app goes into the background and comes back to the foreground.
User Interface Design with Jetpack Compose Components
The UI of Reminder Mate 2.0 is built entirely with Jetpack Compose components. The main screen features a LazyColumn to display the list of reminders. This Composable is highly optimized for rendering long lists and only renders items currently visible on the screen, which is crucial for performance.
Each reminder item in the list is wrapped in a Card component from Material 3, providing a clean and elevated look. We implemented swipe-to-delete functionality using the Modifier.swipeable API, allowing users to remove reminders intuitively.
For adding or editing reminders, we designed a full-screen dialog or a bottom sheet depending on the context. These forms utilize TextField components for input, OutlinedButton for actions, and DatePicker and TimePicker dialogs for scheduling. We ensured that all input fields are validated before submission to prevent empty reminders from being saved.
Advanced Features and Optimizations
While the core functionality focuses on simplicity, we integrated several advanced features to enhance the user experience and app performance.
Notification System Integration
A reminder app is incomplete without a reliable notification system. We integrated Android WorkManager to handle background tasks for triggering notifications. WorkManager is the recommended solution for deferrable, guaranteed background work that needs to run even if the app is closed or the device restarts.
When a user sets a time for a reminder, we schedule a OneTimeWorkRequest using WorkManager. This request includes the reminder’s data (title, description, and ID). When the work is triggered, a NotificationCompat.Builder creates a system notification. We utilized Notification Channels (required for Android 8.0+) to categorize reminders, allowing users to manage notification preferences at a granular level within the system settings.
Search and Filtering Capabilities
As the number of reminders grows, finding specific entries becomes essential. We implemented a search bar at the top of the main screen using a TextField with a trailing icon for clearing the query.
In the ViewModel, we process the search query using a combine operator that merges the reminderList Flow and the searchQuery Flow. This creates a filtered list that updates in real-time as the user types. The filtering logic checks if the query string is contained within the reminder’s title or description, providing immediate feedback. Additionally, we added filtering options for “Pending” and “Completed” reminders, toggled via FilterChip components from Material 3.
Performance Optimization Strategies
To ensure Reminder Mate 2.0 runs smoothly on a wide range of devices, we implemented several optimization strategies:
- Lazy Loading: We used
LazyColumnandLazyRowfor all lists to minimize the composition overhead. - Deriving State: We avoided unnecessary recompositions by deriving state from other state sources rather than duplicating it. For example, the visibility of the empty state (shown when no reminders exist) is derived directly from the
reminderListsize. - Modularization: Although the app is relatively small, we structured the codebase into modules (data, domain, ui). This separation allows for better code organization and faster build times as the project scales.
- ProGuard/R8 Rules: We enabled code shrinking and obfuscation to reduce the APK size, which is crucial for user downloads and storage efficiency.
Development Challenges and Solutions
Building an app from scratch inevitably involves overcoming technical hurdles. We encountered several challenges during the development of Reminder Mate 2.0, and our solutions provide valuable insights for other developers.
Handling Date and Time Complexity
Managing dates and times in Android can be notoriously tricky due to time zones and formatting standards. We adopted the ThreeTenABP library (a backport of Java 8’s java.time API) to handle date logic reliably. This library provides classes like LocalDateTime and ZonedDateTime, which are much easier to work with than the legacy Date and Calendar classes.
When scheduling notifications, we calculated the time difference between the current time and the reminder time, converting it to a timestamp that WorkManager could understand. We also ensured that the UI displays dates in a localized format using java.time.format.DateTimeFormatter, catering to users in different regions.
Ensuring Offline Capabilities
The requirement for an offline app meant we had to handle network states gracefully, although the app itself does not rely on a network. The main challenge was ensuring that the database is the single source of truth. We designed the Repository pattern to fetch data exclusively from the local Room database. This ensures that the UI is always consistent with the stored data, regardless of network availability (or lack thereof). By strictly adhering to a local-first architecture, we eliminated the latency and error handling associated with network calls, making the app fast and reliable.
Testing Strategy
Quality assurance is paramount. We adopted a layered testing strategy:
- Unit Tests: We wrote unit tests for the ViewModel and Repository using JUnit and Mockito. This verified that business logic, such as filtering reminders or validating input, worked correctly.
- Instrumentation Tests: For UI testing, we utilized Espresso (and explored Compose UI Testing). These tests simulated user interactions like clicking buttons, typing text, and swiping items, ensuring that the UI components responded as expected.
- Database Testing: We used an in-memory database for testing Room entities and DAOs to ensure data integrity without affecting the production database.
Publishing to the Google Play Store
Transforming a local project into a published app on the Google Play Store involves several steps. We prepared the app listing by designing high-quality screenshots, a feature graphic, and a compelling description. We emphasized the app’s key selling points: free, ad-free, and offline.
We generated a signed APK (now AAB - Android App Bundle) and created a testing track (alpha/beta) to gather feedback from a small group of users before the full release. This iterative feedback loop allowed us to fix minor bugs and polish the UI. The final step involved configuring the store listing details, content rating questionnaire, and pricing (free). The review process took a few days, and once approved, Reminder Mate 2.0 was available globally on the Play Store.
Future Roadmap and Potential Enhancements
While Reminder Mate 2.0 serves its core purpose effectively, we have identified several areas for future enhancements to elevate the user experience further.
Integration with KMP (Kotlin Multiplatform)
Looking ahead, we plan to migrate the business logic to Kotlin Multiplatform. This would allow us to share the database and ViewModel logic across Android and iOS. By writing the core code in common Kotlin, we can reduce duplication and streamline the development of a future iOS version of the app.
Adding Widgets and Tiles
To increase the app’s visibility and utility, we intend to develop Home Screen Widgets. Using Glance (Jetpack library for widgets), we can display upcoming reminders directly on the user’s home screen. Additionally, for devices running Android 7.0+, we can implement App Shortcuts and Quick Settings Tiles to allow users to add reminders instantly without opening the app.
Advanced Customization and Themes
We aim to introduce a more advanced theming engine. While Material 3 dynamic colors are great, some users prefer specific color palettes. Adding a theme selector with predefined dark and light themes (e.g., Amoled Black, Pastel) would provide greater customization. We also plan to allow users to customize notification sounds and vibration patterns for individual reminders.
Technical Stack Summary
For developers interested in replicating or contributing to this project, here is the complete technical stack used for Reminder Mate 2.0:
- Language: Kotlin
- UI Toolkit: Jetpack Compose
- Design System: Material Design 3
- Architecture: MVVM (Model-View-ViewModel)
- Database: Room Persistence Library
- Asynchronous Programming: Kotlin Coroutines (Flow, StateFlow)
- Dependency Injection: Hilt (optional but recommended for scalability)
- Background Tasks: Android WorkManager
- Date/Time Handling: ThreeTenABP
- Testing: JUnit, Mockito, Compose UI Testing
- IDE: Android Studio Flamingo or higher
Conclusion
Building Reminder Mate 2.0 with Jetpack Compose and Material 3 has been a comprehensive exercise in modern Android development. The combination of declarative UI, reactive state management, and a robust architecture allowed us to create an application that is efficient, maintainable, and visually appealing. By prioritizing a simple, offline-first experience, we addressed a genuine need for a distraction-free reminder tool.
The journey from concept to Play Store publication reinforced the importance of choosing the right tools and architectural patterns. Jetpack Compose has fundamentally changed how we approach UI development, making it faster and less error-prone. Material Design 3 provided a design language that feels native and fresh. Through this project, we not only built a useful app for users but also honed our skills in Kotlin and the Android ecosystem. We believe that Reminder Mate 2.0 stands as a testament to the capabilities of modern Android development and serves as a solid foundation for future iterations and feature expansions.