Profile Information
| Attribute | Details |
|---|---|
| Name | Muhammad Shoaib Khalid |
| gshoaib998@gmail.com | |
| GitHub | https://github.com/Shoaibkhalid65 |
| https://www.linkedin.com/in/muhammad-shoaib-khalid | |
| Location | Punjab, Pakistan |
| Timezone | PKT (UTC +5:00) |
| University | Islamia University of Bahawalpur |
| Degree | BS Software Engineering (Final Year) |
| Mentors | Neeldoshii, Kaartic Sivaraam |
| Availability | 45 hours/week, full-time |
| Expected Project Size | 350 hours (Large) |
Synopsis
Project: Modularization and Jetpack Compose Migration of the Wikimedia Commons Android App
The App
Wikimedia Commons Android is an application that enables users to browse, upload, and contribute freely licensed media directly to the Wikimedia Commons repository from their mobile devices. It is one of the most important tools in the Wikimedia ecosystem — empowering volunteers globally to enrich the world's largest freely licensed media repository.
The Problem & The Solution
| Problem | Solution |
|---|---|
| Monolithic Structure — All modules and files live inside a single :app module. This makes the codebase difficult to maintain, increases Gradle build times, and creates a high barrier for new contributors who struggle to understand where functionality lives. | Introduce a multi-module architecture splitting code into cohesive :core and :feature modules. This increases cohesion and decreases coupling between features, following Google's official Android modularization guide. |
| Outdated Libraries — The app relies on libraries that are no longer standard in modern Android development: RxJava 2 (End-of-Life), legacy Dagger 2 with dagger-android, and Paging 2. These increase complexity, reduce readability, and limit adoption of modern Jetpack APIs. | Incrementally migrate to modern equivalents: Kotlin Coroutines + Flow, Hilt, and Paging 3 — adopted module by module to avoid introducing instability. |
| Legacy XML UI — The UI is built on 100+ XML layout files. XML's imperative nature requires significant extra effort to manage state, handle configuration changes, and integrate with modern Jetpack libraries. It is no longer the standard for Android UI development. | Incrementally migrate to Jetpack Compose — the declarative UI toolkit that is the present and future of Android UI. Compose integrates naturally with ViewModels, StateFlow, and Paging 3, reducing boilerplate and improving state management. |
| Architectural Debt (MVP) — The app uses the Model-View-Presenter pattern with *Contract.kt interface files for every screen. Presenters are destroyed during configuration changes causing permanent state loss. MVP is no longer the recommended pattern and creates tight coupling between View and Presenter. | Migrate to MVVM with StateFlow — the currently recommended architecture by Google. ViewModels survive configuration changes natively. The UI layer will contain only UI-related code, with all state logic inside the ViewModel. |
| Blocking Operations and ANRs — The codebase contains blocking calls such as CsrfTokenClient.getTokenBlocking() and heavy file I/O in FileProcessor.save() that can block the main thread, directly causing Application Not Responding (ANR) errors visible in Android Vitals. | Replace blocking calls with Kotlin Coroutines using Dispatchers.IO for background work and Dispatchers.Main for UI updates — eliminating the root cause of ANRs in the auth and upload flows. |
Before & After These Changes
Before
The app currently uses an old architecture that makes development slow and risky. Whenever someone tries to add a new feature or fix a bug, there is a good chance something else breaks. The codebase is also hard to navigate for new contributors who are used to modern Android development. On top of that, the UI feels outdated, and users have been pointing this out for a long time.
After
- Users — They get a more stable app with fewer crashes and a UI that finally feels modern and polished.
- New volunteer contributors — The code will be easier to understand, easier to test, and much friendlier for someone joining the project for the first time.
- The project long-term — A clean, modular codebase means the community can keep the app healthy and growing without it becoming a burden to maintain.
Issue Links
- App Revamp with Compose (Parent Issue) — #6626
- App Modularization — #6628
- Compose Migration — #6629
- GSoC 2026 Discussion (Scope & Strategy) — #6691
- Phabricator Task — T415272: GSoC 2026: Modularization + Jetpack Compose in Android Commons App
Priority of Modules
- Onboarding
- Auth (Sign In & Sign Up)
- Contributions
- Upload
- Profile
- About
- Notifications
- Review
Why This Priority?
The first four modules — Onboarding, Auth, Contributions, and Upload — are the most critical parts of the app. As mentor @neeldoshi pointed out in issue #6691, the Auth, Contributions, and Upload sections are responsible for the most ANRs and performance issues in the app. After exploring the app myself, I found the same — these sections feel slow and the user experience is noticeably poor compared to what users expect from a modern app.
Beyond the performance issues, these modules are also the core of what the app does. Every user has to go through Onboarding and Auth before they can do anything, and Contributions and Upload are the two main reasons someone opens the app in the first place. It makes sense to fix the foundation before anything else.
After completing the high-priority sections, the Profile module will be addressed next, as its UI is outdated and needs to align with modern app standards. The About, Notifications, and Review sections are planned for later stages due to their lower complexity and minimal dependencies.
Deliverables
Section 4 — Technical Approach
4.1 Proposed Module Structure
Following the NowInAndroid convention and the approach discussed by rohit9625 in issue #6628, the monolithic :app module will be decomposed into the following structure:
build-logic/ ├── convention/ │ ├── commons.android.library.gradle.kts │ ├── commons.android.feature.gradle.kts │ └── commons.android.compose.gradle.kts core/ ├── core:network → NetworkingModule, OkHttpClient, Retrofit config, │ all *Interface.kt files (LoginInterface, UploadInterface, │ MediaInterface, WikidataInterface, CategoryInterface etc.) ├── core:database → AppDatabase, all 8 DAOs (ContributionDao, PlaceDao, │ DepictsDao, UploadedStatusDao, BookmarkLocationsDao etc.) ├── core:domain → Repository interfaces, shared models (Media, Contribution, │ Place) — pure Kotlin, zero Android dependencies ├── core:ui → CommonsTheme, shared Composables, reusable components └── core:di → Shared Dagger component interfaces feature/ ├── feature:auth → LoginActivity, SignupActivity, AccountUtil, │ SessionManager, WikiAccountAuthenticator, │ CsrfTokenClient, LoginClient ├── feature:contributions → ContributionsFragment, ContributionsListFragment, │ ContributionsListAdapter, ContributionController, │ ContributionsRepository, ContributionDao (usage), │ ContributionsPresenter (replaced by ViewModel), │ MainActivity ├── feature:upload → UploadClient, UploadInterface, FileProcessor, │ UploadService, UploadMediaDetailFragment, │ SimilarImageInterface, WikiBaseInterface ├── feature:onboarding → WelcomeActivity, onboarding screens ├── feature:nearby → NearbyParentFragment, NearbyController, PlaceDao (usage) └── feature:explore → ExploreFragment, MediaDetailFragment, BasePagingFragment
Feature modules may never depend on each other directly — they only depend on core modules. The top-level :app module is responsible for wiring everything together. This prevents circular dependencies and enables parallel Gradle compilation.
The build-logic convention plugins remove duplicated Gradle configuration across modules. Creating a new feature module becomes as simple as:
plugins { id("commons.android.feature") }All compileSdk, minSdk, Compose compiler config, and test dependencies are inherited automatically.
4.2 Migration Strategy
The migration follows a strict four-step process per module — never all at once:
- EXTRACT → Move existing code into the new module as-is
- VERIFY → Make sure everything still works exactly as before
- REFACTOR → Migrate to MVVM, Coroutines, and Compose incrementally
- INTEGRATE → Wire back into :app and run full regression tests
During extraction, a @Deprecated facade will be kept in the old package location that delegates to the new module. This prevents merge conflicts with other contributors' open PRs during the transition period.
4.3 Architecture: MVP → MVVM with StateFlow
The current MVP pattern uses ContributionsContract.kt, ContributionsListContract.kt, and ContributionsListPresenter.kt. The core problem with MVP is that it tightly couples the View and the data layer through contract interfaces. On top of that, MVP does not survive configuration changes, which leads to state loss on screen rotation.
MVVM is the architecture recommended by the official Google architecture guide. It separates UI from business logic cleanly, survives configuration changes through ViewModel, and works naturally with Jetpack libraries. The app already uses MVVM in a few places — this project extends it to the rest of the codebase.
Before — legacy MVP in ContributionsListPresenter.kt
class ContributionsListPresenter @Inject constructor( private val repository: ContributionsRepository ) : ContributionsListContract.UserActionListener { override fun setup(userName: String?, isSelf: Boolean) { view.showProgress(true) compositeDisposable.add( repository.getContributions(userName) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { view.showContributions(it) }, { view.showError(it.message) } ) ) } }
After — MVVM with StateFlow
@HiltViewModel class ContributionsViewModel @Inject constructor( private val repository: ContributionsRepository ) : ViewModel() { private val _uiState = MutableStateFlow<ContributionsUiState>(ContributionsUiState.Loading) val uiState: StateFlow<ContributionsUiState> = _uiState.asStateFlow() fun loadContributions(userName: String) { viewModelScope.launch { _uiState.value = ContributionsUiState.Loading repository.getContributions(userName) .catch { _uiState.value = ContributionsUiState.Error(it.message) } .collect { _uiState.value = ContributionsUiState.Success(it) } } } }
Data flow: Data Sources (Network, DB) → Repository → ViewModel → Screen
Note on Use Cases: Use cases will only be introduced where they are truly justified — where there is genuinely redundant ViewModel logic or complex business logic. If a use case simply passes data from the repository to the ViewModel, it adds unnecessary complexity.
4.4 Reactive Programming: RxJava 2 → Kotlin Coroutines + Flow
Current versions: RxJava 2.2.3, RxAndroid 2.1.0 — both End-of-Life. The Kotlin Flow API is the natural and modern replacement. For UI state we will use StateFlow. For one-time events like navigation or showing a Snackbar, we will use SharedFlow.
Critical ANR Fix — CsrfTokenClient.getTokenBlocking()
The method name itself reveals the problem. A synchronous blocking network call in the auth flow is a direct cause of ANRs.
// BEFORE — blocking call, ANR risk fun getTokenBlocking(): String { return runBlocking { csrfTokenInterface.getToken().execute().body()!! } } // AFTER — non-blocking suspend function suspend fun getToken(): String = withContext(Dispatchers.IO) { csrfTokenInterface.getToken() }
Similarly, FileProcessor.save() performs heavy file I/O that must move to Dispatchers.IO.
Bridge Strategy
During the transition, the kotlinx-coroutines-rx2 library will allow newly extracted modules to consume legacy RxJava streams as native Kotlin Flows — with zero changes required to existing RxJava code:
fun <T> Observable<T>.asFlow(): Flow<T> = callbackFlow { val d = subscribe( { trySend(it) }, { close(it) }, { close() } ) awaitClose { d.dispose() } }
4.5 Dependency Injection: Dagger 2 → Hilt (Incremental)
Current version: Dagger 2.23 using legacy dagger-android, AndroidInjectionModule, DispatchingAndroidInjector, and ActivityBuilderModule binding 24+ Activities into one global graph.
A full global migration to Hilt is out of scope for 350 hours — the regression risk across the entire legacy XML-based codebase is too high. The approach instead:
- All newly created :core and :feature modules will use Hilt from day one
- The untouched :app module stays on legacy Dagger 2 during the GSoC period
- Module boundaries will be defined using Dagger component interfaces that architecturally unblock a full Hilt migration in the future without requiring it now
4.6 Paging 2 → Paging 3
Current version: Paging 2.1.2 using DataSource, PagedList, and LivePagedListBuilder in ContributionsListPresenter.kt. Paging 3 introduces PagingSource and Pager emitting Flow<PagingData<T>> — which works natively with Compose via collectAsLazyPagingItems().
class ContributionsPagingSource @Inject constructor( private val repository: ContributionsRepository ) : PagingSource<Int, Contribution>() { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Contribution> { return try { val page = params.key ?: 1 val result = repository.getContributions(page, params.loadSize) LoadResult.Page( data = result, prevKey = if (page == 1) null else page - 1, nextKey = if (result.isEmpty()) null else page + 1 ) } catch (e: Exception) { LoadResult.Error(e) } } }
4.7 Jetpack Compose Migration
The UI will be migrated from XML to Jetpack Compose incrementally — not all at once. Some parts of the app already use Compose through the interoperability API (ComposeView). The migration will start from there and gradually move toward fully standalone Composables.
Screens Prioritized for Compose Migration
| Screen | Files | Priority | Reason |
|---|---|---|---|
| Login / Signup | LoginActivity.kt, SignupActivity.kt + 13 auth files | High | State-heavy, MVP causes rotation bugs, ideal for MVVM + Compose |
| Onboarding | WelcomeActivity.kt | High | Simple screens, low regression risk, good starting point |
| Contributions List | ContributionsListFragment.kt, ContributionsFragment.kt | High | Core user-facing screen, LazyColumn + Paging 3 replaces RecyclerView directly |
| Upload Detail | UploadMediaDetailFragment.kt | Medium | Complex but high impact — ANRs tracked in Android Vitals |
Screens Kept as XML (Hybrid Approach)
| Screen | Reason |
|---|---|
| Nearby Map (NearbyParentFragment.kt) | Tightly coupled to Osmdroid native C++ rendering — no mature Compose wrapper exists |
| Review (ReviewActivity.kt) | Stable, no architectural benefit from migration at this stage |
| Settings (SettingsActivity.kt) | PreferenceFragmentCompat based — complex to migrate, low priority |
4.8 Responsiveness & Adaptive UI
While reviewing PR #6807 (Welcome screen Compose migration), I noticed the contributor used hardcoded padding and size values. This could cause issues on devices with different screen sizes. I have thought about responsiveness in two levels:
- Level 1 — Large screen variants (phones vs tablets vs foldables): Handled using WindowSizeClass API to serve different layouts depending on the available window width.
- Level 2 — Small screen variants (different phone sizes): Handled using BoxWithConstraints and layout modifiers like fillMaxSize, aspectRatio, and weight — no hardcoded dp values.
New Compose screens in this project will follow both levels from the start.
4.9 Material 3 & Material 3 Expressive
I agree with psh's excitement about Material 3 Expressive in issue #6626. I also agree with Ritika's point in issue #6629 that we should not start a full UI revamp without stable design references. The Wikimedia Hackathon design proposals are a good source of inspiration and we should align with those before making major visual decisions.
1. FAB Menu in Contributions Screen
Currently the app manages multiple FABs using a LinearLayout with manual orientation handling. I have a merged PR (#6756) that fixes a related issue in this exact screen. The cleanest long-term solution is using the M3 Expressive FloatingActionButtonMenu:
FloatingActionButtonMenu( expanded = fabExpanded, button = { ToggleFloatingActionButton( checked = fabExpanded, onCheckedChange = { fabExpanded = it } ) { Icon( imageVector = Icons.Default.Add, contentDescription = null, modifier = Modifier.graphicsLayer { rotationZ = checkedProgress * 135f } ) } } ) { fabMenus.forEach { vector -> AnimatedVisibility(visible = fabExpanded) { SmallFloatingActionButton(onClick = { /* handle */ }, shape = CircleShape) { Icon(imageVector = vector, contentDescription = null) } } } }
2. LinearWavyProgressIndicator in Upload Screen
Replacing the current linear progress indicator with LinearWavyProgressIndicator gives a much more modern feel during uploads — consistent with how Google uses these components in Play Store and Google Drive.
Note on stability: M3 Expressive is currently available only in material3 alpha versions. Until it reaches stable, the FAB menu can be achieved using AnimatedVisibility, SmallFab, and Column from stable Material 3.
4.10 Database
Current: AppDatabase at version 21 using Room with 8 DAOs: ContributionDao, PlaceDao, DepictsDao, UploadedStatusDao, NotForUploadStatusDao, ReviewDao, BookmarkCategoriesDao, BookmarkLocationsDao. Room is retained and updated.
As highlighted by psh in issue #6627: @Entity classes must never leak into the UI or domain layers. All data exposed above the repository layer will be mapped to pure Kotlin domain models, keeping the database as a true implementation detail inside :core:database.
4.11 Testing Strategy
All newly extracted and refactored code will have accompanying tests. No PR will be submitted without tests.
- Repository and data layer — Unit tests using JUnit 4 and MockK
- ViewModel state transitions — Tested using kotlinx-coroutines-test with runTest and TestCoroutineScheduler, verifying exact StateFlow emissions
- Compose UI — Using createComposeRule() to verify interactive elements across all state transitions
- Integration — Smoke tests of each integrated module verifying complete user flows before PR submission
Implementation Plan
Community Bonding Period (May 1 – May 24)
This period is not for writing code. It is for making sure that when coding starts, there are no surprises.
- Trace the complete auth flow: LoginActivity → LoginClient → CsrfTokenClient → getTokenBlocking() to fully understand where the ANR is happening
- Study ContributionsListPresenter.kt, ContributionsListContract.kt, ContributionBoundaryCallback.kt to understand the current Paging 2 + RxJava + MVP wiring
- Study UploadService.kt and FileProcessor.kt to understand the file I/O and upload pipeline before touching it
- Read through the full DI graph in NetworkingModule.kt — understand how 24+ Activities are bound into the single Dagger component via ActivityBuilderModule
- Review all relevant issues: #6626, #6627, #6628, #6629, #6691
- Set up development environment, connect to Beta server for testing, join commons-app-android@googlegroups.com
- Finalize module boundaries with mentors — confirm which models (Media, Contribution, Place) are shared enough to live in core:domain
- Write a short architecture decision document and share with mentors for early feedback before Week 1 begins
Phase 1 — Foundation (Weeks 1–2)
Week 1 (May 25 – May 31) — build-logic + :core:domain + :core:di
Goal: Multi-module Gradle structure exists and builds cleanly. No feature code moved yet.
- Set up build-logic/convention/ with three convention plugins: commons.android.library.gradle.kts, commons.android.feature.gradle.kts, commons.android.compose.gradle.kts
- Migrate gradle/libs.versions.toml as the single source of truth for all dependency versions
- Create :core:domain — pure Kotlin, zero Android dependencies. Repository interfaces: LoginRepository, ContributionsRepository, UploadRepository. Shared domain models: Media, Contribution, Place
- Create :core:di skeleton — shared Dagger component interfaces for feature modules
- Verify: full Gradle build passes, :app still works exactly as before
Week 2 (Jun 1 – Jun 7) — :core:network + :core:database
Goal: All shared infrastructure extracted and tested. Feature modules can now depend on real network and database implementations.
- Create :core:network — move NetworkingModule.kt, OkHttpJsonApiClient.kt, CommonsServiceFactory.kt, and all distributed Retrofit *Interface.kt files into it
- Create :core:database — move AppDatabase.kt (version 21), all 8 DAOs, entity classes, and entity → domain model mappers into it
- Keep @Deprecated facades in original package locations delegating to the new modules — protects open contributor PRs from merge conflicts during transition
- Unit tests: network response parser tests, database mapper unit tests for ContributionDao
Phase 2 — Auth Module (Weeks 3–5)
Week 3 (Jun 8 – Jun 14) — :feature:auth data layer + ANR fix
Goal: The most critical ANR in the app is fixed. Auth data layer extracted and tested.
- Create :feature:auth module with data/, domain/, ui/ layers
- Extract all auth-related files into feature:auth/data/ — LoginClient.kt, CsrfTokenClient.kt, LogoutClient.kt, AccountUtil.kt, SessionManager.kt, WikiAccountAuthenticator.kt, WikiAccountAuthenticatorService.kt and all login subpackage files
- ANR Fix: Replace the synchronous blocking CsrfTokenClient.getTokenBlocking() with a proper suspend fun getToken() running on Dispatchers.IO
- Implement LoginRepositoryImpl depending on LoginClient and CsrfTokenClient, implementing LoginRepository from :core:domain
- Add RxJava → Flow bridge utility in :core:common for consuming legacy RxJava streams from new modules during transition
- Unit tests: LoginRepository tests covering successful login, wrong password, 2FA required, and network failure
Week 4 (Jun 15 – Jun 21) — :feature:auth domain + ViewModel
Goal: Auth business logic lives in a testable ViewModel. Login state-loss bugs resolved.
- Implement LoginViewModel with StateFlow for UI state and SharedFlow for one-time navigation events
- Define LoginUiState as a sealed interface: Idle, Loading, Success, TwoFactorRequired, Error
- Use SavedStateHandle to persist 2FA token and username input across configuration changes — directly fixing the login state-loss bugs reported in #6675, #6676, #6678
- Unit tests: assert every StateFlow transition using TestCoroutineScheduler
Week 5 (Jun 22 – Jun 28) — :feature:auth Compose UI + :core:ui
Goal: Login and Signup screens fully in Compose. core:ui theme ready for all future modules.
- Create :core:ui with CommonsTheme, CommonsTypography, CommonsColors, and shared composables: CommonsButton, CommonsTextField, CommonsLoadingWheel, CommonsTopAppBar
- Migrate LoginActivity to Compose using setContent { } — build LoginScreen and TwoFactorScreen composables
- Compose UI tests: verify all interactive states — empty fields, loading, error, success, and navigation events
Phase 3 — Onboarding Module (Week 6)
Week 6 (Jun 29 – Jul 5) — :feature:onboarding full module
Goal: Onboarding is a self-contained Compose module. Establishes the responsive layout patterns all future screens will follow.
- Create :feature:onboarding module, extract WelcomeActivity.kt and all onboarding screen files into it
- Migrate all onboarding screens fully to Compose — use WindowSizeClass for phone vs tablet layouts and layout modifiers like fillMaxWidth(), weight(), aspectRatio() instead of hardcoded dp values (addressing the concern from PR #6807)
- Implement OnboardingViewModel with StateFlow tracking current page and completion state
- Compose UI tests: page transitions, skip button, completion navigates correctly to auth flow
Midterm Milestone (Week 7 — Jul 6 – Jul 12)
Deliverable: :feature:auth + :feature:onboarding fully shipped, integrated, and tested without breaking main.
- Full integration of both modules into :app dependency graph
- Manual regression testing of the complete onboarding → login → main app flow on a physical low-end device (API 26) and tablet emulator (API 34)
- Verify the ANR fix for CsrfTokenClient.getTokenBlocking() on a Beta build
- Write integration tests covering the full onboarding → auth flow
- Fix any regressions, incorporate mentor feedback, prepare midterm evaluation report
Phase 4 — Contributions Module (Weeks 8–9)
Week 8 (Jul 13 – Jul 19) — :feature:contributions data layer + Paging 3
Goal: Contributions data layer extracted. Paging 2 replaced with Paging 3 + Flow.
- Create :feature:contributions module with data/, domain/, ui/ layers
- Extract ContributionsRepository.kt, ContributionsLocalDataSource.kt, ContributionsRemoteDataSource.kt, ContributionsModule.kt, ContributionsProvidesModule.kt, Contribution.kt, ChunkInfo.kt into feature:contributions/data/
- Replace ContributionBoundaryCallback.kt (Paging 2) with ContributionsPagingSource (Paging 3) — repository emits Flow<PagingData<Contribution>>
- Unit tests: PagingSource load tests for first page, next page, empty result, and error state
Week 9 (Jul 20 – Jul 26) — :feature:contributions ViewModel + Compose UI
Goal: ContributionsListPresenter is gone. Screen is in Compose driven by Paging 3.
- Implement ContributionsViewModel replacing ContributionsListPresenter and ContributionsPresenter
- Migrate ContributionsListFragment and ContributionsFragment to Compose — replace RecyclerView and ContributionsListAdapter with LazyColumn and collectAsLazyPagingItems()
- Discuss with mentors where SetWallpaperWorker, MainActivity, and UnswipableViewPager belong — feature module or :app
- Integration tests against Beta server: list loads, paginates correctly, handles network failure gracefully
Phase 5 — Upload Module (Weeks 10–10.5)
Week 10 (Jul 27 – Aug 2) — :feature:upload data layer + ANR fix
Goal: Upload ANR caused by blocking file I/O fixed. Upload data layer extracted.
- Create :feature:upload module with data/, domain/, ui/ layers
- Extract UploadClient.kt, UploadService.kt, FileProcessor.kt, WikiBaseClient.kt, WikiBaseInterface.kt, SimilarImageInterface.kt, DepictsClient.kt into feature:upload/data/
- ANR Fix: Refactor FileProcessor.save() from a blocking call to a suspend fun running on Dispatchers.IO
- Implement UploadRepositoryImpl implementing UploadRepository from :core:domain
- Unit tests: FileProcessor coroutine dispatch tests — verify save() runs on Dispatchers.IO
Week 10.5 (Aug 3 – Aug 5) — :feature:upload ViewModel + Compose UI
Goal: Multi-step upload flow has a clean state machine. Key upload screens in Compose.
- Implement UploadViewModel managing the full upload state machine with StateFlow: Idle → MediaSelection → Categorization → Metadata → Uploading → Success / Error
- Decouple UploadMediaDetailFragment from UploadMediaDetailPresenter and UploadMediaDetailsContract
- Migrate UploadMediaDetailFragment layout to Compose — replace linear progress indicator with LinearWavyProgressIndicator from M3 alpha (with stable LinearProgressIndicator fallback until M3 Expressive stabilizes)
Phase 6 — Testing, Polish & Final Submission (Weeks 11–12)
Week 11 (Aug 6 – Aug 9) — Full regression testing + bug fixes
Goal: Every migrated module is stable and all edge cases are covered. This week is reserved entirely for testing and fixing — not implementing new features.
- Full manual regression pass — complete onboarding → auth → contributions → upload user flow
- Edge cases: no network, wrong credentials, 2FA flow, upload failure midway, large image files, orientation changes
- Test on low-end physical device (API 26) and tablet emulator (API 34)
- Fix all regressions found during the testing pass
- Remove all @Deprecated facades kept during the transition period
- Remove unused XML layouts and Activities fully replaced by Compose screens
Week 12 (Aug 10 – Aug 16) — Documentation + final submission
Goal: Project is documented, clean, and ready for future contributors to continue.
- Write developer documentation covering module boundaries, how to add a new feature module, how the RxJava → Flow bridge works, and how to run the test suite per module
- Clean up the :app module — remove any remaining redundant files and unused dependencies
- Write the final GSoC project blog post
- Submit the final GSoC evaluation report
Summary Table
| Period | Key Deliverable |
|---|---|
| Community Bonding | Architecture decision doc, module boundaries confirmed |
| Week 1 | Multi-module Gradle builds cleanly |
| Week 2 | All shared infra extracted and tested |
| Week 3 | getTokenBlocking() replaced, repository tested |
| Week 4 | LoginViewModel with full StateFlow coverage |
| Week 5 | Login & Signup fully in Compose |
| Week 6 | Onboarding in Compose with adaptive layouts |
| Week 7 — MIDTERM | auth + onboarding shipped, integrated, tested |
| Week 8 | Paging 2 replaced, contributions repository tested |
| Week 9 | LazyColumn + Paging 3 live |
| Week 10 | FileProcessor.save() on Dispatchers.IO |
| Week 10.5 | Upload state machine + Compose UI |
| Week 11 | All modules stable, edge cases covered |
| Week 12 | Project submitted and documented |
Participation
- Communication: I will post weekly progress updates on the relevant Phabricator task and GitHub issues. I will reach out to mentors proactively via IRC or email whenever I am blocked — not at the end of the week.
- Source code: All work will be submitted as individual, reviewable PRs to the main Commons Android GitHub repository. No single PR will contain multiple concerns.
- Timezone overlap: My PKT timezone (UTC +5:00) gives me strong daily overlap with my mentors' IST timezone (UTC +5:30) for real-time discussions and quick review cycles.
About Me
I am Muhammad Shoaib Khalid, a final-year Software Engineering student at Islamia University of Bahawalpur, Pakistan. I have been building native Android applications professionally for 3 years, progressing through Java+XML → Kotlin+XML → and for the past 1.5 years, exclusively Kotlin + Jetpack Compose in production environments.
My most significant project is OpticTool — a client-commissioned optical business management application for an eye specialist. The app handles prescription generation with near/far vision calculations, billing, stock management, and CameraX-based image capture with EXIF metadata handling. The tech stack is Kotlin, Jetpack Compose, Dagger Hilt, Room, Supabase, CameraX, Coil, and Clean Architecture with MVVM. This is not a tutorial project — it is built to real-world client specifications and is currently awaiting Play Store deployment.
Beyond Android, I have solved 180+ problems on LeetCode with 17.5k+ views on my solutions, which reflects my approach to writing efficient, well-reasoned code.
What makes my background directly relevant to this project is that I have lived the full Android migration journey personally. I understand both the old world — XML layouts, MVP, Dagger 2, RxJava — and the modern world — Compose, MVVM, Hilt, Coroutines, Flow — from hands-on production experience.
Past Experience
Merged Contributions to Commons Android:
| PR | Title |
|---|---|
| #6756 — Merged | Fix: FAB buttons shrinking and opening vertically in landscape mode on Contributions screen |
| #6749 — Merged | Fix: Back button unresponsive after closing More bottom sheet on Bookmarks screen |
| #6742 — Merged | Fix: Snackbar overlapping bottom navigation bar on Explore screen |
I also left a code review on PR #6739 showing my understanding of the location handling architecture in
the upload flow.
These contributions are directly relevant to this project. PR #6756 involved ContributionsListFragment.kt — a core file in the proposed :feature:contributions module. PR #6749 involved MainActivity.kt — the central activity that will be refactored as part of the navigation restructuring. PR #6742 involved BasePagingFragment.kt — part of the Explore paging architecture.
I want to acknowledge that my PR count is small and some fixes are only a few lines of code. But as the GSoC guide states, quality matters more than quantity.
When I started contributing, I looked for good first issues as recommended — but most were either already assigned or had unclear descriptions. So instead of waiting, I started using the app myself to find real bugs. The three PRs I submitted are not manufactured contributions — they fix actual problems that real users experience.
After those fixes, I made a deliberate choice. Rather than chasing more small bugs to pad my contribution count, I shifted my focus to deeply studying the codebase, reading the official Android architecture guides, and preparing a proposal that I actually understand end to end.
Any Other Info
Education: BS Software Engineering (Final Year) — Islamia University of Bahawalpur
How I heard about this program: Through the Wikimedia Commons Android GitHub repository while exploring open source Android projects.
Other time commitments: I am fully available for the entire GSoC coding period with no internships, academic conflicts, or other commitments. I will dedicate a minimum of 45 hours per week.
What does making this project happen mean to me?
Wikimedia Commons is one of the most important digital public goods in the world. Making the app that contributes to it easier to build, easier to maintain, and better to use for the people who carry cameras into communities and upload freely licensed knowledge — that is meaningful work. This project is not a resume line for me. It is the kind of architecture work I genuinely enjoy, on a codebase that actually matters.
Post-GSoC Commitment
GSoC is not the end of my involvement with this project — it is the beginning.
The modularization and Compose migration work done during GSoC will leave the codebase
in a significantly better state, but there will always be more to do. I plan to stay
actively involved after the program ends:
- Continuing contributions — I will keep submitting PRs for bug fixes, performance improvements, and feature work — particularly in the modules I built during GSoC, where I will have the deepest context.
- Helping new contributors — One of the goals of this project is to lower the barrier for new contributors. Once the modular structure is in place, I want to help newcomers navigate it — reviewing PRs, answering questions, and writing clear documentation so the work we did during GSoC actually benefits the next person who joins.
- Completing remaining modules — Profile, About, Notifications, and Review were deprioritized due to the 350-hour constraint. I intend to continue migrating these after GSoC on my own time.
- Maintaining what I built — I take ownership of the code I write. If something I built during GSoC causes a regression or needs updating as the Android ecosystem evolves, I will be the first to fix it.
This project matters to me beyond the summer. I want to see it through.