= **[[https://phabricator.wikimedia.org/T415272 | Modularization + Jetpack Compose in Commons Android App]]** =
**Contributor Name:** [[https://www.linkedin.com/in/owmdubey/ | Owm Dubey]]
**Contributor Email:** owmdubey163@gmail.com
**Contributor Github:** [[https://github.com/Roniscend | Roniscend]]
**Location** : India (UTC +5:30)
**Expected Project Size:** 350 hours
**Mentors:**
* [[https://phabricator.wikimedia.org/p/Neeldoshii/ | Neel Doshi]]
* [[https://phabricator.wikimedia.org/p/Kaartic/ | Kaartic]]
---
== 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 **13 PR's** with **3 merged** and 10 currently open and a total of **12 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**.
=== 2.1 Notable Projects ===
* **[[https://github.com/Roniscend/SafarLink | 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
* **[[https://github.com/Roniscend/Atkit | 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
* **[[https://github.com/Roniscend/Survey | 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.
* **[[https://github.com/Roniscend/Moment.ly | 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 ==
| Repository | Pull Request | Issue Solved | Description | Status |
| ----- | ----- | ----- | ----- | ----- |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6663 | #6663]] | [[https://github.com/commons-app/apps-android-commons/issues/6662 | #6662]] | Fix password toggle reset on rotation. | **Merged** |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6681 | #6681]] | [[https://github.com/commons-app/apps-android-commons/issues/6678 | #6678]] | Persist 2FA visibility across configuration changes | **Merged** |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6665 | #6665]] | [[https://github.com/commons-app/apps-android-commons/issues/6650 | #6650]] | Fix SVG decoding in upload flow | Open |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6679 | #6679]] | [[https://github.com/commons-app/apps-android-commons/issues/6669 | #6669]] | Resolve login password toggle desync issue | Open |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6666 | #6666]] | [[https://github.com/commons-app/apps-android-commons/issues/6674 | #6674]] | Enhance character rendering in Nearby list | Open |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6685 | #6685]] | [[https://github.com/commons-app/apps-android-commons/issues/6676 | #6676]] | Handle configuration changes in Place selector | Open |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6682 | #6682]] | [[https://github.com/commons-app/apps-android-commons/issues/6677 | #6677]] | Improve login network error user feedback | Open |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6737 | #6737]] | [[https://github.com/commons-app/apps-android-commons/issues/6699 | #6699]] | Fixed FAB layout and distortion issues | **Merged** |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6753 | #6753]] | [[https://github.com/commons-app/apps-android-commons/issues/6700 | #6700]] | Validated fragment behavior on configuration changes | Open |
| commons-app/apps-android-commons | [[https://github.com/commons-app/apps-android-commons/pull/6740 | #6740]] | [[https://github.com/commons-app/apps-android-commons/issues/6701 | #6701]] | Verified keyboard visibility issue fixed in landscape | Open |
| organicmaps/organicmaps | [[https://github.com/organicmaps/organicmaps/pull/11983 | #11983]] | [[https://github.com/organicmaps/organicmaps/issues/11938 | #11938]] | Fix SplashActivity navigation race condition crash | **Merged** |
| organicmaps/organicmaps | [[https://github.com/organicmaps/organicmaps/pull/12008 | #12008]] | [[https://github.com/organicmaps/organicmaps/issues/10835 | #10835]] | Preserve native debug symbols for readable crashes | **Merged** |
| organicmaps/organicmaps | [[https://github.com/organicmaps/organicmaps/pull/12032 | #12032]] | | Pass specific login error codes to Android layer | **Merged** |
| organicmaps/organicmaps | [[https://github.com/organicmaps/organicmaps/pull/12079 | #12079]] | [[https://github.com/organicmaps/organicmaps/issues/11945 | #11945]] | Optimize layout updates during window inset changes | **Merged** |
| organicmaps/organicmaps | [[https://github.com/organicmaps/organicmaps/pull/12098 | #12098]] | [[https://github.com/organicmaps/organicmaps/issues/12091 | #12091]] | Ensure legacy Android 5 icon compatibility | **Merged** |
| organicmaps/organicmaps | [[https://github.com/organicmaps/organicmaps/pull/12000 | #12000]] | [[https://github.com/organicmaps/organicmaps/issues/11424 | #11424]] | Migrate navbar to AndroidX Edge-to-Edge API | **Merged** |
| organicmaps/organicmaps | [[https://github.com/organicmaps/organicmaps/pull/12006 | #12006]] | [[https://github.com/organicmaps/organicmaps/issues/11668 | #11668]] | Persist login error dialog across configuration changes | **Approved** |
| organicmaps/organicmaps | [[https://github.com/organicmaps/organicmaps/pull/12140 | #12140]] | [[https://github.com/organicmaps/organicmaps/issues/12132 | #12132]] | Fix OpenStreetMap search URL parsing | Open |
| organicmaps/organicmaps | [[https://github.com/organicmaps/organicmaps/pull/12077 | #12077]] | [[https://github.com/organicmaps/organicmaps/issues/12044 | #12044]] | Refactor opening hours layout for readability | Open |
== 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 State | Target State |
| ----- | ----- |
| legacy **Dagger 2** with AndroidInjectionModule and dagger-android | **Hilt** 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
```kotlin
@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**
**{F73438057, layout=center, size=full}**
---
**Hilt The Magic Annotation**
**{F73438829, layout=center, size=full}**
| Feature | Legacy State | Target State |
| ----- | ----- | ----- |
| App Setup | Manual @Component & AndroidInjectionModule | **@HiltAndroidApp** |
| UI Injection | @ContributesAndroidInjector in modules | **@AndroidEntryPoint** |
| Base Classes | DaggerAppCompatActivity/ Fragment | Standard AppCompatActivity/Fragment |
| ViewModels | Manual Factory/Provider injection | **@HiltViewModel** |
=== 6.2 Architecture Pattern ===
| Current State | Target 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.
| Feature | MVP | MVVM |
| ----- | ----- | ----- |
| State handling | Manual updates via View Interfaces | **Reactive streams using State Flow** |
| Lifecycle | Presenters must be manually cleared | **Viewmodels are lifecycle aware** |
| Logic | Tight coupling(Presenter & View) | **Decoupled** (Viewmodel emits View observes) |
| Boilerplate | More(Contract Interfaces for every screen) | **Less** |
**Example:**
Legacy Pattern PagingContract.kt
```kotlin
interface PagingContract {
interface View<T> {
fun showSnackbar()
fun observePagingResults (searchResults: LiveData<...>)
}
interface Presenter<T>: BasePresenter<View<T>> {
//
}
}
```
**Updated Code:**
1. ViewModel
```kotlin
@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
}
}
```
2. UI State
```kotlin
sealed class PagingState {
object Idle: PagingState()
object Loading: PagingState()
data class Error (val message: String): PagingState()
}
```
3. View
```kotlin
@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 Status | Target 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 Area | Current | Target | Affected files |
| ----- | ----- | ----- | ----- |
| Lifecycle Management | Composite Disposable | **ViewModelScope** | 40+ Presenter files(eg. ContributionsListPresenter.kt) |
| Thread Switching | Schedulers.io(), AndroidSchedulers.mainThread() | **Dispatchers.IO, Dispatchers.Main ,withContext()** | Presenters, ViewModels, Repository layers |
| Network return Types | Observable<T>,Single<T>, Completable | **Suspend functions or Flow<T>(streams)** | All Retrofit API interface |
| Data Operators | subscribeOn(), observeOn(), flatMap() | **Standard Kotlin Flow operators(map, flatMapLatest)** | Repository and Data layers |
**Example: ContributionsListPresenter.kt**
RxJava2
```kotlin
@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
```kotlin
@Inject
@IoDispatcher
private val ioDispatcher: CoroutineDispatcher
fun loadContributions() {
viewModelScope.launch(ioDispatcher) {
try {
val result = repository.getContributions()
} catch (e: Exception) {
// Handle error
}
}
}
```
=== 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.
| Migration | Current | Target |
| ----- | ----- | ----- |
| Routing Mechanism | Manual (FragmentTransactions.replace()) | Centralized Navigation Graph & NavController |
| Tab Management | Custom NavTab & NavTabLoggedOut enums | BottomNavigationView.setupWithNavController() |
| Data Passing | Manual Bundle creation and Intent extras| Type 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 Area | Current State | Target State |
| ----- | ----- | ----- |
| UI Framework | 100+ XML layouts + viewbinding | Minimum 40% Jetpack Compose coverage |
| Custom Views | Legacy views like NavtabLayout | Compose Material 3 Adaptive library |
| Screen Hosting | Standard XML inflated Fragments | @Composable functions hosted via ComposableView |
=== 6.6 State Management ===
| Current State | Target State |
| ----- | ----- |
| LiveData+RxJava+Callbacks | StateFlow + 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.
| Migration | Current | Target |
| ----- | ----- | ----- |
| UI State Holding | LiveData & Mutable Live Data | StateFlow & MutableState Flow |
| One Time Events | Manual Callback interface | Shared Flow |
| Compose integration | observeAsState() or manual wrappers | Native 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.
| Migration | Current State | Target State |
| ----- | ----- | ----- |
| Concurrency Framework | RxJava + Custom Thread Pools + AsyncTask | Kotlin Coroutines |
| Thread Switching | Schedulers.io(),AndroidSchedulers.mainThread() | Dispatchers.IO, Dispatchers.Main |
| Lifecycle Safety | Manual cancellation | Structured Concurrency (viewmodelScope) |
| Custom Services | ThreadPoolService.kt | Native 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
```kotlin
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
```kotlin
fun logout(): Completable = Completable.fromObservable(
Observable.empty<Any>()
.doOnComplete {
removeAccount()
currentAccount = null
}
)
```
**Modern Network Architecture:**
```kotlin
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.
| Migration | Current | Target |
| ----- | ----- | ----- |
| Relational Database | Room | Room(Retained, updated for Coroutines/Flow) |
| Key-Value Preferences | Custom JsonKvStore | Jetpack DataStore |
| Execution Context | Synchronous / Custom Threads | Asynchronous (Coroutines & Flow) |
=== 6.12 Security Hardening ===
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.
=== 6.13 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
```kotlin
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
```kotlin
@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**
```kotlin
@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**
```kotlin
@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()
}
}
}
```
== 7. Timeline ==
**GSoC Timeline**
**{F73438062, layout=center, size=full}**
=== 7.1 Week-Wise Distribution ===
**Community Bonding Period**
**Phase1: Foundation & Infrastructure**
**Week 1 (Hilt Migra| Timeframe | Description & Project Setup)**| Est. Hours |
* Gradle Hilt Configuration.Initialize **@HiltAndroidApp** CommonsHiltApplication and completely strip legacy **Dagger AppComponent** initialization and **HasSupportFragmentInjector** implementations.|---|---|---|
* ActivityBuilderModule Replace manual **AndroidInjection.inject(this)** calls with **@AndroidEntryPoint**.| **Week 1** | **Gradle Hilt Configuration & Core DI Migration** | **45 Hrs** |
* Refactor FragmentBuilderModule.apply **@AndroidEntryPoint** to Fragments and validate legacy viewModel injections.Resolve DI related test regressions.Implement **@HiltAndroidTest** via a newly created TestCommonsApplication and configure a CustomTestRunner for robust Hilt UI testing.
**Week 2 (DataStore & Async Foundations)**| Subtask 1.1 | Initialize `@HiltAndroidApp` in `CommonsHiltApplication` and completely strip legacy Dagger `AppComponent` initialization and `HasSupportFragmentInjector` implementations. | 10 Hrs |
* Configure Hilt **DataStoreModule** and **Proto** schemas to globally provide **DataStore<Preferences>**.| Subtask 1.2 | Refactor `ActivityBuilderModule` & `FragmentBuilderModule`. Replace manual `AndroidInjection.inject(this)` calls with `@AndroidEntryPoint` and validate legacy ViewModel injections. | 15 Hrs |
* Migrate standard key - value preferences from legacy BasicKvStore to Jetpack **DataStore**| Subtask 1.3 | Resolve DI-related test regressions. Implement `@HiltAndroidTest` via a newly created `TestCommonsApplication` and configure a `CustomTestRunner` for robust Hilt UI testing. | 20 Hrs |
* Deprecate **JsonKvStore** and refactor classes like **SessionManager** to emit **Flow<T>**,Simultaneously establish and inject centralized Coroutine infrastructure.
**Week 3 (RxJava to Coroutines)**| **Week 2** | **DataStore & Async Foundations** | **45 Hrs** |
* Implement **CommonsCoroutineScope** for centralized,| Subtask 2.1 | Configure Hilt `DataStoreModule` and `Proto` schemas to globally provide `DataStore<Preferences>`. lifecycle aware coroutine management.| 15 Hrs |
* Migrate **ContributionsRepository** from RxJava to Kotlin Flow with **zero UI layer disruption**| Subtask 2.2 | Migrate standard key-value preferences from legacy `BasicKvStore` to Jetpack `DataStore`. | 15 Hrs |
| Subtask 2.3 | Deprecate `JsonKvStore` and refactor classes like `SessionManager` to emit `Flow<T>`, simultaneously establishing and injecting centralized Coroutine infrastructure. | 15 Hrs |
| **Week 3** | **Core Modularization & RxJava to Coroutines** | **45 Hrs** |
| Subtask 3.1 | Set up a `build-logic` module utilizing Gradle Convention Plugins (Version Catalogs) to centralize dependency management across all future modules. | 15 Hrs |
| Subtask 3.2 | Establish foundational core modules (`:core:network`, `:core:database`, `:core:utils`, `:core:ui`) and migrate the modernized components into these strict boundaries. | 15 Hrs |
| Subtask 3.3 | Implement `CommonsCoroutineScope` for centralized, lifecycle-aware coroutine management. Migrate `ContributionsRepository` from RxJava to Kotlin `Flow` with zero UI layer disruption. | 15 Hrs |
* Execute **MVP to MVVM refcator**.Convert **ContributionsListPresenter** to an **@HiltViewModel**(**ContributionsViewModel**), replacing **CompositeDisposable** with viewModelScope and unidirectional Stateflow
**Phase 2: Architecture & Navigation**
**Week 4(MVP to MVVM Migration Core)**| Timeframe | Description | Est. Hours |
* Refactor **ContributionsFragment** to **MVVM**. Embed **@Composable** ContributionsScreen via **ComposeView(DisposeOnViewTreeLifecycleDestroyed)** and observe UI states using collectastateWithLifecycle().|---|---|---|
* Migrate **ExploreFragment** to **MVVM** and implement Compose interoperability for the UI layer.| **Week 4** | **Hybrid UI Migration: MVP -> MVVM** | **45 Hrs** |
* Execute the complex **NearbyParentFragment** MVVM migration.Integrate all newly refactored hybrid Fragments into Mainactivity
**Week 5 (Navigation Component Implementation)**| Subtask 4.1 | Refactor `ContributionsFragment` to MVVM. Embed `@Composable` `ContributionsScreen` via `ComposeView(DisposeOnViewTreeLifecycleDestroyed)` and observe UI states using `collectAsStateWithLifecycle()`. | 15 Hrs |
* Define **nav_graph.xml** to centralize all UI destinations (**ContributionsFragment,NearbyParentFragment,| Subtask 4.2 | Migrate `ExploreFragment` to MVVM and implement Jetpack Compose interoperability for the UI layer. UploadActivity**) using the industry standard **Navigation 3 API**| 15 Hrs |
* Configure **NavHostFragment** and initialize the **NavController** within MainActivity to manage the application's hybrid backstack| Subtask 4.3 | Execute the complex `NearbyParentFragment` MVVM migration. Integrate all newly refactored hybrid Fragments into `MainActivity`. | 15 Hrs |
* Replace legacy **NavTab** routing by binding **BottomNavigationView** directly to the NavController(setupWithNavController)| **Week 5** | **Navigation 3 API, Paging 3 Migration & Core UI** | **45 Hrs** |
| Subtask 5.1 | Migrate `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.2 | Migrate 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.3 | Create 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. Concurrently implement **explicit deep link routing** for external media and notifications.
**Week 6(Network Layer Modernization)**| 10 Hrs |
| **Week 6** | **Network Modernization & Core Feature Extraction** | **45 Hrs** |
*| Subtask 6.1 | Initialize **`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 |
* Refactor **retrofit interfaces**,| Subtask 6.2 | Refactor Retrofit interfaces, replacing `RxJava Observable` returns with Coroutine `suspend` functions mapped to `Flow` in repositories. Extract the `:feature:contributions` module. | 15 Hrs |
| Subtask 6.3 | Extract 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. replacing **RxJava Observable** returns with **Coroutine suspend functions** mapped to **Flow** in repositories| 15 Hrs |
**Phase 3: UI Modernization & Feature Modules**
| **Week 7(Compose UI** | **Core UI Module, MD3 System & Design Tokens)**Compose Integration** | **45 Hrs** |
* Establish an independent **core-ui module** to centralize and encapsulate the new **Jetpack Compose** design system,| Subtask 7.1 | Establish 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. separating UI logic from the app features.| 10 Hrs |
* Implement a complete **Material Design 3** theme architecture, defin| Subtask 7.2 | Build a highly reusable Compose component library utilizing responsive light/dark schemes,the new MD3 tokens. Commons Typography,Rewrite the main contribution list rendering (`RecyclerView` equivalent) purely in Compose utilizing `LazyColumn` and `collectAsLazyPagingItems()`. and CommonsShapes| 20 Hrs |
* Build a highly reusable **Compose component library** utilizing the new MD3 tokens,| Subtask 7.3 | Concurrently migrate legacy image loading to Coil. Concurrently migrate legacy image loading to **Coil**
**Week 8(Feature Modularization)**Strip Fresco XML definitions and implement lightweight Coil `AsyncImage` composables to deeply optimize the image rendering pipeline. | 15 Hrs |
* Extract the logic into a standalone feature:contributions module, utilizing Convention Plugins to segregate the legacy logic from the **core data layer**.| **Week 8** | **Advanced Feature Extraction & Dependency Resolution** | **45 Hrs** |
*| Subtask 8.1 | Extract 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.2 | Refactor the Nearby map functionality into a standalone feature:nearby module, establishing a **decoupled dependency graph** that only uses core:domain to **avoid circular dependencies**.`:feature:nearby` module, Concurrently establish strict inter-module navigation graphs and dependency rules within settings.gradle.kts to prevent circular dependencies.
**Week 9 (Complex Screens Migration)**establishing a decoupled dependency graph that only uses `:core:domain` to avoid circular dependencies. | 15 Hrs |
* Refactor **UploadActivity** into a **Compose based** multi step wizard| Subtask 8.3 | Extract 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. Route the sub flows strictly utilizing the Android **Navigation Component**| 15 Hrs |
*| **Week 9** | **Compose UI Migrate the complexions: Upload, data dense UI of **MediaDetailFragment** entirely to Jetpack Compose & Nearby** | **45 Hrs** |
*| Subtask 9.1 | Refactor **NearbyParentFragment**,`UploadActivity` into a Compose-based multi-step wizard. replacing legacy map implementations with Compose compatible rendering
**Week 10 (Remaining Screens & Polish)**Route the sub-flows strictly utilizing the Android Navigation Component. | 15 Hrs |
* Refactor **ProfileActivity** to **| Subtask 9.2 | Migrate the complex, data-dense UI of `MediaDetailFragment` entirely to Jetpack Compose**,. fully migrating the interactive achievements and leaderboard UI components.| 15 Hrs |
* Migrate **SettingsActivity** and **AboutActivity** to Compose based screens| Subtask 9.3 | Refactor `NearbyParentFragment`, replacing legacy map implementations with Compose-compatible rendering. | 15 Hrs |
* Refactor the final remaining screens **ReviewActivity** and **NotificationActivity**.Execute a final codebase sweep to systematically delete most of the **deprecated XML layouts**
**Week 11(Testing and Quality)**| **Week 10** | **Remaining Screens & Polish** | **45 Hrs** |
* Setup the modern testing stack **Junit5| Subtask 10.1 | Refactor `ProfileActivity` to Compose, MockK,** and either **Turbine** or **kotlinx-coroutines-test** fully replacing legacy **JUnit 4** and **Mockito dependencies**fully migrating the interactive achievements and leaderboard UI components. | 25 Hrs |
* Implement Viewmodel unit tests utilizing **runTest , StandardTestDispatcher**,| Subtask 10.2 | Migrate `SettingsActivity` and `AboutActivity` to Compose-based screens. and **kotlin-coroutines-test** to validate sequential StateFlow emissions
**Week 12**| 10 Hrs |
* Conduct final **end to end testing** across the entire application to resolve all remaining bugs and ensure **absolute stability**| Subtask 10.3 | Refactor the final remaining screens `ReviewActivity` and `NotificationActivity`. Execute a final codebase sweep to systematically delete most of the deprecated XML layouts. | 10 Hrs |
* Conduct a **feedback round** from early adopters and community members,| **Week 11** | **Modern Testing Stack & Coroutine Unit Testing** | **45 Hrs** |
| Subtask 11.1 | Setup 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.2 | Implement ViewModel unit tests utilizing `runTest`, `StandardTestDispatcher`, and `kotlin-coroutines-test` to validate sequential `StateFlow` emissions. | 25 Hrs |
| **Week 12** | **End-to-End Testing, Community Feedback & Final Documentation** | **45 Hrs** |
| Subtask 12.1 | Conduct 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.2 | Conduct 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.3 | Prepare the final blog post summarizing the project outcomes, architectural shifts, and future directions for the Wikimedia Commons app. Submit final work product. Work on follow up issues or PRs based on feedback| 10 Hrs |
* Final **blog post** summarizing the project outcomes and **future directions**
== 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