Mutual Fund Explorer is an offline-first Android application designed to discover, search, and track mutual funds. It focuses on local caching, pagination, background synchronization, and a strict Single Source of Truth architecture
The application implements the Model-View-ViewModel (MVVM) architecture with Clean Architecture principles.
- Data Layer: Handles all data operations, including API requests via Retrofit and local caching via Room. Repositories act as mediators.
- UI Layer: Implemented entirely in Jetpack Compose. Screens observe state exclusively through StateFlow exposed by ViewModels.
- Single Source of Truth: The UI strictly observes the local Room database to render arrays and states. The network layer merely acts as a synchronization mechanism responsible for populating the cache rather than pushing to the view.
The application guarantees offline functionality by persisting all critical data locally.
Room is used as the underlying SQLite wrapper. Three separate DAOs manage the structural entities:
ExploreCacheDao: Caches categorized funds (Index, Bluechip, ELSS, Large Cap) required for the main dashboard.FundDetailCacheDao: Caches historical NAV data paths (1M, 3M, 6M, 1Y) mapped to specific scheme schemas.WatchlistDao: Stores user-created watchlists and implements relation mapping matching scheme codes into custom collections.
When a user navigates to a screen, data is emitted instantly from the Room cache, whilst a network routine is executed concurrently.
- If the network request resolves, the Room cache is overwritten, natively propagating the updated StateFlow tree to the UI.
- If the network fails, the user interacts seamlessly with the existing cached memory. A non-obtrusive "Stale Data" state logic is logged and handled quietly at the UI layer.
Handling thousands of mutual fund payloads is managed via Jetpack Paging 3 to restrict excessive memory buffer consumption and huge unpaginated network requests.
- Search Screen & View All: The API provides paginated constraints (
limitandoffset). MFFundsPagingSourceandSearchFundsPagingSourcemanage sequential network offsets natively.- The UI maps a single
Flow<PagingData<MutualFund>>integrating instantly into ComposeLazyColumns. As the user reaches pre-defined threshold bounds in the layout list, consecutive request offsets resolve lazily.
The platform evaluates metrics via deferred background task synchronization managed by WorkManager.
- WatchlistSyncWorker: Because NAV metrics fluctuate, funds actively added to a user's local watchlist are periodically re-synced against remote values.
- The underlying worker activates, queries the
WatchlistDaosequentially for all tracked scheme constraints, fetches the latest mapped NAV array from the API, and persists updates securely back to Room-executing independently of the application process.
Dagger Hilt resolves dependencies globally reducing boilerplate code allocation.
- Singleton Scope: Room database implementations, Retrofit configurations, and Repository objects are initiated safely and provided globally across execution graphs.
- ViewModel Components: Hilt natively constructs interface variables (
MFRepo,ThemeRepo) parsing them into designated ViewModels (e.g.,MFViewModel). - WorkManager resolution uses Hilt-Work extensions (
@HiltWorker), permitting DAOs and Repositories to inject cleanly into background synchronizer classes mapping strict modular bounds.
Data fetching boundaries are structured inside MFApiService.
- Retrofit & GSON: Raw JSON network paths convert symmetrically to domain-level Kotlin structures (e.g.,
FundDetail,MutualFund). - OkHttp Configurations: Includes raw Request/Response logging interceptor debugging parameters.
- Static Endpoints: Base environment URL configuration is strictly segmented securely to standard configuration rules via
local.properties:
BASE_URL="[API_ENDPOINT]"Which is then globally exposed into build.gradle.kts via a property helper and registered as a compilation constant:
buildConfigField("String", "BASE_URL", getBaseUrl())Standard SharedPreferences mapping mechanics are abandoned for native Jetpack DataStore processing parameters.
- Utilized exclusively internally inside
ThemeRepocomponents. - Actions execute asynchronously binding to Kotlin Coroutines to avoid main thread synchronous parsing blockades.
- Provides immediate Flow array streams forcing native configuration refreshes across Theme states (Dark, Light, System, Dynamic Monet config boundaries) implicitly.
To display historical NAV performance trends, dynamic data visualization was required on the Product Details screen.
Initially, the Vico charting library was integrated to handle the line graphs. However, upon evaluation, the visual output and strict styling constraints of Vico did not align with the premium, polished aesthetic targeted for this application.
Consequently, a custom declarative charting solution was implemented natively using Jetpack Compose Canvas. Building a bespoke Canvas solution provided absolute, granular control over path drawing, smooth gradient fills beneath the data lines, and fluid performance, avoiding the overhead of a heavy third-party graphing dependency while achieving a significantly better look and feel.
The package components enforce modular separation.
app/src/main/java/com/omsharma/mfexplorer/
├── data/
│ ├── local/ # Room Database generation, Schema configurations, and DAOs
│ ├── remote/ # Retrofit Interface bindings and DTO response entities
│ ├── repo/ # Aggregation bindings mapping Network constraints securely to Local Database schemas
│ ├── sync/ # Background routine tasks allocated via WorkManager execution arrays
│ └── model/ # Core data configurations and serializable payloads
├── di/ # Dagger Hilt Injectable Component arrays
├── navigation/ # Compose Route controllers orchestrating screen destinations
├── state/ # UI state variables mapping network results (Success/Error/Loading/Stale)
├── ui/
│ ├── screens/ # Concrete layout paths routing visual implementations natively mapped to ViewModels
│ ├── components/ # Granular reusable visual widget configurations
│ └── theme/ # Compose native Matrix/Dynamic palette implementations
└── viewmodel/ # Architecture states evaluating formatting functions formatting outputs explicitly to Compose States
- Android Studio
- Android SDK 34 minimum mapped inside Gradle rules.
- Initialize a clone instance of the repository root framework.
- In the
local.propertiesfile located at the structural root constraint, insert your Mock OR Live HTTP API string parameter:
BASE_URL="https://api.mfapi.in/"- Trigger Gradle Sync natively to fetch missing dependencies.
- Issue the standard Android Studio Run execution protocol attached to an emulator instance or physical debug unit.
To manually generate a standalone deployable Android Package (APK):
- Navigate directly to the embedded terminal view inside Android Studio (or host shell mapped to project root).
- Execute the Gradle build wrapper instruction natively:
./gradlew assembleDebug- Upon task completion, the executable package will compile directly at:
app/build/outputs/apk/debug/app-debug.apk