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

ItemValue
Modules:app (org.platformcommons.urvi), :data (org.mindtree.app.agricultor.data, Android library)
Kotlin648 files, 91,553 LOC (:app 69,265, :data 22,288)
Java3 files, 237 LOC (2018 logging and Gson helpers)
Layout XML170 files, 29,065 LOC
Resources143 drawables, 1,036 English strings
Screens21 activities, 54 fragments, 28 ViewModels
compileSdk / targetSdk / minSdk36 / 36 / 26
VersionversionCode 126, versionName 2.2.13 (versions.gradle says 103 / 2.2.4, stale)
AGP / Gradle8.12.3 / 8.13
Kotlin plugin / KSP2.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 typesdebug, release (minify and shrink off)
FlavorsNone. Environment is switched by commenting XML in build_type_config.xml
Room4 DBs, 49 entities, 49 DAO files
WorkManager23 workers
Tests6 files, 9 @Test methods

Key dependencies

GroupArtifact and versionNote
Room2.8.3 (KSP)
WorkManager2.11.0
Lifecyclelifecycle-extensions 2.2.0 (deprecated), viewmodel/livedata-ktx 2.9.4102 ViewModelProviders calls
Navigation2.9.5 + safe-argsUnused
Networkokhttp 5.3.0, retrofit 2.11.0, moshi 1.14.0, jackson-module-kotlin 2.20.0
Firebase BOM31.1.1 (2022)Crashlytics, FCM, Remote Config, Firestore, Storage, Analytics, In-App Messaging
Locationplay-services-location 21.3.0
Mapsosmdroid 6.1.20, osmbonuspack 6.9.0osmbonuspack unused
Imagesglide 5.0.5, compressor 3.0.1, fishbun 1.1.1fishbun unused
UIlottie 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 jars149 files, 304 imports. The core rewrite coupling
PCF internalcommons-assessment-urvi-lib 1.0.13-SNAPSHOT48 files

Not present: Compose, Hilt, Paging, CameraX, Google Maps, SQLCipher, EncryptedSharedPreferences.

Architecture as-is

ConcernWhat the code does
UIXML layouts + data binding (111 files), 66 findViewById left
PresentationMVVM in name only: 28 ViewModels for 75 screens. Fragments call repositories and DAOs directly
Reactive typeLiveData (676 hits). Flow: none. StateFlow: 1
ThreadingExecutors / AppExecutors dominate (409 hits in 105 files). Raw Thread 53 hits. Coroutines 89 hits, mostly new code. 2 AsyncTask
DINone. Manual singletons and object repositories
NavigationManual fragment transactions in app/.../utility/ActivityUtils.kt
Networking5 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)
JSON3 libraries: Gson (79 files), Moshi (50), Jackson (13)
Logging4 facades: android.util.Log, custom utility/Log.java, Timber, printStackTrace (174)
Null safety1,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

FileLOCWhy it hurts
data/.../repo/DataRepository.kt3,395God repository, 440 functions, every DAO behind one class; 141 commits
app/.../view/ie/AddIeFragment.kt1,990194 ifs; farmer registration rules live in the Fragment
app/.../utility/AppUtils.kt1,970149 functions; season rules, feature flags and dialogs mixed
app/.../view/aap_25_26/ActionPlanFragment2025To2026.kt1,830Season copy of AnnualActionPlanFragment (1,160)
app/.../view/option/OptionFragment.kt1,795133 ifs; 139 commits
app/.../view/portfolio/AddPortfolioActivity.kt1,746117 ifs
app/.../view/endorsements/vo/EndorsementFragment.kt1,501Large single screen
app/.../view/datasetup/DataSetupViewModel.kt1,31317 nesting levels; near-copy of UnitDataSetupViewModel (982)
data/.../db/AppDatabase.kt1,25257 hand-written migrations to v62
app/.../view/landasset/AddUpdateAssetNewDialog.kt1,099Dialog with lookups and save logic
app/.../utility/DataUtil.kt1,077Master seeding, prefs, a hardcoded licence list; name clashes with data/.../utils/DataUtil.kt
app/.../view/home/HomeActivity.kt1,05814 nesting levels; sync, account and in-app update in one Activity
app/.../view/geofencing/GeofencingMapActivity.kt1,027Polygon 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, AnnualActionPlan2526DataSet
  • ActionPlanFragment2025To2026 (copy of AnnualActionPlanFragment)
  • FarmerAbstract2425BottomSheet + farmer_abstract_2425_fragment.xml (38 duplicate windows vs FarmerAbstractBottomSheet)
  • AnnualActionPlan2526PostRH, AnnualActionPlan2526PutRH

The rewrite must make season forms schema-driven from server config.

Other duplicate pairs

PairDuplicate windows
DataSetupViewModel / UnitDataSetupViewModel77
DataSetupActivity / UnitDataSetupActivity39
DailyMisFragment / MonthlyMisFragmentn/a
CustomPracticeATMAdapter / CustomPracticeAdaptern/a
SetUpWorknode / SetupPieWorkNoden/a
FetchFarmerAssetDataWorker in app/.../utility/FarmerDataUpdateWorker.kt and data/.../utils/LandDataUpdateWorker.ktsame class name, both scheduled
Account-add block in HomeActivity.kt:369-385 and SettingsActivity.kt:460-477copy-paste

Also duplicated: FcmUtil x2, DataUtil x2, Resource x2, Constants x2 in :data, preference wrappers x3, Retrofit clients x4.

DB and migration pain

DBFileVersionProblem
agri_dbdata/.../db/AppDatabase.ktv6257 hand-written migrations; .allowMainThreadQueries() (line 314); .fallbackToDestructiveMigrationFrom(1) (line 315)
ie_register.dbapp/.../datasource/database/AppDatabase.ktv20Second, overlapping DB; .fallbackToDestructiveMigration() (line 91) can wipe data
error_log_dbdata/.../db/ErrorLogDatabase.ktv2Stores url, body, headers and raw response of failures
sync_ledger_dbdata/.../db/SyncLedgerDB.ktv1.allowMainThreadQueries() (line 38)
SYNC_QUEUE_DBOfflineAndroid AARv3Java-serialized, class-name-bound rows (see Sibling libraries)
  • AppDatabase.kt has 113 commits; schema dirs still use the Mindtree package name.
  • No migration tests exist.
  • A rewrite must migrate agri_db v62 in place or drain everything to the server first.

Testing

Test fileTestsCovers
app/src/test/.../ApiOperationUtilTest.kt, AllOperationsTest.kt6URL-to-label mapping
data/src/test/.../ExampleUnitTest.kt1Template
data/src/androidTest/.../IEDaoTest.kt, SyncWorkStatusDaoTest.kt2Two DAOs

Coverage of business logic, sync and migrations is about 0%. There is no CI config in the repo.

Localization coverage

LocaleFolderStringsMissing vs English
Englishvalues/1,036 (+22 arrays)n/a
Teluguvalues-te/989 (+3 arrays)48
Tamilvalues-ta/620 (+1 array)416 (40%)
:datadata/src/main/res/values18English only
  • 146 hardcoded android:text / android:hint literals, 26 hardcoded setText, 25 literal toasts.
  • 172 unused string keys (about 17%).
  • values-te/strings.xml is 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.
CommitsFileSignal
196res/values-te/strings.xmlManual translation
191res/values/strings.xml
167versions.gradlePeople bump dead version fields
141data/.../repo/DataRepository.ktGod repo
139app/.../view/option/OptionFragment.ktHotspot
113data/.../db/AppDatabase.ktMigrations
108app/src/debug/res/values/build_type_config.xmlEnv switching by editing XML
108app/build.gradle
100app/.../view/portfolio/PortfolioActivity.ktHotspot
97app/.../view/ie/AddIeFragment.ktHotspot

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):

KindApprox. LOC
Unused classes (for example AddUpdateAssetDialog.kt 422, FetchEndorsementForVoDataWorker.kt 202, IeSkillRepositoryImpl.kt 124)1,800
Unused layouts (24 files)2,200
Commented-out code2,500
Root scratch files and old publish/sign config150

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

ActionItemEvidence
Removeio.github.sangcomz:fishbun0 refs
Removenavigation-fragment-ktx, navigation-ui-ktx, safe-args plugin0 NavController / NavHost
Removeretrofit2:converter-moshi0 MoshiConverterFactory
RemoveosmbonuspackOnly in licence text
Removekotlin-kapt pluginNo kapt deps
RemovemultidexNative at minSdk 26
RemoveKotlin 1.9.25 force in root build.gradle:59-64Fights the 2.2.20 plugin
Migratelifecycle-extensions 2.2.0Replace 102 ViewModelProviders calls
UpgradeFirebase BOM 31.1.1, CalendarView 1.0.4, activity 1.8.0Old pins
Checkfirebase-analytics, firebase-inappmessaging-display0 code refs; may be console-driven
Replacejcenter(), mavenLocal()Sunset / supply-chain risk
ConsolidateGson + Moshi + JacksonPick one
Consolidate4 Retrofit clients, 4 log facadesOne each
Replaceversions.gradleGradle version catalog
ReplaceXML commenting for environmentsBuild flavors
Drop at rewriteAll Bridge libs, org.apache.http.legacySee Sibling libraries