Personal Information
| Attribute | Details |
|---|---|
| Name | Akul Tyagi |
| akultyagi.cs@gmail.com | |
| GitHub | https://github.com/Akul-Tyagi |
| https://www.linkedin.com/in/akul-tyagi/ | |
| Portfolio Website | https://akul-tyagi.github.io/ |
| Location | Delhi, India |
| Timezone | IST (UTC +5:30) |
| University | Galgotias College of Engineering and Technology |
| Degree | B.Tech Computer Science and Engineering (2023–2027) |
| Mentors | Neeldoshii, Kaartic Sivaraam (Kaartic) |
| Availability | 46+ hours/week, full-time; extended hours available for critical milestones |
About Me
I am an Android engineer and the Founder and Lead Android Engineer of Naivety, an incubated startup selected for the GCET Accelerator. Through Naivety, I architected, built, and shipped an offline-first Android application currently serving over 800 active users across 50+ countries. Unlike theoretical tutorials or academic prototypes, this application was built entirely from the ground up on the exact modern technology stack prioritized for the Wikimedia Commons modernization: Jetpack Compose, MVVM, Dagger-Hilt, Kotlin Coroutines, Room, and Retrofit. Serving over 25,000+ sessions with a verified 99.9% crash-free rate (monitored via Firebase Analytics and Android Vitals), I have navigated the very real, production-level complexities of Compose recomposition performance, database connection pooling, and lifecycle-aware state management in unpredictable network environments.
Beyond mobile engineering, I hold the rank of Specialist on Codeforces (1540+ rating), placing me in the top 7% globally with 350+ DSA problems solved. In the realm of native engineering, I am one of the first developers globally to modernize the native C++ AndroidPdfViewer and PdfiumAndroid libraries, rebuilding 64-bit binaries to enforce 16KB page-size alignment - reducing my application's memory footprint by 30% and ensuring strict compliance with Android 15 kernel mandates and Google Play 2025 policies. My work in AI includes fine-tuning a Falcon3-7B-Instruct LLM using QLoRA and 4-bit quantization, achieving 35% lower inference latency on T4 GPUs.
My commitment to open-source engineering naturally led me to the Wikimedia Commons Android app. As a platform that democratizes free educational media globally, its mission resonates deeply with my belief in building software that empowers communities. My ongoing contributions have been deliberately focused on the upload workflow, offline error handling, and custom media selector - the exact domains prioritized for modularization in this proposal. Having spent a month studying the Commons architecture, untangling the deeply embedded Dagger 2 setup, and tracing the legacy RxJava streams through UploadMediaDetailFragment and ContributionController, I am fully prepared to execute a safe, incremental modernization that resolves outstanding technical debt without disrupting the daily workflows of thousands of volunteer contributors.
Past Experience and Contributions
My contributions to the Wikimedia Commons repository are not scattered across the codebase - they are deliberately concentrated in the media selection and upload pipelines, the precise domains targeted by this proposal. Rather than superficial string corrections or minor layout tweaks, every contribution has directly engaged with the tightly-coupled architectural limitations that modularization will resolve.
Merged Pull Requests
| PR | Title | Status | Architectural Impact |
|---|---|---|---|
| PR #6752 | Upload: resolve infinite connection error dialog loop during offline upload | Merged | Fixed a critical recursive self-call bug in UploadMediaDetailFragment where tapping OK on a connection error while offline trapped users in an infinite loop. Refactored the dialog logic to dismiss correctly, preserving the user's upload state without forcing a complete flow cancellation. |
| PR #6746 | UI: improve swipe to dismiss reliability on More bottom sheet | Merged | Replaced ScrollView with NestedScrollView to properly hand off nested scroll signals to BottomSheetBehavior, resolving flaky swipe-to-dismiss gestures. Approved and merged by Nicolas Raoul. |
| PR Review #6737 | Reviewed: Fixed FAB layout and distortion issues on NearbyFragment | Merged | Provided rigorous manual testing, comprehensive before-and-after video documentation of layout inflation behavior on physical devices, and verified the absence of visual regressions across orientations. Acknowledged with a ❤️ from Nicolas Raoul. |
Open Pull Requests - Actively In Review
| PR | Title | Status | Description |
|---|---|---|---|
| #6808 | Nearby: prevent multiple offline snackbars from showing during map scroll | In Review | Fixed a snackbar state thrashing bug (Issue #6806) raised by neeldoshii where panning the map while offline caused repeated snackbar messages. Implemented a minimal state deduplication flag to stabilize the snackbar lifecycle without requiring a broader presenter refactor. Assigned by neeldoshii who self-requested review. |
| #6765 | Upload: drop duplicate images and display warning in custom selector | In Review | Resolved a silent data drop (Issue #6764) where SHA1-based deduplication mutated the user's selection without any notification. Currently in active review with maintainer RitikaPahwa4444 - iterating based on her feedback to simplify the duplicate detection logic using size-comparison after distinctBy. |
| #6772 | Upload: drop duplicate images and display warning in normal selector | In Review | Addressed the parity gap (Issue #6771) between the custom and normal selectors, ensuring consistent duplicate-content handling across both upload entry points. |
Issues Raised and Investigated
| Issue | Title | Status | Investigation |
|---|---|---|---|
| #6750 | Connection Error dialog traps user in infinite loop during offline upload | Closed | Root-caused an inescapable modal trap in the upload initialization phase. Fixed by PR #6752. |
| #6744 | Inconsistent swipe-to-dismiss behavior on More bottom sheet | Closed | Documented touch interception failure between ScrollView and BottomSheetCoordinator. Fixed by PR #6746. |
| #6764 | Custom selector silently drops duplicate content images | Open | Investigated SHA1 deduplication logic silently mutating the user's selection state array. Fixed by PR #6765. |
| #6771 | Normal selector allows duplicate content images without warning | Open | Identified the parity gap between gallery picker implementations. Fixed by PR #6772. |
| #6806 | Multiple Snackbars shown during No Internet scenario on Nearby Screen | Open | Reported by neeldoshii. Investigated the event flow in NearbyParentFragment - updateSnackbar() fires repeatedly during map scroll offline, causing text thrashing. Fixed by PR #6808. |
Pattern of Contributions: My work has consistently intersected with UploadMediaDetailFragment, ContributionController, and the custom media selectors - the precise classes that sit at the boundary between feature:contributions and the upload pipeline. By debugging recursive dialog loops, SHA1 deduplication races, and scroll-interception failures, I have developed a firsthand, applied understanding of why these classes are so difficult to maintain in their current monolithic form. This familiarity uniquely positions me to safely untangle them without causing upstream regressions. My work on the Nearby snackbar bug (#6808), assigned directly by neeldoshii, further demonstrates active engagement with mentor-identified issues.
Synopsis and Project Overview
The Problem
The Wikimedia Commons Android app has grown over the years into a highly capable but increasingly fragile software monolith. The application currently uses a mixed architecture: callback-heavy Activity flows (for example login), MVP contract/presenter patterns in major areas like upload and explore, and partial ViewModel/Compose adoption. It also has a large XML UI surface (124 layout XML files in the repo) and significant RxJava 2 usage, a reactive library that has officially reached End-of-Life status (Issue #4665) and is no longer maintained, alongside newer coroutine-based code, which increases maintenance complexity and migration risk.
This tight coupling produces real, user-facing consequences. Configuration change crashes in the login flow (#6675, #6676, #6678) stem from lifecycle complexity: state is managed directly in LoginActivity with callback-driven async paths; although it already persists key fields via onSaveInstanceState, the flow remains brittle under error/retry and configuration transitions. ANR risk exists in blocking paths such as (fr.free.nrw.commons.auth.csrf.CsrfTokenClient.getTokenBlocking), and upload-related file processing paths require explicit dispatcher confinement and call-site auditing to ensure heavy I/O never runs on the UI thread. The monolithic structure also severely impacts Gradle build times and creates a daunting barrier to entry for new contributors.
The Solution
This project proposes a pragmatic, phased, and deeply technical modernization of the Commons app through Modularization and Incremental Jetpack Compose Integration. Based on the strategic priorities explicitly defined by maintainers neeldoshii, rohit9625, and RitikaPahwa4444 in architecture discussions (#6691, #6627), this project will reject a dangerous big-bang rewrite. Instead, it employs an incremental "copy, extract, and refactor" approach - isolating features into independent Gradle modules, proving the architecture, and integrating them back without breaking the main branch.
The 350-hour scope is strictly focused on two critical user flows, in the priority order explicitly requested by the maintainers:
- The Login / Auth Module (Priority 1): The foundational module, currently plagued by state-loss bugs on device rotation. Extracted first to establish the new architectural standard, prove the convention plugin infrastructure, and eliminate the highest-severity ANRs.
- The Contributions Module (Priority 2): The user's media upload history - a high-visibility bottom-nav surface with a clean Room-backed data boundary (Contribution entity, ContributionDao), Paging 2 infrastructure ready for a Paging 3 upgrade, and MVP presenters ready for MVVM/StateFlow extraction. Modularized second, building directly on the architectural conventions proven by feature:auth. The upload pipeline is explicitly deferred as a post-GSoC continuation per mentor guidance, ensuring the 350-hour scope delivers two high-quality, fully-tested modules rather than one incomplete migration of an over-complex wizard.
Two fully stabilized, modernized, rigorously tested modules are more valuable than ten poorly integrated modules that break the app. This is the core engineering philosophy of this proposal.
Issue Links:
- App Revamp with Compose (Parent Issue): https://github.com/commons-app/apps-android-commons/issues/6626
- App Modularization: https://github.com/commons-app/apps-android-commons/issues/6628
- Compose Migration: https://github.com/commons-app/apps-android-commons/issues/6629
- GSoC 2026 Discussion (Scope & Strategy): https://github.com/commons-app/apps-android-commons/issues/6691
- Phabricator Task: T415272: GSoC 2026: Modularization + Jetpack Compose in Android Commons App
Technical Approach and Implementation Details
Every technical decision below is directly informed by three sources: my production experience scaling Naivety to 800+ global users, my month of study of the Commons codebase, and the explicit architectural consensus emerging from the maintainer discussions. Where the maintainers have open debates, I take explicit, reasoned positions.
5.1 Modularization Strategy
I will implement a phased multi-module architecture inspired by Google's NowInAndroid conventions - an approach specifically championed by rohit9625 in Issue #6691. The current repo is a single-module setup (include(":app")), so this plan starts with infrastructure extraction (build-logic + core modules) and then incrementally migrates priority features. The overarching, unbreakable rule governing the entire dependency graph is: Feature modules may never depend on each other. They depend only on core modules. The top-level app module will progressively become an integration shell as core/feature modules are introduced in phases. This eliminates circular dependencies and maximizes parallel Gradle compilation.
The proposed module graph:
- build-logic: The foundation. Contains Gradle Convention Plugins (e.g., commons.android.feature, commons.android.compose) written in Kotlin DSL, following the NowInAndroid pattern. Centralizing dependency versions via version catalogs and build configurations here ensures completely DRY build scripts across all future modules, preventing version drift.
- core:network: Base Retrofit configurations, OkHttp interceptors, error handling sealed classes, and shared API interface foundations.
- core:database: Room database setup. Critical architectural note per psh's specific warning in #6627: DB @Entity classes will be kept strictly internal to this module. All data exposed to the domain layer will be mapped to pure Kotlin data classes. Database annotations must never pollute the UI or Domain layers. The ongoing ContentProvider-to-Room migration tracked in Issue #6768 is a parallel initiative and is explicitly out of scope.
- core:domain: Shared business models, abstract repository interfaces, and core use-cases. This layer contains zero Android framework dependencies, making it Kotlin Multiplatform (KMP) ready - directly enabling the maintainers' long-term vision of an iOS release.
- core:ui: The centralized Jetpack Compose design system. Encapsulates Material 3 theme tokens overridden with Wikimedia Codex color/typography values, and reusable stateless composables (e.g., CommonsPrimaryButton, ErrorStateView, MediaThumbnail).
- core:di: Shared Dagger 2 component interfaces and module definitions, acting as integration glue between the legacy monolithic graph and the newly extracted modules.
- feature:auth: Priority 1. Encapsulates login, signup, CSRF token handling, and user session management. Contains LoginClient, CsrfTokenClient, WikiAccountAuthenticatorService.
- feature:contributions: Priority 2. Encapsulates the user's media contribution history: the Contribution Room entity, ContributionDao, ContributionsLocalDataSource, ContributionsRepository, ContributionBoundaryCallback, and the ContributionsListFragment / ContributionsListAdapter UI surface. The upload trigger boundary (ContributionController → UploadActivity) will be abstracted via a clean domain interface (UploadLauncher) in core:domain, allowing feature:contributions to be fully independent
5.2 Architecture Pattern: MVP to MVVM / MVI Hybrid
The current architecture includes MVP contracts in multiple flows (for example ContributionsContract, PagingContract, UploadMediaDetailsContract) and callback-heavy Activity logic in auth, creating inconsistent state handling patterns across the app and massive circular dependencies between Views and Presenters. This paradigm produces context leaks, untestable UI logic, and catastrophic state loss during Android lifecycle events.
Transitioning to Unidirectional Data Flow (UDF) is non-negotiable, as explicitly agreed upon by all maintainers. I propose a pragmatic MVVM + StateFlow architecture with MVI characteristics applied only where complexity demands it:
- For simpler screens (Login, Onboarding): Pure MVVM with Kotlin StateFlow is highly effective. Introducing full MVI intents and reducers for a login screen creates unnecessary boilerplate.
- For complex, event-driven screens (Upload Flow, Nearby): An MVI-adjacent pattern using Kotlin sealed classes for UiState and UiEvent provides rigid state guarantees. When EXIF processing, SHA1 hashing, and network requests mutate state simultaneously, MVI prevents race conditions. This aligns with rohit9625's MVVM+MVI proposal and psh's observation that event flows are better handled with sealed classes.
Code Transformation: Current Login Flow → MVVM/MVI
Current login implementation in LoginActivity.kt (callback-driven, no lifecycle safety):
@VisibleForTesting fun performLogin() { val username = binding!!.loginUsername.text.toString() val password = binding!!.loginPassword.text.toString() val twoFactorCode = binding!!.loginTwoFactor.text.toString() showLoggingProgressBar() loginClient.doLogin(username, password, lastLoginResult, twoFactorCode, Locale.getDefault().language, object : LoginCallback { override fun success(loginResult: LoginResult) = runOnUiThread { progressDialog!!.dismiss() onLoginSuccess(loginResult) } override fun error(caught: Throwable) = runOnUiThread { progressDialog!!.dismiss() showMessageAndCancelDialog(caught.localizedMessage ?: "") } } ) }
State is also manually saved/restored today (brittle under error/retry paths):
override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString(SAVE_USERNAME, binding!!.loginUsername.text.toString()) outState.putString(SAVE_PASSWORD, binding!!.loginPassword.text.toString()) outState.putBoolean(SAVE_TWO_FACTOR_VISIBILITY, binding!!.twoFactorContainer.visibility == View.VISIBLE) }
Proposed MVVM/MVI ViewModel (LoginViewModel.kt in feature:auth):
class LoginViewModel @Inject constructor( private val loginRepository: LoginRepository, private val savedStateHandle: SavedStateHandle // Survives process death ) : ViewModel() { private val _uiState = MutableStateFlow<LoginUiState>(LoginUiState.Idle) val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow() fun handleEvent(event: LoginEvent) = when (event) { is LoginEvent.SubmitCredentials -> doLogin(event.username, event.password) is LoginEvent.TogglePasswordVisibility -> toggleVisibility() is LoginEvent.TwoFactorEntered -> handleTwoFactor(event.code) } private fun doLogin(username: String, password: String) { viewModelScope.launch { _uiState.value = LoginUiState.Loading _uiState.value = when (val result = loginRepository.login(username, password)) { is Result.Success -> LoginUiState.Success(result.data) is Result.Error -> LoginUiState.Error(result.message) } } } }
This transition will closely follow the template demonstrated in neeldoshii's draft PR #6082, ensuring alignment with maintainer expectations from day one.
5.3 RxJava to Kotlin Coroutines + Flow Migration
RxJava 2's official End-of-Life status (Issue #4665) necessitates a migration. However, attempting to remove RxJava globally across a decade-old codebase in a single 350-hour project is catastrophic risk. The strategy I will employ is precisely the approach suggested by psh: the kotlinx-coroutines-rx2 bridge library.
This library provides extension functions (.asFlow(), rxSingle { }, .await()) that allow incremental interop while both paradigms coexist (RxJava2 is still widely used, and kotlinx-coroutines-rx2 is already present in dependencies), reducing rewrite risk during modular extraction. Existing Rx code requires no changes during the initial extraction phase.
Execution Context Safety: A subtle, known issue with the Rx2 bridge is the behavior of Dispatchers.Unconfined when chaining rxSingle calls (tracked in kotlinx.coroutines issue #2925). I will enforce explicit dispatcher definitions (Dispatchers.IO or Dispatchers.Default) within all coroutine scopes to guarantee thread-switching behaves identically to the legacy Schedulers.io(), preventing UI freezes introduced by the migration itself.
Targeted ANR Resolution: The migration directly addresses the critical ANR identified by RitikaPahwa4444: fr.free.nrw.commons.auth.csrf.CsrfTokenClient.getTokenBlocking. The method name itself betrays the architectural flaw - a synchronous, blocking network call. Migrating CsrfTokenClient to Coroutines replaces this with a suspend fun executed safely via withContext(Dispatchers.IO), inherently resolving the ANR while vastly improving error propagation. The current codebase reveals this is a true blocking path:
@Throws(Throwable::class) fun getTokenBlocking(): String { val response = newSingleThreadExecutor() .submit(Callable { csrfTokenInterface.getCsrfTokenCall().execute() }) .get() // ... }
5.4 Feature 1: Login Module (Priority 1) - Detailed Plan
Per neeldoshii's explicit direction in Issue #6691, the auth/login flow is the immediate priority. The current implementation is plagued by systemic configuration change bugs - not individual bugs requiring patches, but root-cause architectural failures requiring a proper fix.
Why These Bugs Exist: Issues #6678 (2FA state disappears on rotation), #6676 (Place type screen freezes), and #6675 (crash after bad credentials) all stem from the same root: the auth flow is implemented in LoginActivity with callback-driven async handling and manual state restoration via onSaveInstanceState. While key fields are persisted, this pattern remains brittle under complex transitions (2FA, error/retry paths) and is significantly harder to reason about and test than a ViewModel state model. The correct fix is MVVM migration with SavedStateHandle - not additional onSaveInstanceState hacks.
Execution Plan:
- Extraction: Extract LoginClient, CsrfTokenClient, and WikiAccountAuthenticatorService from the monolithic app module into feature:auth/data.
- State Survival: Implement LoginViewModel with Android's SavedStateHandle. User input strings and 2FA visibility toggles are persisted through process death and configuration changes, permanently addressing the root causes of #6678 and #6675 without any brittle manual hacks.
- ANR Eradication: Replace CsrfTokenClient.getTokenBlocking() with a non-blocking suspend fun using withContext(Dispatchers.IO), removing a blocking CSRF token path and reducing ANR risk from Android Vitals.
- Architectural Reference: The architectural lessons from psh's 2024 proof-of-concept PR #5695 will be studied and adapted to fit the finalized multi-module boundaries.
- UI Migration: LoginActivity will be migrated incrementally to Compose using Activity-level setContent { ... } (the same pattern already used in SingleWebViewActivity.kt), while ComposeView remains the interoperability path for Fragment/XML surfaces. This becomes the first screen in the new core:ui design system.
5.5 Feature 2: Contributions Module (Priority 2)
The Contributions screen is the first thing a logged-in Commons contributor sees. It is also the most architecturally representative surface for proving the modularization pattern: it has a clean Room-backed data boundary, a Paging 2 pipeline ready for upgrade, two tightly coupled MVP presenters, and a RecyclerView list UI that is a textbook Compose migration candidate.
My direct contributions to the upload workflow - the offline dialog loop fix in UploadMediaDetailFragment (#6752), the duplicate-hash detection in the custom selector (#6765), and the SHA1 parity gap in the normal selector (#6772) - have given me firsthand knowledge of exactly why the upload module is too large and tightly coupled to safely migrate alongside Core and Auth in 350 hours. The Contributions module gives us the same architectural value - a proven Room entity, presenter-to-ViewModel migration, and a Compose list surface - without the EXIF, SHA1, WorkManager, and multi-step wizard risk that makes upload a dedicated GSoC project in its own right.
Why Contributions is the right second module:
The Contribution Room entity (ContributionDao) is already isolated from the rest of the codebase and provides a clean extraction seam:
@Entity(tableName = "contribution") data class Contribution( @Embedded(prefix = "media_") val media: Media, @PrimaryKey val pageId: String = media.pageId, var state: Int = 0, var imageSHA1: String? = null, var dateUploadStarted: Date? = null, // ... )
The DAO already uses Room and exposes paginated queries via Paging 2's DataSource.Factory:
@Query("SELECT * FROM contribution order by media_dateUploaded DESC") abstract fun fetchContributions(): DataSource.Factory<Int, Contribution>
The network sync uses ContributionBoundaryCallback with RxJava - a straightforward kotlinx-coroutines-rx2 bridge target:
mediaClient.getMediaListForUser(userName) .map { list -> list.map { Contribution(media = it, state = STATE_COMPLETED) } } .subscribeOn(ioThreadScheduler) .subscribe({ saveContributionsToDB(it) }, { Timber.e(...) })
The upload trigger coupling in ContributionController → UploadActivity will be abstracted into a UploadLauncher interface in core:domain, allowing feature:contributions to fire upload intents without a hard dependency on the upload module.
Execution Plan:
- Extraction: Extract Contribution, ContributionDao, ContributionsLocalDataSource, ContributionsRepository, ContributionBoundaryCallback, ContributionsListPresenter, ContributionsPresenter, and the list UI fragments into feature:contributions, with minimal behavioral changes. Verify the app builds and runs identically after extraction.
- Architecture Upgrade: Replace ContributionsListPresenter and ContributionsPresenter (MVP/RxJava) with ContributionsViewModel using StateFlow. Upgrade Paging 2 DataSource.Factory + LivePagedListBuilder to Paging 3 PagingSource + Pager emitting Flow<PagingData<Contribution>>, using the kotlinx-coroutines-rx2 bridge for the ContributionBoundaryCallback network fetch.
- Compose Migration: Replace the legacy RecyclerView + ContributionsListAdapter with a Compose LazyVerticalGrid using collectAsLazyPagingItems(). Build a reusable ContributionThumbnail composable in core:ui using AsyncImage (Coil) to replace the Fresco SimpleDraweeView.
- Upload Boundary: Introduce a UploadLauncher interface in core:domain. Implement it in the app module to launch UploadActivity, keeping feature:contributions fully decoupled from the upload pipeline and documenting this boundary as the seam where a future feature:upload module will plug in.
- Tests: Unit tests for ContributionsViewModel StateFlow transitions using TestCoroutineScheduler. Paging 3 PagingSource tests. Compose UI tests for the contribution list grid state transitions.
5.6 Jetpack Compose Integration
My experience with Jetpack Compose goes beyond documentation; through Naivety, I architected and shipped an offline-first Compose application to 800+ users globally. I have worked through real-world challenges: understanding where unnecessary recomposition can decimate frame rates on low-end devices, how to structure state hoisting in multi-step flows, and how to manage CompositionLocal sparingly to avoid global state coupling.
Integration Strategy: I will use ComposeView within the newly refactored Fragments to host @Composable screens, guaranteeing safe interoperability with the existing XML-based UI and FragmentManager throughout the transition. The codebase already supports Activity-level Compose integration, as seen in SingleWebViewActivity.kt:
setContent { Scaffold( topBar = { TopAppBar( title = { Text(getString(R.string.vanish_account)) } ) } ) { /* ... */ } }
Design System Resolution: Acknowledging the ongoing debate between Material Design and Wikimedia's Codex design system (Ritika vs. rohit9625), I propose a pragmatic middle path. Building a bespoke Compose system to perfectly mirror Codex CSS is out of scope for 350 hours. Instead, core:ui will be built upon standard Material Design 3 components with the token values (Typography, Colors, Shapes) strictly overridden with Wikimedia Codex design tokens. This hybrid - mirroring the strategy employed by the Wikipedia iOS app - satisfies Ritika's requirement for Wikimedia branding consistency without massive engineering overhead.
Baseline Profiles will be integrated into the build process to pre-compile critical Compose UI paths, preventing JIT compilation delays on first launch (~30% startup improvement per Android documentation).
5.7 Dependency Injection Strategy
The Commons app is deeply entwined with legacy Dagger 2 constructs including dagger-android, AndroidInjectionModule, and DispatchingAndroidInjector.
Critical Scope Definition: No Global Hilt Migration
- Migrating the entire legacy monolithic Dagger 2 graph to Hilt in 350 hours is an unacceptable risk that will almost certainly introduce regressions across a large set of legacy XML-based screens/layouts.
- The untouched app module will remain on legacy Dagger 2.
- The newly created core and feature modules will use standard Dagger 2 components with clearly defined interface boundaries (Dependencies).
- By scoping dependencies across module boundaries now, this architecture architecturally unblocks a future Hilt migration without requiring it today.
- This demonstrates the engineering realism the maintainers have explicitly asked for.
5.8 Navigation
Maintainer psh correctly highlighted the advantages of Google's new Navigation 3 API. Navigation 3 is built from the ground up for Compose - the back stack is simply a SnapshotStateList<T>, giving developers complete type-safe control over navigation state.
For project safety in a 350-hour timeline, all existing FragmentManager transactions will remain entirely unchanged during the extraction phases. Navigation modernization inside newly Compose-based feature surfaces will be approached incrementally after module boundaries and state architecture are stabilized, and only as capacity allows. This ensures that navigation complexity never becomes a risk to the primary deliverables.
5.9 Testing Strategy
Following the strict requirements in gsoc.md, all newly extracted and refactored code will be rigorously tested. No PR will be submitted without accompanying tests.
- Domain / Data: Comprehensive unit tests for UseCases and Repositories using JUnit 5 and MockK.
- ViewModel State Transitions: Validated using kotlinx-coroutines-test with runTest and TestCoroutineScheduler to virtually advance time, verifying exact StateFlow emissions without flaky timing dependencies.
- Compose UI Tests: Using createComposeRule() to verify that interactive elements - password toggle, 2FA input, and contribution list grid state transitions (Loading, Success, Empty, Error) - behave correctly across all state transitions.
- Integration Tests: Smoke tests of each integrated module against the Beta server, verifying the complete auth flow and upload pipeline end-to-end before any PR is submitted for merge.
The Login module currently suffers from sparse test coverage. This targeted migration will dramatically elevate its reliability metrics, directly benefiting future contributors.
5.10 Scope Management: What Will NOT Be Done
To ensure that feature:auth and feature:upload are migrated with the highest degree of safety and production quality within 350 hours, the following are strictly out of scope:
Upload module migration (wizard + background pipeline): As discussed with mentor @Kaartic, the Upload flow (UploadActivity, FileProcessor, UploadService, and 43+ related files across multiple presenter/contract layers, WorkManager pipelines, and EXIF/SHA1 processing chains) is too large and tightly coupled to safely migrate alongside Core and Auth in a 12-week timeline. It is strictly treated as post-GSoC continuation work. The architectural foundation this project establishes - core:domain interfaces, core:database entity-to-domain mapping, and the proven feature:auth modularization pattern - is designed to make that future extraction safe and straightforward.
Full Dagger 2 → Hilt migration: The regression risk across a large set of legacy XML-based screens/layouts is too high within this time constraint.
Global RxJava removal: RxJava is removed only within the boundaries of feature:auth and feature:contributions. The kotlinx-coroutines-rx2 bridge safely handles all remaining legacy code including the upload pipeline.
Fresco → Coil image library migration: While Coil is natively superior for Compose, replacing the global image pipeline introduces systemic risk entirely unrelated to the primary modularization goal.
ContentProvider-to-Room global migration: This data-layer shift is actively tracked in Issue #6768 and is a parallel initiative, not this proposal's responsibility.
Full Compose migration of large set of legacy XML-based screens/layouts: Only the auth and contributions modules receive Compose UI migration within this project's scope. All other screens remain on XML.
Certificate pinning and security audit: Valuable long-term, but out of scope for this architectural modernization.
These explicit boundaries guarantee the delivery of two fully stabilized, production-quality modules - not ten poorly integrated, regression-prone modules that over-promise and under-deliver.
Diagrams
The following five diagrams support the architectural narrative of this proposal.
Diagram 1: Module Dependency Graph
Diagram 2: MVP → MVVM/MVI Migration Flow
Diagram 3: Incremental Compose Adoption Strategy
Diagram 4: RxJava → Coroutines Bridge Strategy
Diagram 5: 12-Week Gantt Chart
Timeline
NOTE: This timeline is an initial framework and will be actively refined in consultation with my mentors (neeldoshii and Kaartic) to accommodate any unforeseen codebase complexities discovered during the extraction phases.
| Week / Phase | Focus Area | Specific Deliverables & Tasks |
|---|---|---|
| Pre-Bonding | Discovery & Architecture | Deep study of legacy DI graph, UploadService, WikiAccountAuthenticatorService, and LoginClient. Set up development environment. Discuss and finalize module boundaries with mentors to prevent scope creep. Send proposal to mailing list (commons-app-android@googlegroups.com) for early architectural feedback. |
| Week 1 | Core Infrastructure - Phase A | Establish build-logic module with Gradle Convention Plugins (NowInAndroid pattern) - the lowest-risk, highest-leverage first step. Create core:domain (interfaces, shared models, zero Android dependencies). Create core:di foundation. Work part-by-part starting from the smallest shared pieces as advised, with explicit compile-and-test gates before moving forward. Note: Core extraction is stability-gated, not calendar-gated - clean module boundaries matter more than hitting a fixed date. Verify: all Gradle builds execute successfully before advancing. |
| Week 2 | Core Infrastructure - Phase B | Extract core:network (Retrofit base configs, OkHttp interceptors, shared API error handling). Establish core:database (Room setup, strict entity-to-domain model mapping per psh's guidance - @Entity classes strictly internal, domain layer receives only pure Kotlin data classes). Each extraction step validated with compile-and-test gates before proceeding to the next. Tests: network parser unit tests, database mapper unit tests. |
| Week 3 | Core Stabilization + feature:auth Skeleton | Stabilize all core module boundaries - verify no regressions across the full test suite, confirm @Entity isolation holds, and harden dependency rules in settings.gradle.kts so no feature module can accidentally import another. Begin feature:auth module skeleton: extract LoginClient, CsrfTokenClient, and WikiAccountAuthenticatorService from the monolith with zero behavioral changes. Introduce non-blocking coroutine-based token retrieval and begin migrating callers away from getTokenBlocking(). This week serves as both a core health checkpoint and the auth extraction starting point - ensuring the foundation is proven stable before any ViewModel or UI work begins. Tests: full regression suite across core modules; initial LoginRepository skeleton unit tests. |
| Week 4 | feature:auth Domain, ViewModel & Repository | Complete LoginRepository with kotlinx-coroutines-rx2 bridge. Implement LoginUseCase and LoginViewModel with StateFlow. Integrate SavedStateHandle and explicit UI-state modeling to preserve 2FA tokens and input state across configuration and process recreation, directly targeting the failure patterns behind #6675, #6678, #6676. Tests: Repository unit tests using kotlinx-coroutines-test; ViewModel unit tests with TestCoroutineScheduler asserting all StateFlow transitions. |
| Week 5 | feature:auth Compose UI | Establish core:ui with Material 3 theme tokens overridden with Wikimedia Codex values. Migrate LoginActivity UI incrementally to Jetpack Compose using Activity-level setContent { ... }, using ComposeView for Fragment/XML interop where needed. Build LoginScreen() and TwoFactorScreen() composables. Tests: Compose UI tests for the full interactive login flow. |
| ⭐ WEEK 6 - MIDTERM | Midterm Integration | Full integration of feature:auth into the main app module dependency graph. Comprehensive manual and automated regression testing of the complete login pipeline on physical devices. MIDTERM MILESTONE: 1 complete, modernized, fully tested feature module shipped without breaking the main branch. |
| Week 7 | feature:contributions Extraction | Create feature:contributions module skeleton. Extract Contribution, ContributionDao, ContributionsLocalDataSource, ContributionsRepository, and ContributionBoundaryCallback from the monolith with zero behavioral changes. Introduce UploadLauncher interface in core:domain to abstract the ContributionController → UploadActivity trigger boundary. Verify: app builds and behaves identically after extraction. Tests: compile-and-run regression suite. |
| Week 8 | feature:contributions Architecture Upgrade | Replace ContributionsListPresenter and ContributionsPresenter (MVP/RxJava) with ContributionsViewModel using StateFlow. Upgrade Paging 2 DataSource.Factory + LivePagedListBuilder to Paging 3 PagingSource + Pager emitting Flow<PagingData<Contribution>>. Use kotlinx-coroutines-rx2 bridge for ContributionBoundaryCallback network fetch. Tests: ContributionsViewModel StateFlow transition unit tests; PagingSource unit tests. |
| Week 9 | feature:contributions Compose UI | Replace legacy RecyclerView + ContributionsListAdapter (Paging 2) with a Compose LazyVerticalGrid using collectAsLazyPagingItems(). Build reusable ContributionThumbnail composable in core:ui to replace Fresco SimpleDraweeView. Tests: Compose UI tests for contribution list grid states (Loading, Success, Empty, Error). |
| Week 10 | feature:contributions Integration & Post-GSoC Foundation | Full integration of feature:contributions into the main app module. Expand core:ui with contributions-specific reusable composables. Document the UploadLauncher interface boundary as the clean seam for a future feature:upload module. Tests: End-to-end integration tests of the contribution history flow including paging, media detail navigation, and upload retry trigger using Beta server test accounts. |
| Week 11 | Testing & Optimization | Comprehensive manual regression pass across both migrated modules, including edge-case network failure scenarios. Generate Baseline Profiles for login and upload flows to optimize Compose first-launch rendering. Fix any lingering regressions. |
| Week 12 | Polish & Documentation | Finalize developer documentation covering module boundaries, DI component graph, and rx2 bridge usage patterns - enabling future GSoC contributors to continue the migration. Draft final project blog post. Submit final GSoC report. |
Motivation: Why Me?
I am applying for this project because the technical challenges it presents - modularization, complex lifecycle-safe state management, and declarative UI performance optimization - are areas where I have gained genuine, hands-on expertise through real-world production work.
My experience with Jetpack Compose goes beyond documentation; through Naivety, I architected and shipped an offline-first Compose application that is currently serving over 800 users globally. Working through production challenges - preventing unnecessary recomposition from degrading frame rates on sub-$100 Android devices, structuring state hoisting in multi-step flows like an upload wizard, and understanding why CompositionLocal is dangerous for global state - has given me practical knowledge that I look forward to applying collaboratively on the Commons codebase. When I migrate a screen to Compose for Commons, I will apply these production-hardened optimizations to ensure the app remains fast and responsive across all device tiers.
My contributions to Commons have also been focused rather than scattered. Every PR I have submitted - the offline upload loop fix in UploadMediaDetailFragment, the duplicate-hash detection in the custom selector, the scroll interception fix in the bottom sheet - has directly engaged with the tightly-coupled complexity of the upload module. I have seen firsthand what happens when business logic, UI orchestration, and background work are spread across tightly coupled presenter/contract classes and callback-driven Activity logic. Having contributed three focused PRs to the upload module and traced its UploadActivity wiring through 43+ files, EXIF pipelines, SHA1 hashing, and WorkManager chains, I fully agree with the mentor guidance that it deserves its own dedicated GSoC cycle - and the architectural conventions, core:domain interfaces, and proven module boundaries this project establishes are designed explicitly to make that future extraction safe and straightforward for whoever picks it up. The modularization strategy in this proposal was shaped by someone who has read the specific files being migrated.
I have carefully read every comment in the maintainer architecture discussions (#6691, #6627). I understand Ritika's preference for Codex styling and have proposed a practical path to achieve it. I have adopted rohit9625's NowInAndroid-inspired three-layer proposal as the foundation of my plan. I have built psh's specific warning about leaking DB annotations into the core:database boundary definition. Finally, I have followed neeldoshii's explicit priority to fix Login first, and actively adapted Priority 2 to feature:contributions based on mentor Kaartic's direct guidance to ensure a safe, de-risked timeline. This proposal is not an independent invention - it is a synthesis of the community's own architectural requirements, and I am eager to implement it collaboratively, under the guidance of the mentors, and with respect for the community's consensus-driven process.
I am committed to this work beyond the GSoC period. Continuing to modularize the contributions module, the nearby module, and beyond - building the architectural foundation that future GSoC contributors can build upon for years - is something I genuinely want to do as part of this community.
Previous GSoC / Outreachy Experience
This is my first application to Google Summer of Code. I have not previously participated in GSoC or Outreachy.
Availability Statement
I am fully available to commit 46+ hours per week to this project throughout the GSoC 2026 coding period. I have no other internships, intensive academic coursework (my end semester exam would be over this month itself), or external commitments scheduled during this time. If critical milestones require extended effort to maintain quality and stability, I am entirely willing to increase my hours. My timezone (IST, UTC +5:30) provides substantial natural overlap with my mentors' schedules, enabling rapid review cycles, code feedback, and real-time architectural discussions throughout the summer.




