Tech debt
Urvi is a 92k-line Kotlin app with five HTTP stacks, four Room databases, copy-pasted season screens and almost no tests, so a rewrite should extract domain rules first and port nothing blindly.
Paths below use app/... for app/src/main/java/org/platformcommons/urvi/ and data/... for data/src/main/java/org/mindtree/app/agricultor/data/.
Tech stack factsheet
| Item | Value |
|---|---|
| Modules | :app (org.platformcommons.urvi), :data (org.mindtree.app.agricultor.data, Android library) |
| Kotlin | 648 files, 91,553 LOC (:app 69,265, :data 22,288) |
| Java | 3 files, 237 LOC (2018 logging and Gson helpers) |
| Layout XML | 170 files, 29,065 LOC |
| Resources | 143 drawables, 1,036 English strings |
| Screens | 21 activities, 54 fragments, 28 ViewModels |
| compileSdk / targetSdk / minSdk | 36 / 36 / 26 |
| Version | versionCode 126, versionName 2.2.13 (versions.gradle says 103 / 2.2.4, stale) |
| AGP / Gradle | 8.12.3 / 8.13 |
| Kotlin plugin / KSP | 2.2.20 / 2.2.20-2.0.4, but root build.gradle:59-64 forces all Kotlin artifacts to 1.9.25 |
| Java target | :app 17, :data 1.8 |
| Build types | debug, release (minify and shrink off) |
| Flavors | None. Environment is switched by commenting XML in build_type_config.xml |
| Room | 4 DBs, 49 entities, 49 DAO files |
| WorkManager | 23 workers |
| Tests | 6 files, 9 @Test methods |
Key dependencies
| Group | Artifact and version | Note |
|---|---|---|
| Room | 2.8.3 (KSP) | |
| WorkManager | 2.11.0 | |
| Lifecycle | lifecycle-extensions 2.2.0 (deprecated), viewmodel/livedata-ktx 2.9.4 | 102 ViewModelProviders calls |
| Navigation | 2.9.5 + safe-args | Unused |
| Network | okhttp 5.3.0, retrofit 2.11.0, moshi 1.14.0, jackson-module-kotlin 2.20.0 | |
| Firebase BOM | 31.1.1 (2022) | Crashlytics, FCM, Remote Config, Firestore, Storage, Analytics, In-App Messaging |
| Location | play-services-location 21.3.0 | |
| Maps | osmdroid 6.1.20, osmbonuspack 6.9.0 | osmbonuspack unused |
| Images | glide 5.0.5, compressor 3.0.1, fishbun 1.1.1 | fishbun unused |
| UI | lottie 6.4.0, flexbox 3.0.0, CalendarView 1.0.4, signature-pad 1.3.1 | |
| Bridge (private Nexus) | bridge-account-lib 2.7.13-urvi, CoreAPI 8.2.2, OfflineAndroid 8.2.3, OfflineCore 8.2.1, framework-android-rest-client 4.2.0, BRBASE 9.1.3, BRCTLD 9.0.5 and other generated DTO jars | 149 files, 304 imports. The core rewrite coupling |
| PCF internal | commons-assessment-urvi-lib 1.0.13-SNAPSHOT | 48 files |
Not present: Compose, Hilt, Paging, CameraX, Google Maps, SQLCipher, EncryptedSharedPreferences.
Architecture as-is
| Concern | What the code does |
|---|---|
| UI | XML layouts + data binding (111 files), 66 findViewById left |
| Presentation | MVVM in name only: 28 ViewModels for 75 screens. Fragments call repositories and DAOs directly |
| Reactive type | LiveData (676 hits). Flow: none. StateFlow: 1 |
| Threading | Executors / AppExecutors dominate (409 hits in 105 files). Raw Thread 53 hits. Coroutines 89 hits, mostly new code. 2 AsyncTask |
| DI | None. Manual singletons and object repositories |
| Navigation | Manual fragment transactions in app/.../utility/ActivityUtils.kt |
| Networking | 5 HTTP stacks: Bridge framework-android-rest-client, Bridge CoreAPI queue, data/.../net/RestClient.kt and RestClientBridge.kt (Retrofit + Jackson), RetrofitAPIClient.kt (Retrofit + Gson), RetrofitProvider.kt (Retrofit + Gson) |
| JSON | 3 libraries: Gson (79 files), Moshi (50), Jackson (13) |
| Logging | 4 facades: android.util.Log, custom utility/Log.java, Timber, printStackTrace (174) |
| Null safety | 1,205 !! in 215 files, 246 lateinit var |
| Module boundary | :data holds a god repository and sync. :app has its own Room DB and repositories too |
Spaghetti hotspots
| File | LOC | Why it hurts |
|---|---|---|
data/.../repo/DataRepository.kt | 3,395 | God repository, 440 functions, every DAO behind one class; 141 commits |
app/.../view/ie/AddIeFragment.kt | 1,990 | 194 ifs; farmer registration rules live in the Fragment |
app/.../utility/AppUtils.kt | 1,970 | 149 functions; season rules, feature flags and dialogs mixed |
app/.../view/aap_25_26/ActionPlanFragment2025To2026.kt | 1,830 | Season copy of AnnualActionPlanFragment (1,160) |
app/.../view/option/OptionFragment.kt | 1,795 | 133 ifs; 139 commits |
app/.../view/portfolio/AddPortfolioActivity.kt | 1,746 | 117 ifs |
app/.../view/endorsements/vo/EndorsementFragment.kt | 1,501 | Large single screen |
app/.../view/datasetup/DataSetupViewModel.kt | 1,313 | 17 nesting levels; near-copy of UnitDataSetupViewModel (982) |
data/.../db/AppDatabase.kt | 1,252 | 57 hand-written migrations to v62 |
app/.../view/landasset/AddUpdateAssetNewDialog.kt | 1,099 | Dialog with lookups and save logic |
app/.../utility/DataUtil.kt | 1,077 | Master seeding, prefs, a hardcoded licence list; name clashes with data/.../utils/DataUtil.kt |
app/.../view/home/HomeActivity.kt | 1,058 | 14 nesting levels; sync, account and in-app update in one Activity |
app/.../view/geofencing/GeofencingMapActivity.kt | 1,027 | Polygon capture, osmdroid and GPS in one Activity |
Layout giants: fragment_annual_action_plan_25_26.xml (2,373 lines), fragment_annual_action_plan.xml (1,649), fragment_add_ie.xml (1,113), farmer_abstract_2425_fragment.xml (1,044).
33 files reach 10 or more indent levels.
Duplication
About 8.2% of non-trivial lines sit in duplicated 12-line windows.
Season copy-paste
Each agricultural year is built by copying screens, entities and sync handlers:
AnnualActionPlan2526,AnnualActionPlan2526DataSetActionPlanFragment2025To2026(copy ofAnnualActionPlanFragment)FarmerAbstract2425BottomSheet+farmer_abstract_2425_fragment.xml(38 duplicate windows vsFarmerAbstractBottomSheet)AnnualActionPlan2526PostRH,AnnualActionPlan2526PutRH
The rewrite must make season forms schema-driven from server config.
Other duplicate pairs
| Pair | Duplicate windows |
|---|---|
DataSetupViewModel / UnitDataSetupViewModel | 77 |
DataSetupActivity / UnitDataSetupActivity | 39 |
DailyMisFragment / MonthlyMisFragment | n/a |
CustomPracticeATMAdapter / CustomPracticeAdapter | n/a |
SetUpWorknode / SetupPieWorkNode | n/a |
FetchFarmerAssetDataWorker in app/.../utility/FarmerDataUpdateWorker.kt and data/.../utils/LandDataUpdateWorker.kt | same class name, both scheduled |
Account-add block in HomeActivity.kt:369-385 and SettingsActivity.kt:460-477 | copy-paste |
Also duplicated: FcmUtil x2, DataUtil x2, Resource x2, Constants x2 in :data, preference wrappers x3, Retrofit clients x4.
DB and migration pain
| DB | File | Version | Problem |
|---|---|---|---|
agri_db | data/.../db/AppDatabase.kt | v62 | 57 hand-written migrations; .allowMainThreadQueries() (line 314); .fallbackToDestructiveMigrationFrom(1) (line 315) |
ie_register.db | app/.../datasource/database/AppDatabase.kt | v20 | Second, overlapping DB; .fallbackToDestructiveMigration() (line 91) can wipe data |
error_log_db | data/.../db/ErrorLogDatabase.kt | v2 | Stores url, body, headers and raw response of failures |
sync_ledger_db | data/.../db/SyncLedgerDB.kt | v1 | .allowMainThreadQueries() (line 38) |
SYNC_QUEUE_DB | OfflineAndroid AAR | v3 | Java-serialized, class-name-bound rows (see Sibling libraries) |
AppDatabase.kthas 113 commits; schema dirs still use the Mindtree package name.- No migration tests exist.
- A rewrite must migrate
agri_dbv62 in place or drain everything to the server first.
Testing
| Test file | Tests | Covers |
|---|---|---|
app/src/test/.../ApiOperationUtilTest.kt, AllOperationsTest.kt | 6 | URL-to-label mapping |
data/src/test/.../ExampleUnitTest.kt | 1 | Template |
data/src/androidTest/.../IEDaoTest.kt, SyncWorkStatusDaoTest.kt | 2 | Two DAOs |
Coverage of business logic, sync and migrations is about 0%. There is no CI config in the repo.
Localization coverage
| Locale | Folder | Strings | Missing vs English |
|---|---|---|---|
| English | values/ | 1,036 (+22 arrays) | n/a |
| Telugu | values-te/ | 989 (+3 arrays) | 48 |
| Tamil | values-ta/ | 620 (+1 array) | 416 (40%) |
:data | data/src/main/res/values | 18 | English only |
- 146 hardcoded
android:text/android:hintliterals, 26 hardcodedsetText, 25 literal toasts. - 172 unused string keys (about 17%).
values-te/strings.xmlis the most-changed file in git (196 commits). Translations are hand-maintained.- Language split in bundles is off so in-app switching (
LocaleManager) works.
Git history insights
- 1,013 commits, history from 2022-07-25 (re-import). Code dates to about 2018 (Mindtree IGX). Last commit 2026-10-06.
- Commits per year: 2022: 191, 2023: 323, 2024: 331, 2025: 68, 2026: 99.
- 9 author names, about 5-6 real people.
- Pack size 38.8 MiB, 1,119 tracked files.
| Commits | File | Signal |
|---|---|---|
| 196 | res/values-te/strings.xml | Manual translation |
| 191 | res/values/strings.xml | |
| 167 | versions.gradle | People bump dead version fields |
| 141 | data/.../repo/DataRepository.kt | God repo |
| 139 | app/.../view/option/OptionFragment.kt | Hotspot |
| 113 | data/.../db/AppDatabase.kt | Migrations |
| 108 | app/src/debug/res/values/build_type_config.xml | Env switching by editing XML |
| 108 | app/build.gradle | |
| 100 | app/.../view/portfolio/PortfolioActivity.kt | Hotspot |
| 97 | app/.../view/ie/AddIeFragment.kt | Hotspot |
Large and high-churn files carry the domain rules worth extracting first: DataRepository, OptionFragment, AddIeFragment, AppUtils, PortfolioActivity / AddPortfolioActivity, HomeActivity, AppDatabase.
Dead code
Estimated 6,500-7,000 LOC (about 5-6% of Kotlin + layout XML):
| Kind | Approx. LOC |
|---|---|
Unused classes (for example AddUpdateAssetDialog.kt 422, FetchEndorsementForVoDataWorker.kt 202, IeSkillRepositoryImpl.kt 124) | 1,800 |
| Unused layouts (24 files) | 2,200 |
| Commented-out code | 2,500 |
| Root scratch files and old publish/sign config | 150 |
Also: 24 unused drawables, 172 unused strings, about 40 of 55 versions.gradle entries. The full list is in the stale-debt data.
Dependency clean-up list
| Action | Item | Evidence |
|---|---|---|
| Remove | io.github.sangcomz:fishbun | 0 refs |
| Remove | navigation-fragment-ktx, navigation-ui-ktx, safe-args plugin | 0 NavController / NavHost |
| Remove | retrofit2:converter-moshi | 0 MoshiConverterFactory |
| Remove | osmbonuspack | Only in licence text |
| Remove | kotlin-kapt plugin | No kapt deps |
| Remove | multidex | Native at minSdk 26 |
| Remove | Kotlin 1.9.25 force in root build.gradle:59-64 | Fights the 2.2.20 plugin |
| Migrate | lifecycle-extensions 2.2.0 | Replace 102 ViewModelProviders calls |
| Upgrade | Firebase BOM 31.1.1, CalendarView 1.0.4, activity 1.8.0 | Old pins |
| Check | firebase-analytics, firebase-inappmessaging-display | 0 code refs; may be console-driven |
| Replace | jcenter(), mavenLocal() | Sunset / supply-chain risk |
| Consolidate | Gson + Moshi + Jackson | Pick one |
| Consolidate | 4 Retrofit clients, 4 log facades | One each |
| Replace | versions.gradle | Gradle version catalog |
| Replace | XML commenting for environments | Build flavors |
| Drop at rewrite | All Bridge libs, org.apache.http.legacy | See Sibling libraries |