Page MenuHomePhabricator

GSoC 2026: Modularization + Jetpack Compose in Commons Android App
Closed, DeclinedPublic

Assigned To
Authored By
Roniscend
Mar 22 2026, 9:40 PM
Referenced Files
F73863433: image.png
Mar 27 2026, 10:38 PM
F73775520: b.png
Mar 26 2026, 11:46 PM
F73770192: b.png
Mar 26 2026, 9:34 PM
F73770184: a.png
Mar 26 2026, 9:34 PM
F73438829: Hilt].png
Mar 22 2026, 9:52 PM
F73438062: Timeline.png
Mar 22 2026, 9:40 PM
F73438057: Dagger.png
Mar 22 2026, 9:40 PM
F73438051: 69f6a3c9-7cbc-4790-9497-41b58cc99601.png
Mar 22 2026, 9:40 PM

Description

Modularization + Jetpack Compose in Commons Android App

Contributor Name: Owm Dubey
Contributor Email: owmdubey163@gmail.com
Contributor Github: Roniscend
Location : India (UTC +5:30)
Expected Project Size: 350 hours

Mentors:


1. About Me

I am a sophomore pursuing a B.E. in Computer Science Engineering in Bangalore, India. I have a strong interest in Android Development and Open Source Contribution. My focus is on building scalable and well-architected mobile applications using Kotlin/Java and the latest Android Development Technologies.

I have a strong reason to be a part of the Modularization and Jetpack Compose Initiative because the application's legacy MVP architecture is becoming increasingly difficult to scale and maintain. The application needs a modernization effort in its architecture from the legacy MVP architecture to the latest and greatest MVVM architecture and, more specifically, converting the application's XML-based view hierarchy to the latest Jetpack Compose UI.

The Modularization and Jetpack Compose Initiative is a great move in the direction of making the Wikimedia Commons application more scalable and efficient. I would love the opportunity to contribute to this project and work on making the application's architecture more scalable and efficient by identifying the architectural challenges in the application's legacy MVP architecture and migrating the application's XML-based UI to the latest Jetpack Compose UI.

2. Past Experiences

Over the past few months I have been actively contributing the Commons Application Project. At the time of writing this proposal, I have a total of 30 PR's with 14 merged and 16 currently open and a total of 25 Issues Created.

Beyond open-source, I've also worked as an Android developer Intern at LettrBlack, where I have architected an Android application using MVVM and Clean Architecture. I implemented the entire data layer including Repository patterns and Room persistence while ensuring a reactive UI layer with StateFlow.

After the submission of this proposal, I plan on continuing to contribute to the Commons
Android application. An updated list of all my pull requests, issues, and discussions can be
found in the following GitHub Gist, which I will keep on updating.
Link : https://gist.github.com/Roniscend/b080c4ef0632d9fa25f6a47fb6cccd11

2.1 Notable Projects

  • SafarLink : A unified platform that compares ride and delivery services in real time and simplifies booking through intelligent deep linking and automated cross-app navigation
  • Atkit : Atkit is Kotlin based Android camera app built with MVVM and Jetpack Compose featuring CameraX integration,smart search , adaptive theming,and efficient Session- based photo management for oral health imaging
  • Survey : A modern Android survey application developed using Kotlin and Jetpack Compose,implementing MVVM architecture for efficient collection management and persistence of user responses through a scalable and intuitive UI.
  • Moment.ly : A feature rich Android social media app using Firebase Authentication and Firestore with a dynamic scrolling feed and declarative UI in Jetpack Compose

3. Prior contributions

RepositoryPull RequestIssue SolvedDescriptionStatus
commons-app/apps-android-commons#6663#6662Fix password toggle reset on rotation.Merged
commons-app/apps-android-commons#6681#6678Persist 2FA visibility across configuration changesMerged
commons-app/apps-android-commons#6665#6650Fix SVG decoding in upload flowOpen
commons-app/apps-android-commons#6669#6666Resolve login password toggle desync issueOpen
commons-app/apps-android-commons#6679#6674Enhance character rendering in Nearby listOpen
commons-app/apps-android-commons#6685#6676Handle configuration changes in Place selectorOpen
commons-app/apps-android-commons#6682#6677Improve login network error user feedbackOpen
commons-app/apps-android-commons#6737#6699Fixed FAB layout and distortion issuesMerged
commons-app/apps-android-commons#6753#6700Validated fragment behavior on configuration changesOpen
commons-app/apps-android-commons#6753#6700Fixed BookmarkFragment UI on rotationMerged
commons-app/apps-android-commons#6796#6788Removed overlapping header in report screenMerged
commons-app/apps-android-commons#6794#6790Fixed OpenStreetMap link handling in Explore screenMerged
commons-app/apps-android-commons#6800#6784Fixed tab layout by making it scrollable to prevent text wrappingMerged
commons-app/apps-android-commons#6793#6782Made tabs scrollable to fix text wrapping issueOpen
commons-app/apps-android-commons#6795#6789Restored bottom sheet expand behavior in landscape modeOpen
commons-app/apps-android-commons#6799#6783Prevented dialog from reappearing after dismissal on rotationOpen
commons-app/apps-android-commons#6801#6786Fixed Nearby screen layout issues in landscape modeOpen
commons-app/apps-android-commons#6802#6787Fixed missing info icon in peer review screenOpen
commons-app/apps-android-commons#6807#6629Migrated WelcomeActivity to Compose with responsive UI and dark themeOpen
commons-app/apps-android-commons#6740#6701Verified keyboard visibility issue fixed in landscapeOpen
organicmaps/organicmaps#11983#11938Fix SplashActivity navigation race condition crashMerged
organicmaps/organicmaps#12008#10835Preserve native debug symbols for readable crashesMerged
organicmaps/organicmaps#12032Pass specific login error codes to Android layerMerged
organicmaps/organicmaps#12079#11945Optimize layout updates during window inset changesMerged
organicmaps/organicmaps#12098#12091Ensure legacy Android 5 icon compatibilityMerged
organicmaps/organicmaps#12000#11424Migrate navbar to AndroidX Edge-to-Edge APIMerged
organicmaps/organicmaps#12006#11668Persist login error dialog across configuration changesApproved
organicmaps/organicmaps#12140#12132Fix OpenStreetMap search URL parsingOpen
organicmaps/organicmaps#12077#12044Refactor opening hours layout for readabilityOpen
commons-app/apps-android-commons#6803#6629Migrated About screen to Compose for better UI consistencyOpen

4. Commitments during GSoC

I have no prior commitments during the GSoC period and I'll be able to work 35-45 hours per week solely on completing this project.


Project Idea

5. Abstract

This project proposes a comprehensive architectural revamp of the Android application to resolve technical debt and mitigate the risks associated with its outdated, tightly coupled codebase. Currently, the legacy architecture bottlenecks feature development, complicates testing and creates a high barrier to entry for new contributors.

The core objective of this proposal is twofold: achieving complete modularization of the app and migrating at least 40% of the legacy XML UI to Jetpack Compose.

By dismantling the monolithic structure into highly cohesive, feature based modules following strict Clean Architecture principles, the project will drastically improve separation of concerns, gradle build times, and isolated testability. Concurrently, migrating key user flows to Jetpack Compose integrated with the industry standard Navigation 3 API will establish a Unidirectional Data Flow(UDF) using a modern MVVM pattern and reactive state observation.

The modernization effort will directly benefit end-users by delivering a highly stable, crash resilient app with a refreshed interface. Furthermore, it ensures long term scalability by providing volunteer contributors with a clean, standard compliant codebase that significantly accelerates onboarding and future development.

6. Implementation Details

6.1 Dependency Injection

Current StateTarget State
legacy Dagger 2 with AndroidInjectionModule and dagger-androidHilt modern DI

Problem Areas:

  • Uses dagger android deprecated with AndroidInjector, DispatchingAndroidInjector, HasSupportFragmentInjector
  • Complex Dagger setup with ApplicationlessInjection, CommonsDaggerAppCompatActivity, CommonDaggerSupportFragment
  • Manual component builders ActivityBuilderModule, FragmentBuilderModule requiring every Activity/Fragment to be registered.

Files to update(eg.): CommonsApplicationComponent.kt

@Component(
    modules = [
        CommonsApplicationModule::class,
        NetworkingModule::class,
        AndroidInjectionModule::class,
        AndroidSupportInjectionModule::class,
        ActivityBuilderModule::class,
        FragmentBuilderModule::class,
        // ...
    ]
)

Migration Path: The migration from legacy Dagger 2 to Hilt focuses on replacing manual component management with Hilt's standard annotations. By annotating the Application class with @HiltAndroidApp and UI components with @AndroidEntryPoint, you eliminate the need for ActivityBuilderModule, FragmentBuilderModule, and custom base classes like CommonsDaggerAppCompatActivity. This transition simplifies the dependency graph, removes significant boilerplate code, and enables the use of @HiltViewModel for modern architecture.

Dagger The Tangled Web

Dagger.png (1,553×821 px, 284 KB)


Hilt The Magic Annotation

Hilt].png (1,612×790 px, 217 KB)

FeatureLegacy StateTarget State
App SetupManual @Component & AndroidInjectionModule@HiltAndroidApp
UI Injection@ContributesAndroidInjector in modules@AndroidEntryPoint
Base ClassesDaggerAppCompatActivity/ FragmentStandard AppCompatActivity/Fragment
ViewModelsManual Factory/Provider injection@HiltViewModel

6.2 Architecture Pattern

Current StateTarget State
MVP(Model View Presenter)MVVM(Model View ViewModel + Stateflow)

The migration to MVVM focuses on the complete elimination of the legacy MVP infrastructure, specifically removing the BasePresenter.kt interface and all associated Contract.kt files such as ContributionsContract.kt, ContributionsListContract.kt, and PagingContract.kt.

By removing these contracts and over 30+ Presenter files, we are transitioning from an imperative, interface-driven architecture to a modern reactive approach. This allows us to move logic into lifecycle-aware ViewModels that utilize StateFlow to emit updates. This shift effectively eliminates the high boilerplate of the contract based pattern and ensures the application handles configuration changes natively via viewModel.

When a configuration change triggers the recreation of the UI, The new Activity requests ViewModel via ViewModelProvider. Because Android framework preserves the ViewModelStore across these lifecycle events, ViewModelProvider retrieves the exact same ViewModel instance rather than instantiating a new one. ViewModelScope remains active and StateFlow retains its latest state. The newly initialized UI reconnects to the existing flow, ensuring a seamless transition without data loss.

FeatureMVPMVVM
State handlingManual updates via View InterfacesReactive streams using State Flow
LifecyclePresenters must be manually clearedViewmodels are lifecycle aware
LogicTight coupling(Presenter & View)Decoupled (Viewmodel emits View observes)
BoilerplateMore(Contract Interfaces for every screen)Less

Example:
Legacy Pattern PagingContract.kt

interface PagingContract {
    interface View<T> {
        fun showSnackbar()
        fun observePagingResults (searchResults: LiveData<...>)
    }
    interface Presenter<T>: BasePresenter<View<T>> {
        //
    }
}

Updated Code:

  1. ViewModel
@HiltViewModel
class PagingViewModel @Inject constructor(
    private val repository: Paging Repository
): ViewModel() {
    private val _uiState = MutableStateFlow<PagingState> (PagingState.Idle)
    val uiState: StateFlow<PagingState> = _uiState.asStateFlow()
    
    val pagingResults: Flow<PagingData<T>> = repository.getResults()
        .cachedIn(viewModelScope)
        
    fun handleAction() {
        // Implementation
    }
}
  1. UI State
sealed class PagingState {
    object Idle: PagingState()
    object Loading: PagingState()
    data class Error (val message: String): PagingState()
}
  1. View
@AndroidEntryPoint
class PagingFragment: Fragment (R.layout.fragment_paging) {
    private val viewModel: PagingViewModel by viewModels()
    
    override fun onViewCreated (view: View, savedInstanceState: Bundle?) {
        super.onViewCreated (view, savedInstanceState)
        
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle (Lifecycle.State.STARTED) {
                viewModel.pagingResults.collectLatest { pagedData ->
                    adapter.submitData (pagedData)
                }
            }
        }
        
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle (Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    when (state) {
                        is PagingState. Error -> {
                            Snackbar.make(view, state.message, Snackbar.LENGTH_LONG).show()
                        }
                        PagingState. Loading -> {
                            // Handle loading state
                        }
                        else -> { }
                    }
                }
            }
        }
    }
}

6.3 Reactive Programming

Current StatusTarget Status
RxJava2(Observable, Single, Competable, CompositeDisposable)Kotlin Coroutines + Flow

A critical priority of this modernization is overhauling the asynchronous execution model across 40+ legacy files, transitioning from RxJava2 (Observable, Single, Completable) to Kotlin Coroutines and Flow. The codebase currently relies on manual thread scheduling (Schedulers.io(), AndroidSchedulers.mainThread()) and error-prone lifecycle management via CompositeDisposable.

By adopting structured concurrency, we will replace complex RxJava chains and outdated callback interfaces (e.g., LoginCallback in LoginClient.kt) with sequential, readable suspend functions. This shift eliminates the need for manual disposal management; instead, viewModelScope and lifecycleScope will automatically handle coroutine cancellation when the UI is destroyed, drastically reducing boilerplate and the risk of memory leaks. Finally, background processing will be unified by migrating any remaining RxWorker implementations to native CoroutineWorker classes.

Migration AreaCurrentTargetAffected files
Lifecycle ManagementComposite DisposableViewModelScope40+ Presenter files(eg. ContributionsListPresenter.kt)
Thread SwitchingSchedulers.io(), AndroidSchedulers.mainThread()Dispatchers.IO, Dispatchers.Main ,withContext()Presenters, ViewModels, Repository layers
Network return TypesObservable<T>,Single<T>, CompletableSuspend functions or Flow<T>(streams)All Retrofit API interface
Data OperatorssubscribeOn(), observeOn(), flatMap()Standard Kotlin Flow operators(map, flatMapLatest)Repository and Data layers

Example: ContributionsListPresenter.kt

RxJava2

@Inject
@Named (CommonsApplicationModule.IO_THREAD)
private val ioThreadScheduler: Scheduler
private val composite Disposable = Composite Disposable()

fun loadContributions() {
    composite Disposable.add(
        repository.getContributions()
            .subscribeOn(ioThreadScheduler)
            .observeOn (Android Schedulers.mainThread())
            .subscribe({ result ->
                // Handle result
            }, { error ->
                // Handle error
            })
    )
}

Coroutines

@Inject
@IoDispatcher
private val ioDispatcher: CoroutineDispatcher

fun loadContributions() {
    viewModelScope.launch(ioDispatcher) {
        try {
            val result = repository.getContributions()
        } catch (e: Exception) {
            // Handle error
        }
    }
}

Currently, the codebase hardcodes Dispatchers.IO across multiple files such as UploadWorker.kt, DepictsDao.kt, NearbyParentFragmentPresenter.kt, OkHttpJsonApiClient.kt, ZoomableActivity.kt, CustomSelectorActivity.kt, and ImageAdapter.kt. This leads to three key issues:

a)Untestable coroutines: Hardcoding Dispatchers.IO prevents tests from controlling execution. Coroutines run on real IO threads, making tests nondeterministic, flaky, and slow. Injecting @IoDispatcher allows replacing it with TestCoroutineDispatcher or StandardTestDispatcher for deterministic testing.

b)Violation of Dependency Inversion Principle: Classes directly depend on a concrete dispatcher instead of an abstraction. With @IoDispatcher, the dependency is inverted and provided by Hilt, keeping classes decoupled from implementation details.

c)Lack of architectural consistency: In a modular setup like :core:domain and :core:data, dispatchers should be injected via constructors. This centralizes concurrency control and avoids widespread changes if strategies evolve.

6.4 Navigation

To modernize the application's routing we will migrate from manual fragment transactions to the industry standard Android Navigation 3 API. Currently the MainActivity relies heavily on boilerplate code, using FragmentTransaction.replace() and custom enums(NavTab, NavTabLoggedOut) to manage bottom tab states. By adopting this modern approach, we will define all application destinations and routing logic within a centralized Navigation Graph.

A NavController will then be utilized within a NavHostFragment to trigger transitions and manage the back stack automatically. This eliminates the need for manual intent - based Activity routing, ensures type safe argument passing, and significantly reduces the complexity of handling fragment lifecycles.

MigrationCurrentTarget
Routing MechanismManual (FragmentTransactions.replace())Centralized Navigation Graph & NavController
Tab ManagementCustom NavTab & NavTabLoggedOut enumsBottomNavigationView.setupWithNavController()
Data PassingManual Bundle creation and Intent extrasType safe Kotlin DSL routing

6.5 UI Layer

The UI layer is currently in a transitional state , relying on over 100+ XML layout files and ViewBinding , with only partial Jetpack Compose adoption in specific areas like SingleWebViewActivity and MediaDetailFragment. The primary objective for this phase is to achieve a minimum of 40% Jetpack Compose coverage across the application's UI.

We will systematically replace high priority legacy XML layouts and ViewBinding implementations with modern @Composable functions. Since the application will utilize the standard Android Navigation 3 API , the migrated Compose screens will be integrated as Fragment destinations using ComposeView. This approach guarantees smooth interoperability between the newly built Compose UI.

To guarantee smooth performance for the UI after migrating to Compose, Baseline Profiles will be used to pre-compile the new UI components and prevent frame drops on the first launch.

Migration AreaCurrent StateTarget State
UI Framework100+ XML layouts + viewbindingMinimum 40% Jetpack Compose coverage
Custom ViewsLegacy views like NavtabLayoutCompose Material 3 Adaptive library
Screen HostingStandard XML inflated Fragments@Composable functions hosted via ComposeView

Priority 1: Screens That Already Use Compose
The codebase already has partial Compose adoption. These screens should be completed first since the infrastructure is already in place:

ScreenCurrent StateRationale
WelcomeScreen.ktAlready fully Compose (@Composable pages: WelcomeDoUploadPage, WelcomeDontUploadPage, etc.)Already done. Only needs cleanup and theme token alignment with :core:ui.
BookmarkCategoriesFragment.ktAlready uses ComposeViewAlready hybrid. Extend to cover the remaining bookmark tabs.
MediaDetailFragment.ktPartially Compose (fileUsagesComposeView)Already hosts a ComposeView. Incrementally migrate remaining XML sections of this fragment.
SingleWebViewActivity.ktAlready @ComposableAlmost done

Priority 2: High-Value, Low-Complexity Migrations
These screens are state-heavy but structurally simple, making them ideal Compose candidates:

ScreenFilesWhy Migrate?
Auth Flow (LoginActivity.kt, SignupActivity.kt)6 filesHighly state-dependent (Loading -> Error -> 2FA -> Success). Compose's declarative when(state) pattern is a massive upgrade over the current imperative XML toggle logic. Low user traffic = low regression risk.
Profile / Achievements (ProfileActivity.kt)1 file + 2 subdirs (achievements/, leaderboard/)Simple data-display screens. LazyColumn for leaderboard lists is a direct, clean replacement for RecyclerView.
Notifications (NotificationActivity.kt)8 filesA straightforward list screen. Perfect candidate for LazyColumn + StateFlow.
Settings (SettingsActivity.kt, SettingsFragment.kt)3 filesSettingsFragment is large but is mostly a PreferenceFragmentCompat. Can be incrementally wrapped in ComposeView for custom preference items.
About (AboutActivity.kt)1 fileStatic content screen. Trivial to port.

Priority 3: High-Impact, Medium-Complexity Migrations
These are the core user-facing screens where Compose delivers the biggest performance and UX gains:

ScreenFilesWhy Migrate?
Contributions List (ContributionsListFragment.kt, ContributionsFragment.kt)22 files totalThe most visible screen in the app. Currently uses legacy RecyclerView + Fresco SimpleDraweeView for image loading. Migrating to Compose LazyColumn + Paging 3 collectAsLazyPagingItems() + Coil AsyncImage will dramatically improve scroll performance and drop legacy layout inflation entirely.
Explore / Search (ExploreFragment.kt, SearchActivity.kt)6 files + 7 subdirsPaging-heavy search results. Same LazyColumn + Paging 3 benefits as Contributions.

Which stable screens should be kept as XML (Hybrid Approach)?
These screens are massive, deeply intertwined, or Java-heavy. Rewriting them in Compose during GSoC introduces unacceptable regression risk. They will be modularized to MVVM but will strictly retain their XML ViewBinding:

ScreenFilesWhy Keep as XML?
Upload Wizard (UploadActivity.kt)43 files + 6 subdirsThis is the single most critical feature in the app it's how users contribute media to Wikimedia Commons. With deeply coupled MVP presenters (UploadPresenter.kt, UploadContract.kt), FragmentStatePagerAdapter, multi-step wizard logic, and complex Camera/Location/EXIF integrations, rewriting this to Compose is extremely high risk. It will be extracted to :feature:upload and migrated to MVVM, but the XML stays.
Nearby Map (NearbyController.java, NearbyParentFragment)19 files + 4 subdirsStill primarily written in Java (not even Kotlin). Tightly coupled to the native Osmdroid C++ map rendering engine. Compose has no mature Osmdroid wrapper. Rewriting this means rewriting the entire map interaction layer. Extract to :feature:nearby, convert Java -> Kotlin, migrate to MVVM, but keep XML.
Review (ReviewActivity.kt, ReviewImageFragment.kt)9 filesA mature, stable peer-review flow with custom ViewPager swiping logic. Low user complaints, no architectural benefit from Compose migration. Keep as XML.
Custom Selector (CustomSelectorActivity.kt)-Already uses @Composable partially Hybrid state. Keep the existing XML shell and expand Compose incrementally.
Location Picker (LocationPickerActivity.kt)-Tightly bound to Mapbox/Osmdroid native views. Cannot be ported to Compose without a compatible map composable. Keep as XML.

6.6 State Management

Current StateTarget State
LiveData+RxJava+CallbacksStateFlow + Shared Flow Kotlin Flow

The application's current state management is fragmented , relying on a mixture of LiveData, RxJava observables and manual callback interfaces to push updates to the UI via the standard observer pattern. To unify the architecture and fully support the planned Jetpack Compose migration, we will transition entirely to Kotlin's StateFlow and SharedFlow. This modernization replaces lifecycle bound LiveData with reactive Flowstreams which are inherently more flexible and integrate seamlessly with Compose via collectAsState().

Furthermore , tightly coupled callback interfaces will be eliminated in favor of SharedFlow to handle one time UI events such as Snackbars,toast messages , or navigation triggers safely and efficiently without holding duplicate state.

MigrationCurrentTarget
UI State HoldingLiveData & Mutable Live DataStateFlow & MutableState Flow
One Time EventsManual Callback interfaceShared Flow
Compose integrationobserveAsState() or manual wrappersNative collectAsState()

6.7 Background Work & Threading

The application's background work and threading are currently managed through a mix of legacy systems, including RxJava Schedulers, a custom ThreadPoolService.kt for thread pool management, and remnants of deprecated AsyncTask implementations. To standardize asynchronous operations and ensure resource safety, we will migrate entirely to Kotlin Coroutines.

By adopting Coroutines, we will leverage structured concurrency to tie background work directly to the lifecycle of the UI components via viewModelScope and lifecycleScope. This eliminates the need for manual thread management and custom services, guaranteeing that ongoing tasks are automatically canceled when the associated screen is destroyed thereby preventing memory leaks and application crashes.

MigrationCurrent StateTarget State
Concurrency FrameworkRxJava + Custom Thread Pools + AsyncTaskKotlin Coroutines
Thread SwitchingSchedulers.io(),AndroidSchedulers.mainThread()Dispatchers.IO, Dispatchers.Main
Lifecycle SafetyManual cancellationStructured Concurrency (viewmodelScope)
Custom ServicesThreadPoolService.ktNative Coroutine Dispatchers

6.8 Image Loading

The application currently relies on Facebook's Fresco library for image loading, which requires heavy, explicit initialization in CommonsApplication.kt and the use of custom SimpleaDraweeView components within XML layouts. To align with our Kotlin first architecture and the ongoing Jetpack Compose migration, we will replace Fresco with Coil (Coroutine Image Loader).

The migration process involves completely removing Com.facebook.drawee.backends.pipeline. Fresco and its Image PipelineConfig from the application class, taking advantage of Coil's lightweight, automatic initialization. In the UI layer, legacy SimpleDraweeView XML tags will be replaced with standard ImageView components utilizing Coil's load() extension function, while newly built Compose screens will natively adopt Coil's AsyncImage composable. Finally all Fresco artifacts will be stripped from the build.gradle dependencies, which will significantly reduce the final APK size as Coil is a very lightweight library.

6.9 Pagination

The application currently utilizes the legacy Paging 2 library, relying on DataSource, PagedList, and LivePagedListBuilder to handle data pagination in classes such as ContributionsListPresenter.kt. To modernize the data layer and ensure seamless compatibility with Jetpack Compose, we will migrate to the Paging 3 library.

This transition involves replacing the legacy DataSource components with PagingSource and swapping the LivePagedListBuilder for the modern Pager API, which emits paginated data as a continuous Kotlin Flow<PagingData>. This upgrade not only provides robust error handling and loading state management natively but also enables direct integration with Compose lists such as LazyColumn and LazyVerticalGrid using the paging compose artifact.

To modernize the data layer and fully support Jetpack Compose, we will migrate from Paging 2 to Paging 3 targeting legacy files like Contributions List Presenter.kt. The outdated DataSource and LivePagedListBuilder components will be replaced with the modern PagingSource and Pager APIs. Instead of exposing a LiveData stream of PagedList objects, the new architecture will emit a continuous Kotlin Flow<PagingData>. This Coroutine native approach integrates seamlessly with the updated architecture and allows Compose UI components such as LazyColumn and LazyVerticalGrid to efficiently consume and display paginated data using the paging compose library.

6.10 Network Layer

To completely modernize the network layer targeting legacy files like LoginInterface.kt, we will transition from Retrofit with RxJava callbacks to Coroutine based suspend functions, eliminating callback hell and ensuring type-safe and efficient data parsing.

To guarantee robust network execution, inconsistent try-catch blocks will be standardized globally using a custom Retrofit CallAdapter, and manual retry logic will be centralized into an OkHttp RetryInterceptor, alongside new certificate pinning for enhanced security.

Finally, we will establish a true offline first architecture by abstracting raw Retrofit calls behind a Repository pattern emitting Kotlin Flow, utilize a Flow-based NetworkBoundResource rather than relying solely on the basic OkHttp cache.

Legacy API Pattern:
LoginInterface.kt

interface LoginInterface {
    @GET("?format=json&formatversion=2&errorformat=plaintext")
    @Headers("Cache-Control: no-cache")
    fun getLogin Token(): Call<MwQueryResponse?>
}

Legacy RxJava Network Pattern:
SessionManager.kt

fun logout(): Completable = Completable.fromObservable(
    Observable.empty<Any>()
        .doOnComplete {
            removeAccount()
            currentAccount = null
        }
)

Modern Network Architecture:

interface LoginApi {
    @GET("login")
    suspend fun login (@Body request: LoginRequest): Result<LoginResponse>
}

class LoginRepository @Inject constructor(
    private val api: LoginApi,
    private val db: UserDao
) {
    // The CallAdapter makes api.login return a Result automatically
    fun login(request: LoginRequest): Flow<Result<User>> = flow {
        emit(Result.Loading)
        val result = api.login(request)
        if (result is Result. Success) {
            db.saveUser(result.data.toEntity())
        }
        emit(result)
    }
}

6.11 Data Storage

To completely overhaul the storage layer, we will replace legacy SharedPreferences (BasicKvStore.kt) and the Gson based JsonKvStore with Jetpack DataStore (Preferences and Proto) for type safe key value management.

For structured data, we will finalize a migration to Room, fully deprecating both the legacy SQLiteOpenHelper (DBOpenHelper.java) and the custom ContentProviders used for bookmarks in favor of a standard Room and Repository pattern.

MigrationCurrentTarget
Relational DatabaseRoomRoom(Retained, updated for Coroutines/Flow)
Key-Value PreferencesCustom JsonKvStoreJetpack DataStore
Execution ContextSynchronous / Custom ThreadsAsynchronous (Coroutines & Flow)

6.12 Testing

To ensure long term stability, the legacy testing infrastructure will undergo a comprehensive modernization. We will migrate the unit test stack from JUnit 4 and PowerMock to a modern setup utilizing JUnit 5, MockK, and either Turbine or kotlinx-coroutines-test, as Turbine is third party library and older but still google recommends it for precise Coroutine Flow validation.

For UI and integration testing, the custom Dagger mock setup will be replaced by HiltTestApplication, transitioning to a hybrid of Compose Testing and Espresso. Finally, to prevent future UI and edge case regressions, we will introduce Compose Screenshot Testing tool for automated screenshot testing alongside Kotlin Test for property based coverage.

Legacy Test Pattern:
TestCommonsApplication.kt

class TestCommonsApplication: Application() {
    private var mockApplicationComponent: CommonsApplication Component? = null
    // Complex manual mock setup
}

class MockCommonsApplicationModule (appContext: Context): CommonsApplicationModule (appContext) {
    // mock
}

val defaultSharedPreferences: JsonKvStore = mock() // Mockito

Legacy UI Test Pattern:
MainActivityTest.kt

@RunWith(AndroidJUnit4::class) // OLD API
class MainActivityTest {
    @get: Rule
    var activityRule: ActivityTestRule<LoginActivity> = ActivityTestRule (LoginActivity::class.java) // Deprecated
    
    // Manual login helpers
    private lateinit var defaultKvStore: JsonKvStore
}

Compose UI Test

@HiltAndroidTest
class MainScreenTest {
    @get: Rule
    val hiltRule = HiltAndroidRule(this)
    
    @get: Rule
    val composeTestRule = createAndroidComposeRule<MainActivity>()
    
    @Test
    fun bottomNavigation_switches Tabs() {
        composeTestRule
            .onNodeWithText("Contributions")
            .performClick()
            
        composeTestRule
            .onNodeWithTag("contributions_list")
            .assertIsDisplayed()
    }
}

Modern Test Architecture

@ExtendWith(MockKExtension::class)
class LoginViewModelTest {
    @MockK
    private lateinit var repository: LoginRepository
    private lateinit var viewModel: LoginViewModel
    private val testDispatcher = UnconfinedTestDispatcher()
    
    @BeforeEach
    fun setup() {
        Dispatchers.setMain (testDispatcher)
        viewModel = LoginViewModel (repository)
    }
    
    @Test
    fun `login emits loading then success` () = runTest {
        coEvery { repository.login(any()) } returns flow {
            emit(Result.Success (User("test")))
        }
        
        viewModel.login("user", "pass").test {
            assertEquals (LoginUiState. Loading, awaitItem())
            assertEquals (LoginUiState. Success, awaitItem())
            awaitComplete()
        }
    }
}

6.13 Uncoupling the Monolith

The current architecture of commons-app heavily centralizes logic into the :app module, which drastically impairs scalability and increases build times. For example, UploadActivity.kt currently operates as a massive class that directly @Injects sweeping, cross-domain dependencies like UserClient (Network), JsonKvStore (Database), and LocationServiceManager (Hardware) straight into the Activity layer. Furthermore, ActivityBuilderModule.kt binds over 24 different Activities into one massive global Dagger graph. This architecture exponentially inflates incremental Gradle build times, prevents compilation avoidance, and creates tight coupling across unrelated feature boundaries.

My Implementation Strategy: To permanently resolve this technical debt and modernize the compilation pipeline, I will architect a strict Domain driven multi module directed acyclic graph (DAG) based on the following pillars:

Strict Encapsulation (Vertical Layering): Feature modules (e.g., :feature:upload, :feature:nearby) will enforce Kotlin's internal visibility modifiers for their localized MVVM infrastructure. *UploadActivity will be stripped of its direct Network and Hardware injections. It will instead rely purely on an UploadViewModel** that communicates exclusively through abstract domain interfaces (e.g., UserRepository), entirely blinding the UI to how data is retrieved.

Module-Scoped DI & Horizontal Compilation Avoidance: By replacing the bloated multiple activities ActivityBuilderModule with strictly scoped @HiltAndroidApp component hierarchies, and extracting :core:network (MediaWiki APIs) and :core:database (Room DAOs) into isolated horizontal layer modules, we clear the compilation bottleneck.
Decoupling Transitive Dependencies : Currently, the :app monolith globally bundles massive libraries like Fresco, Glide, RxJava, and Osmdroid. This forces lightweight text-only features to compile heavy C++ map and image rendering libraries. In the new DAG architecture, dependencies will be strictly hidden. For instance, libs.osmdroid.android will exist only inside :feature:nearby using Gradle's implementation keyword (rather than api), physically preventing it from leaking into the broader application. This eliminates class-path collisions and drastically reduces the compiled APK footprint.
Before (The Monolithic Dagger Tangle)

a.png (1,256×1,198 px, 194 KB)


After (True Clean Architecture via Dependency Inversion)

b.png (3,041×1,517 px, 437 KB)

6.14 Proposed Module Structure

To execute this architecture, the monolithic :app folder will be dismantled into the following cohesive Gradle modules:

1. Core Modules (Horizontal Infrastructure): These modules form the backbone of the application and do not depend on any specific UI feature.

  • :core:domain (Pure Kotlin. Holds Entities, Result wrappers, and Repository Interfaces).
  • :core:network (Retrofit, OkHttp, MediaWiki API endpoints).
  • :core:database (Room DAOs, Jetpack DataStore preferences).
  • :core:ui (Jetpack Compose Material 3 tokens, theme, and reusable common components).
  • :core:testing (Hilt test rules, MockK extensions for shared module testing).

2. Feature Modules (Vertical Flows): These modules encapsulate distinct user flows. By using Gradle's implementation keyword, their massive 3rd-party dependencies (like Fresco or Osmdroid) remain physically isolated and cannot leak into the broader application.

  • :feature:auth (Login, 2FA, Signup UI flows).
  • :feature:contributions (The MVVM paged list of user contributions).
  • :feature:explore (Search and categories).
  • :feature:nearby (The osmdroid map rendering UI).
  • :feature:upload (The Upload Wizard flow).

How will multi-modules NOT increase maintenance overhead?

1. Gradle Convention Plugins (build-logic)
The #1 cause of multi-module nightmare is duplicated build.gradle.kts boilerplate. I will create a build-logic/ included build with Kotlin DSL convention plugins:

build-logic/
├── convention/
│ ├── src/main/kotlin/
│ │ ├── commons.android.application.gradle.kts
│ │ ├── commons.android.feature.gradle.kts -> For feature modules
│ │ ├── commons.android.library.gradle.kts -> For core modules
│ │ └── commons.android.compose.gradle.kts -> Compose compiler config

Result: Creating a new module requires exactly one line in its build.gradle.kts:

plugins { id("commons.android.feature") }

All compileSdk, minSdk, Hilt, Compose compiler, and test dependency configurations are inherited automatically. Zero duplication, zero version conflicts.

2. Version Catalog (libs.versions.toml)
The project already uses a Version Catalog. All library versions will be centralized here. Upgrading a library (e.g., Retrofit) means changing one line in libs.versions.toml, and it propagates to every module automatically.

3. :core:domain as the Single Source of Truth
All inter-module communication goes through pure Kotlin interfaces in :core:domain. A feature module cannot directly depend on another feature module. This means:

  • Changing :feature:upload internals cannot break :feature:contributions.
  • A small bug fix in one feature module only recompiles that module, not the entire app.

4. Strict Dependency Rules in settings.gradle.kts
I will enforce dependency direction using Gradle's api vs implementation:

  • Feature modules use implementation for :core:network, :core:database -> changes in data layer do NOT trigger feature recompilation.
  • Only :core:domain is exposed via api -> the public contract never leaks implementation details.

5. CI Enforcement
A simple Gradle task (or a lint rule) can be added to CI that fails the build if any :feature:* module directly depends on another :feature:* module, preventing developers from accidentally introducing circular dependencies.

What reusable components should :core:ui include as subtasks?
A. Design System & Theme Foundation

ComponentStatus / Current ImplementationRationale
CommonsThemestyles.xml, colors.xmlUnified Material 3 theme; replaces BaseActivity theme logic.
CommonsColorSchemeScattered color resourcesCentralized light/dark palette; removes SystemThemeUtils dependency.
CommonsTypographyInconsistent hardcoded sizesStandardized type scale using Google Fonts (Inter/Outfit).
EdgeToEdgeShellEdgeToEdgeUtils.kt (273 lines)Migrates complex window inset and IME (keyboard) handling to Compose WindowInsets.

B. Navigation & Layout Shells

ComponentMigrates FromNotes
CommonsTopAppBartoolbar_binding.xmlSupports dynamic menus found in 15+ Activities.
CommonsBottomNavBarNavTabLayout.ktReplaces custom NavTab enum based fragment switching.
CommonsScaffoldRepeated XML patternsStandardizes layout for primary navigation destinations.
LayoutHelpersLayoutUtils.ktMigrates aspect ratio and dimen helpers to Compose Modifiers.

C. Media & Content Primitives

ComponentMigrates FromNotes
MediaThumbnailContributionViewHolder, GridViewAdapterReplaces Fresco SimpleDraweeView with Coil/Glide.
ZoomableImageZoomableActivity.ktMigrates custom touch/zoom logic to Compose transformable.
HtmlTextHtmlTextView.ktNative Compose implementation using AnnotatedString.
NearbySwipeCardNearbyNotificationCardView.javaMigrates complex SwipeableCardView gesture + compass rotation logic.

D. List & State Management

ComponentMigrates FromNotes
PaginatedListFooterAdapter.kt, BaseViewHolderConsolidates LoadMore/Retry/Loading states for list UIs.
EmptyStateViewScattered View.GONE logicStandardized "No Data" UI found in 20+ fragments.
CommonProgressBarprogressBar in 25+ filesUnified loading indicator for long running operations.

E. Input & Interaction

ComponentMigrates FromNotes
PasteSensitiveFieldPasteSensitiveTextInputEditText.ktRetains plain text paste logic required for security/formatting.
CommonsSearchBarSearchActivity / ExploreMapFragmentStandardized search behavior with animation.
CategoryChipGroupCategoryChip.javaEfficient chip based selection for tags and depicts.

F. Dialogs & Utilities

ComponentMigrates FromNotes
CommonsAlertDialogDialogUtil.kt (7 overloads)Migrates showSafely and custom view dialog wrappers.
MoreBottomSheetMoreBottomSheetFragment.ktReplaces BottomSheetDialogFragment with native ModalBottomSheet.
FeedbackSheetFeedbackDialog.ktRefactors user reporting flow into a modern sheet.
UIEffectHandlersViewUtil.ktMigrates Snackbar/Toast/Keyboard helpers to Compose Scaffold / LocalSoftwareKeyboardController.

Result Wrapper is a sealed interface that safely and exhaustively encapsulates the result of a domain operation without relying on exception based flow control.

sealed interface Result<out T> {
    data class Success<T>(val data: T) : Result<T>
    data class Error(val exception: Throwable, val message: String? = null) : Result<Nothing>
    data object Loading : Result<Nothing>
}

This standardizes error handling by ensuring:

  • Repositories return Result<T> instead of raw values
  • ViewModels handle states exhaustively using when
  • UI receives predictable states for rendering (loading, success, error)

Yes, DDD Use Cases can also be introduced in :core:domain.
Benefits of Use Cases in :core:domain:

  • Single Responsibility: each Use Case does one thing
  • Reusable: multiple ViewModels across feature modules can share the same Use Case
  • Testable: pure Kotlin, no Android dependencies
  • Decoupled: ViewModels don't need to know about repositories directly
  • Domain logic centralization: business rules live in the domain layer, not scattered across ViewModels and Activities

7. Timeline

GSoC Timeline

Timeline.png (1,233×1,026 px, 317 KB)

7.1 Week-Wise Distribution

Community Bonding Period

Phase1: Foundation & Infrastructure

TimeframeDescriptionEst. Hours
Week 1Gradle Hilt Configuration & Core DI Migration45 Hrs
Subtask 1.1Initialize @HiltAndroidApp in CommonsHiltApplication and completely strip legacy Dagger AppComponent initialization and HasSupportFragmentInjector implementations.10 Hrs
Subtask 1.2Refactor ActivityBuilderModule & FragmentBuilderModule. Replace manual AndroidInjection.inject(this) calls with @AndroidEntryPoint and validate legacy ViewModel injections.15 Hrs
Subtask 1.3Resolve DI-related test regressions. Implement @HiltAndroidTest via a newly created TestCommonsApplication and configure a CustomTestRunner for robust Hilt UI testing.20 Hrs
Week 2DataStore & Async Foundations45 Hrs
Subtask 2.1Configure Hilt DataStoreModule and Proto schemas to globally provide DataStore<Preferences>.15 Hrs
Subtask 2.2Migrate standard key-value preferences from legacy BasicKvStore to Jetpack DataStore.15 Hrs
Subtask 2.3Deprecate JsonKvStore and refactor classes like SessionManager to emit Flow<T>, simultaneously establishing and injecting centralized Coroutine infrastructure.15 Hrs
Week 3Core Modularization & RxJava to Coroutines45 Hrs
Subtask 3.1Set up a build-logic module utilizing Gradle Convention Plugins (Version Catalogs) to centralize dependency management across all future modules.15 Hrs
Subtask 3.2Establish foundational core modules (:core:network, :core:database, :core:utils, :core:ui) and migrate the modernized components into these strict boundaries.15 Hrs
Subtask 3.3Implement CommonsCoroutineScope for centralized, lifecycle-aware coroutine management. Migrate ContributionsRepository from RxJava to Kotlin Flow with zero UI layer disruption.15 Hrs

Phase 2: Architecture & Navigation

TimeframeDescriptionEst. Hours
Week 4Hybrid UI Migration: MVP -> MVVM45 Hrs
Subtask 4.1Refactor ContributionsFragment to MVVM. Embed @Composable ContributionsScreen via ComposeView(DisposeOnViewTreeLifecycleDestroyed) and observe UI states using collectAsStateWithLifecycle().15 Hrs
Subtask 4.2Migrate ExploreFragment to MVVM and implement Jetpack Compose interoperability for the UI layer.15 Hrs
Subtask 4.3Execute the complex NearbyParentFragment MVVM migration. Integrate all newly refactored hybrid Fragments into MainActivity.15 Hrs
Week 5Navigation 3 API, Paging 3 Migration & Core UI45 Hrs
Subtask 5.1Migrate ContributionsListPresenter to ContributionsViewModel. Define nav_graph.xml to centralize all UI destinations (ContributionsFragment, NearbyParentFragment, UploadActivity) using the industry standard Navigation 3 API.15 Hrs
Subtask 5.2Migrate legacy Paging 2 DataSource to Paging 3 PagingSource returning isolated Flow<PagingData>. Configure NavHostFragment and initialize the NavController within MainActivity to manage the application’s hybrid backstack.20 Hrs
Subtask 5.3Create the :core:ui module establishing foundational Material 3 Compose tokens (CommonsTypography, CommonsColors). Replace legacy NavTab routing by binding BottomNavigationView directly to the NavController (setupWithNavController). Concurrently implement explicit deep link routing for external media and notifications.10 Hrs
Week 6Network Modernization & Core Feature Extraction45 Hrs
Subtask 6.1Initialize Kotlin Serialization and establish a NetworkResult sealed interface to standardize API response states. Extract the :feature:auth module and verify Gradle Single Responsibility constraints (it cannot access anything but :core:domain).15 Hrs
Subtask 6.2Refactor Retrofit interfaces, replacing RxJava Observable returns with Coroutine suspend functions mapped to Flow in repositories. Extract the :feature:contributions module.15 Hrs
Subtask 6.3Extract the highly stable UploadActivity logic into the :feature:upload module while strictly keeping its XML layout intact, ensuring all modernized network responses route correctly through the new module boundaries.15 Hrs

Phase 3: UI Modernization & Feature Modules

Week 7Core UI Module, MD3 System & Compose Integration45 Hrs
Subtask 7.1Establish an independent :core:ui module to centralize the Jetpack Compose design system. Implement a complete Material Design 3 theme (responsive light/dark schemes, CommonsTypography, CommonsShapes), and embed ComposeView into the ContributionsFragment layout.10 Hrs
Subtask 7.2Build a highly reusable Compose component library utilizing the new MD3 tokens. Rewrite the main contribution list rendering (RecyclerView equivalent) purely in Compose utilizing LazyColumn and collectAsLazyPagingItems().20 Hrs
Subtask 7.3Concurrently migrate legacy image loading to Coil. Strip Fresco XML definitions and implement lightweight Coil AsyncImage composables to deeply optimize the image rendering pipeline.15 Hrs
Week 8Advanced Feature Extraction & Dependency Resolution45 Hrs
Subtask 8.1Extract the Explore screen into a standalone :feature:explore module, leveraging Convention Plugins to enforce a strict dependency graph that relies only on :core:domain (for business logic) and :core:ui (for design system tokens), ensuring the feature remains completely decoupled from data implementation details.15 Hrs
Subtask 8.2Refactor the Nearby map functionality into a standalone :feature:nearby module, establishing a decoupled dependency graph that only uses :core:domain to avoid circular dependencies.15 Hrs
Subtask 8.3Extract the logic into a standalone :feature:contributions module, utilizing Convention Plugins to segregate the legacy logic from the core data layer. Concurrently establish strict inter-module navigation graphs and dependency rules within settings.gradle.kts to prevent circular dependencies.15 Hrs
Week 9Compose UI Migrations: Upload, Media & Nearby45 Hrs
Subtask 9.1Refactor UploadActivity into a Compose-based multi-step wizard. Route the sub-flows strictly utilizing the Android Navigation Component.15 Hrs
Subtask 9.2Migrate the complex, data-dense UI of MediaDetailFragment entirely to Jetpack Compose.15 Hrs
Subtask 9.3Refactor NearbyParentFragment, replacing legacy map implementations with Compose-compatible rendering.15 Hrs
Week 10Remaining Screens & Polish45 Hrs
Subtask 10.1Refactor ProfileActivity to Compose, fully migrating the interactive achievements and leaderboard UI components.25 Hrs
Subtask 10.2Migrate SettingsActivity and AboutActivity to Compose-based screens.10 Hrs
Subtask 10.3Refactor the final remaining screens ReviewActivity and NotificationActivity. Execute a final codebase sweep to systematically delete most of the deprecated XML layouts.10 Hrs
Week 11Modern Testing Stack & Coroutine Unit Testing45 Hrs
Subtask 11.1Setup the modern testing stack: JUnit 5, MockK, and either Turbine or kotlinx-coroutines-test, fully replacing legacy JUnit 4 and Mockito dependencies across the project.20 Hrs
Subtask 11.2Implement ViewModel unit tests utilizing runTest, StandardTestDispatcher, and kotlin-coroutines-test to validate sequential StateFlow emissions.25 Hrs
Week 12End-to-End Testing, Community Feedback & Final Documentation45 Hrs
Subtask 12.1Conduct final end-to-end testing across the entire application to resolve all remaining bugs and ensure absolute stability before the final submission.20 Hrs
Subtask 12.2Conduct a feedback round from early adopters and community members; work on follow-up issues or PRs based on that feedback to ensure production readiness.15 Hrs
Subtask 12.3Prepare the final blog post summarizing the project outcomes, architectural shifts, and future directions for the Wikimedia Commons app. Submit final work product.10 Hrs

Extended Timeline
This section outlines additional work that may be carried out if time permits or remains after
the official GSoC timeline.

To fortify the application's security posture, several critical vulnerabilities within the legacy architecture will be systematically addressed. First, we will eliminate the potentially insecure practice of storing account passwords in AccountManager, transitioning to a token only authentication model secured by Jetpack EncryptedSharedPreferences.

At the network level, OkHttp CertificatePinner will be implemented to prevent Man in the middle (MITM) attacks. To protect the application from reverse engineering, we will remove legacy Don't obfuscate ProGuard rules and fully enable R8 obfuscation. Finally, runtime security will be enhanced by integrating the RootBeer library to detect compromised device environments, and Flag Secure will be applied to the window flags of sensitive screens.

8. What motivates me to work with Wikimedia for GSOC

The Wikimedia Commons Android app is a cornerstone of the open knowledge ecosystem, empowering millions to enrich one of the world's largest freely licensed media repositories. Contributing to this mission through Google Summer of Code is a unique privilege to build software that democratizes information globally.

Beyond its impact, the Commons app is backed by a vibrant engineering community.Collaborating in this environment presents an ideal opportunity to master large scale open source development,adhere to rigorous code quality standards, and collectively maintain software that operates at a massive scale.

The proposed initiative Modularization and Jetpack Compose migration is critical overcoming the technical friction inherent in the app's legacy architecture. Currently, the heavily coupled monolithic structure complicates dependency management, hinders isolated testing, and inflates Gradle build times.

By decoupling the codebase into high cohesive,feature based modules,we can enforce strict architectural boundaries.. This restructuring will cleanly separate the presentation,domain, and data layers,optimizing parallel build execution and paving the way for a more robust Clean Architecture implementation.

Simultaneously, migrating the presentation layer to Jetpack Compose will fundamentally modernize the application's UI paradigm. Transitioning away from imperative XML layouts and state-heavy view controllers to a declarative framework allows us to enforce a Unidirectional Data Flow(UDF).

Crucially,this migration will overhaul the app's routing architecture. By phasing out manual Fragment transactions, we will integrate the modern Android Navigation API.Adopting this industry standard moves the app toward a highly stable single -activity architecture with a centralized NavHost.

This provides robust, type safe argument passing and completely eliminates the lifecycle related crashes and state loss exceptions notoriously associated with complex Fragment backstacks. Furthermore, Compose will allow us to build a reusable, modular design system that guarantees visual consistency across the app.

The downstream impact of these architectural shifts is profound. For the developer community, feature isolation and a standardized navigation component dramatically lower the cognitive load for new contributors and enable highly targeted, reliable unit and UI testing without bringing up the entire application graph.

For the end users, a modernized UI ensures a more performant fluid, and crash resilient experience. I am eager to drive this modernization forward, delivering scalable, maintainable and contributor-friendly platform that strengthens Wikimedia's mission of global collaboration.

9. Career Goals

As an Android developer, my primary goal is to master the Android SDK, multithreading, and related technologies while building a strong portfolio of projects that includes personal applications, open-source contributions, and projects developed through academic or professional work.

I have also explored AOSP to understand the deeper internals of the Android platform and continue to actively contribute to open-source projects as part of my growth as a developer.

As I gain more experience, I aim to strengthen my expertise in UI/UX design alongside enterprise-scale mobile application development and gradually move into leadership roles. Being largely self taught, open source has played a crucial role in my learning journey, and I hope to give back to the community by sharing knowledge through technical blogs, tutorials, speaking engagements, and continued open-source contributions.

10. Previous Participation in GSoC

No, this is my first participation in Google Summer of Code

11. Application to Multiple Orgs

I will be applying only to Wikimedia for this year's GSoC

Event Timeline

Hi @Roniscend,

Thanks for the proposal.

Can you list down week-wise tasks and further break them into sub-tasks along with the estimated hours you will be targeting each week?

Also, the proposal mentions a lot of things to be done. Could you specifically clarify which modules/features you are planning to migrate to Compose? I don’t think migrating 100% of the legacy code is feasible or recommended at this stage. A hybrid approach (XML + Compose) with incremental migration would be more practical. It would be good to know:

  • Which screens/modules you plan to migrate
  • Why those are being prioritized
  • Some screen might be very stable and I don't think it should be part of priority first to migrate rather we can follow hybrid approach there to keep it into XML for now and in later phase we can migrate it grcefully. So my question comes next is which stable screens can be kept as-is for now?

Regarding multi-module architecture, could you explain your approach, why and how you plan to break the current codebase? Also, what factors are you considering for separating modules to ensure Single Responsibility?

Additionally, it would help to understand:

  • Why a particular module (X) should be separate from another (Y)
  • What feature modules you are planning to introduce
  • How you will ensure that introducing multi-modules does not lead to unnecessary maintenance overhead, like even for a small change the complexity won't increase.

Looking forward to your clarification.

Thanks.

Roniscend updated the task description. (Show Details)

Hi @Neeldoshii,
Thank you so much for the detailed and constructive feedback . I have completely updated the proposal to address each of your questions,
First regarding the Jetpack Compose migration ,I completely agree and my proposal never aimed for a 100% rewrite , I have updated the 6.5 UI layer It now includes categorized tables outlining exactly which screens are prioritized for Compose. I have introduced two new sections Section 6.14 Uncoupling the Monolith and and Section 6.15 Proposed Module Structure
Looking forward to your feedback and any further suggestions.

@Inject
@IoDispatcher
private val ioDispatcher: CoroutineDispatcher

fun loadContributions() {

viewModelScope.launch(ioDispatcher) {
    try {
        val result = repository.getContributions()
    } catch (e: Exception) {
        // Handle error
    }
}

Can you explain why would you require to inject @IoDispatcher?

To modernize the application's routing we will migrate from manual fragment transactions to the industry standard Android Navigation 3 API. Currently the MainActivity relies heavily on boilerplate code, using FragmentTransaction.replace() and custom enums(NavTab, NavTabLoggedOut) to manage bottom tab states. By adopting this modern approach, we will define all application destinations and routing logic within a centralized Navigation Graph.

Nice Approach Owm👍

image.png (2,060×774 px, 226 KB)

Here, now that we are introducing Navigation 3 and Nav graph can we eliminate the Profile Activity, Notification Activity, Settings Activity, About Activity can we have it just composable screen and a SingleActivity that acts a host i.e MainActivity. Need your inputs should we focus this for GSOC? maybe goes little out of the scope given that bandwidth should we club into one or keep things as it is. @RitikaPahwa4444 @Kaartic

At the network level, OkHttp CertificatePinner will be implemented to prevent Man in the middle (MITM) attacks. To protect the application from reverse engineering, we will remove legacy Don't obfuscate ProGuard rules and fully enable R8 obfuscation. Finally, runtime security will be enhanced by integrating the RootBeer library to detect compromised device environments, and Flag Secure will be applied to the window flags of sensitive screens.

Can you please create a Github issue for SSL Pinning. Its nice that you are focusing on security but I think we should focus first on Modularization & Compose Migration first.

:core:domain (Pure Kotlin. Holds Entities, Result wrappers, and Repository Interfaces).
:core:network (Retrofit, OkHttp, MediaWiki API endpoints).
:core:database (Room DAOs, Jetpack DataStore preferences).
:core:ui (Jetpack Compose Material 3 tokens, theme, and reusable common components).
:core:testing (Hilt test rules, MockK extensions for shared module testing).

Can you explain what is Result Wrappers in core domain? Can we also have DDD [Domain Driven Development] Usecase?
For :core:ui can you add in the proposal what all reusable components you will be adding as a subtasks which will be useful for feature modules to reuse the components.

Hi @Neeldoshii , thank you for the detailed feedback

Q1: Can you explain why would you require to inject @IoDispatcher?
Currently, the codebase hardcodes Dispatchers.IO across multiple files such as UploadWorker.kt, DepictsDao.kt, NearbyParentFragmentPresenter.kt, OkHttpJsonApiClient.kt, ZoomableActivity.kt, CustomSelectorActivity.kt, and ImageAdapter.kt. This leads to three key issues:

a)Untestable coroutines: Hardcoding Dispatchers.IO prevents tests from controlling execution. Coroutines run on real IO threads, making tests nondeterministic, flaky, and slow. Injecting @IoDispatcher allows replacing it with TestCoroutineDispatcher or StandardTestDispatcher for deterministic testing.

b)Violation of Dependency Inversion Principle: Classes directly depend on a concrete dispatcher instead of an abstraction. With @IoDispatcher, the dependency is inverted and provided by Hilt, keeping classes decoupled from implementation details.

c)Lack of architectural consistency: In a modular setup like :core:domain and :core:data, dispatchers should be injected via constructors. This centralizes concurrency control and avoids widespread changes if strategies evolve.

Q2: Can we eliminate ProfileActivity, NotificationActivity, SettingsActivity, AboutActivity and have a SingleActivity with Navigation 3?
Yes, this is the ideal architectural end state, but it should be treated as a follow up rather than included within GSoC scope.

Q3: What are Result Wrappers in :core:domain? Can we also have DDD Use Cases?
Result Wrapper is a sealed interface that safely and exhaustively encapsulates the result of a domain operation without relying on exception based flow control.

sealed interface Result<out T> {
    data class Success<T>(val data: T) : Result<T>
    data class Error(val exception: Throwable, val message: String? = null) : Result<Nothing>
    data object Loading : Result<Nothing>
}

This standardizes error handling by ensuring:

  • Repositories return Result<T> instead of raw values
  • ViewModels handle states exhaustively using when
  • UI receives predictable states for rendering (loading, success, error)

Yes, DDD Use Cases can also be introduced in :core:domain.
Benefits of Use Cases in :core:domain:

  • Single Responsibility: each Use Case does one thing
  • Reusable: multiple ViewModels across feature modules can share the same Use Case
  • Testable: pure Kotlin, no Android dependencies
  • Decoupled: ViewModels don't need to know about repositories directly
  • Domain logic centralization: business rules live in the domain layer, not scattered across ViewModels and Activities

Q4: What reusable components should :core:ui include as subtasks?
A. Design System & Theme Foundation

ComponentStatus / Current ImplementationRationale
CommonsThemestyles.xml, colors.xmlUnified Material 3 theme; replaces BaseActivity theme logic.
CommonsColorSchemeScattered color resourcesCentralized light/dark palette; removes SystemThemeUtils dependency.
CommonsTypographyInconsistent hardcoded sizesStandardized type scale using Google Fonts (Inter/Outfit).
EdgeToEdgeShellEdgeToEdgeUtils.kt (273 lines)Migrates complex window inset and IME (keyboard) handling to Compose WindowInsets.

B. Navigation & Layout Shells

ComponentMigrates FromNotes
CommonsTopAppBartoolbar_binding.xmlSupports dynamic menus found in 15+ Activities.
CommonsBottomNavBarNavTabLayout.ktReplaces custom NavTab enum based fragment switching.
CommonsScaffoldRepeated XML patternsStandardizes layout for primary navigation destinations.
LayoutHelpersLayoutUtils.ktMigrates aspect ratio and dimen helpers to Compose Modifiers.

C. Media & Content Primitives

ComponentMigrates FromNotes
MediaThumbnailContributionViewHolder, GridViewAdapterReplaces Fresco SimpleDraweeView with Coil/Glide.
ZoomableImageZoomableActivity.ktMigrates custom touch/zoom logic to Compose transformable.
HtmlTextHtmlTextView.ktNative Compose implementation using AnnotatedString.
NearbySwipeCardNearbyNotificationCardView.javaMigrates complex SwipeableCardView gesture + compass rotation logic.

D. List & State Management

ComponentMigrates FromNotes
PaginatedListFooterAdapter.kt, BaseViewHolderConsolidates LoadMore/Retry/Loading states for list UIs.
EmptyStateViewScattered View.GONE logicStandardized "No Data" UI found in 20+ fragments.
CommonProgressBarprogressBar in 25+ filesUnified loading indicator for long running operations.

E. Input & Interaction

ComponentMigrates FromNotes
PasteSensitiveFieldPasteSensitiveTextInputEditText.ktRetains plain text paste logic required for security/formatting.
CommonsSearchBarSearchActivity / ExploreMapFragmentStandardized search behavior with animation.
CategoryChipGroupCategoryChip.javaEfficient chip based selection for tags and depicts.

F. Dialogs & Utilities

ComponentMigrates FromNotes
CommonsAlertDialogDialogUtil.kt (7 overloads)Migrates showSafely and custom view dialog wrappers.
MoreBottomSheetMoreBottomSheetFragment.ktReplaces BottomSheetDialogFragment with native ModalBottomSheet.
FeedbackSheetFeedbackDialog.ktRefactors user reporting flow into a modern sheet.
UIEffectHandlersViewUtil.ktMigrates Snackbar/Toast/Keyboard helpers to Compose Scaffold / LocalSoftwareKeyboardController.

Hi @Neeldoshii @Kaartic ,
I had a small doubt regarding testing. For our approach, should we primarily use Turbine which is widely used and recommended by Google for Flow testing or stick with the official kotlin-coroutines-test library? Which one would be more appropriate for our use case?

Hi, thanks for submitting your GSoC 2026 project proposal with Wikimedia!

Please make sure you’ve also submitted your proposal on the official Summer of Code website: https://summerofcode.withgoogle.com. The deadline for both submission and any edits is the same, so ensure everything is finalized before March 31, 18:00 UTC, as changes won’t be possible after that.

We strongly recommend completing any updates at least 30 minutes before the deadline to avoid last-minute glitches or unexpected technical issues.

Wishing you all the best for your application. Hope to see you as part of the program soon! 🚀

Hi, thank you for your submission and the effort you put into your proposal. This year we received over 380 strong applications, and unfortunately we were not able to offer you a slot. This was a very competitive process, and many high quality proposals could not be selected. We truly encourage you to stay engaged and continue contributing to Wikimedia projects. Over the years, many contributors who were not selected for Google Summer of Code have gone on to make impactful contributions and become long term members of the community. Please do not see this as a failure, but as a step forward in your journey. We would love to stay in touch and support your continued involvement.

If you would like guidance on how to contribute to our projects outside GSoC, feel free to reach out to any of the mentors or org admins, they will be happy to help you get started.

You can get started or continue contributing here:

We hope to see your contributions in our community soon.