AndroidCareProvider - Code Quality & Technical Debt Report¶
Repository: AndroidCareProvider
Platform: Android (Java + Kotlin)
Analysis Date: November 7, 2025
Version: 2.0.33 (Build 68)
Language Distribution: Java (85%), Kotlin (15%)
Executive Summary¶
This code quality analysis identifies significant technical debt across the AndroidCareProvider codebase. The application contains ~150,000+ lines of Java code with numerous code smells, anti-patterns, and maintainability issues. Key problems include God Classes (2,800+ LOC files), excessive copy-paste code, poor error handling, and lack of architectural consistency.
Quality Metrics Summary¶
| Metric | Value | Status | Industry Standard |
|---|---|---|---|
| Total LOC | ~150,000+ | ⚠️ Large | - |
| Largest File | 2,963 LOC | 🔴 Critical | <500 LOC |
| God Classes | 5+ | 🔴 Critical | 0 |
| TODO Comments | 50+ | 🟠 High | <10 |
| Generic Exception Catches | 100+ | 🔴 Critical | 0 |
| Duplicated Permission Classes | 3 | 🟠 High | 1 |
| Static Mutable State | 15+ | 🟠 High | 0 |
| Naming Convention Issues | 20+ | 🟡 Medium | 0 |
Technical Debt Assessment¶
| Category | Severity | Count |
|---|---|---|
| God Classes | 🔴 Critical | 5 |
| Error Handling | 🔴 Critical | 100+ |
| Code Duplication | 🟠 High | 30%+ |
| Static State | 🟠 High | 15+ |
| Naming Violations | 🟡 Medium | 20+ |
| Deprecated Code | 🟡 Medium | 10+ |
Table of Contents¶
- Code Complexity & Size Issues
- Error Handling Anti-Patterns
- Code Duplication
- Naming Conventions
- Architecture & Design Issues
- Memory & Performance
- Code Smells
- Deprecated & Dead Code
- Documentation Issues
- Refactoring Recommendations
Code Complexity & Size Issues¶
🔴 CRITICAL: God Classes (5 Files >2,000 LOC)¶
Issue: Multiple files exceed 2,000 lines of code, violating Single Responsibility Principle.
| File | Lines | Complexity | Issue |
|---|---|---|---|
CalendarCustomView.java |
2,963 | 🔴 Extreme | Calendar logic, API calls, UI, scheduling all mixed |
WeeklyScheduleFragment.java |
2,824 | 🔴 Extreme | Schedule management, validation, API, UI combined |
Utils.java |
2,000+ | 🔴 Extreme | Utility dumping ground with 100+ methods |
LoginActivity.java |
1,500+ | 🟠 High | Auth, social login, navigation, validation |
RegisterBasicInfo.java |
919 | 🟠 High | Registration logic with excessive UI code |
Example - CalendarCustomView.java:
// Lines 1-2963 - Single file doing EVERYTHING
public class CalendarCustomView extends LinearLayout {
// 50+ instance variables
private TextView previousButton, current_year;
private ExpandableHeightGridView calendarGridView;
private SimpleDateFormat formatter1, formatter2;
private Calendar cal, cal1;
private GridAdapter mAdapter;
private SlotsGridAdapter slotsAdapter;
// ... 40 more fields ...
// 80+ methods covering:
// - Calendar rendering
// - Slot booking
// - API calls (10+ endpoints)
// - Payment integration
// - Date validation
// - UI state management
// - Event handling
// - Data parsing
private class DoCalculationTask extends AsyncTask<String, Void, Void> {
// Async task inside view class (anti-pattern)
}
}
Problems:
- Single Responsibility violated - doing calendar UI, business logic, networking, and data management
- Impossible to test - no separation of concerns
- High coupling - changes in one area break others
- Performance issues - massive object creation
- Merge conflicts - multiple developers editing same file
Refactoring Plan:
// Recommended structure:
// 1. ViewModel for business logic
class CalendarViewModel : ViewModel() {
private val repository = CalendarRepository()
val slots = MutableLiveData<List<Slot>>()
fun loadSlots(date: Date) {
viewModelScope.launch {
slots.value = repository.getSlots(date)
}
}
}
// 2. Repository for data layer
class CalendarRepository(private val api: CalendarApi) {
suspend fun getSlots(date: Date): List<Slot> {
return api.getSlots(date)
}
}
// 3. Simple View class (<300 LOC)
class CalendarCustomView(context: Context) : LinearLayout(context) {
private val viewModel: CalendarViewModel by viewModels()
init {
setupUI()
observeData()
}
private fun setupUI() { /* UI setup only */ }
private fun observeData() { /* Observe ViewModel */ }
}
// 4. Separate utility classes
object DateUtils { /* Date operations */ }
object ValidationUtils { /* Validation logic */ }
🟠 HIGH: Excessively Long Methods¶
Issue: Many methods exceed 100 lines, making them hard to understand and test.
| File | Method | Lines | Issue |
|---|---|---|---|
WeeklyScheduleFragment.java |
onCreateView() |
300+ | Initialization hell |
CalendarCustomView.java |
FillBookingGridList() |
200+ | Complex booking logic |
LoginActivity.java |
FBLogin() |
150+ | Facebook login with inline callbacks |
RegisterBasicInfo.java |
RegisterCareProvider() |
200+ | Form validation + API call |
Example:
// WeeklyScheduleFragment.java - onCreateView()
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_weekly_schedule, container, false);
// 300 lines of:
// - findViewById calls (50+)
// - Click listener setup (30+)
// - Adapter initialization (10+)
// - API calls (5+)
// - Validation logic (50+)
// - Date calculations (40+)
// - UI state setup (60+)
// - Permission checks (20+)
return view; // Line 300+
}
Recommendation:
// Break into smaller functions
override fun onCreateView(...): View {
val view = inflater.inflate(R.layout.fragment_weekly_schedule, container, false)
initializeViews(view)
setupAdapters()
setupClickListeners()
observeViewModel()
return view
}
private fun initializeViews(view: View) { /* <30 LOC */ }
private fun setupAdapters() { /* <20 LOC */ }
private fun setupClickListeners() { /* <40 LOC */ }
private fun observeViewModel() { /* <20 LOC */ }
🟡 MEDIUM: High Cyclomatic Complexity¶
Issue: Deep nesting and excessive branching logic.
Example:
// Typical pattern found throughout
public void processSlot(Slot slot) {
if (slot != null) {
if (slot.isAvailable()) {
if (userIsLoggedIn()) {
if (hasPaymentMethod()) {
if (slotNotExpired()) {
if (userHasPermission()) {
// Finally do something (6 levels deep!)
bookSlot(slot);
} else {
showPermissionError();
}
} else {
showExpiredError();
}
} else {
showPaymentError();
}
} else {
redirectToLogin();
}
} else {
showUnavailableError();
}
}
}
Better Approach:
fun processSlot(slot: Slot?) {
// Early returns reduce nesting
if (slot == null) return
if (!slot.isAvailable()) return showUnavailableError()
if (!userIsLoggedIn()) return redirectToLogin()
if (!hasPaymentMethod()) return showPaymentError()
if (slotNotExpired()) return showExpiredError()
if (!userHasPermission()) return showPermissionError()
bookSlot(slot)
}
Error Handling Anti-Patterns¶
🔴 CRITICAL: Generic Exception Catching (100+ Occurrences)¶
Severity: Critical
Count: 100+ instances
Issue: Catching broad Exception or Throwable hides bugs and makes debugging impossible.
Examples Found:
// 1. Silent failures (50+ occurrences)
try {
// Complex operation
performCriticalOperation();
} catch (Exception e) {
// NO ACTION TAKEN - failure silently ignored!
}
// 2. Only printStackTrace (30+ occurrences)
try {
processPayment();
} catch (Exception e) {
e.printStackTrace(); // ❌ Not useful in production
// User sees nothing, payment fails silently
}
// 3. Catching Throwable (10+ occurrences)
try {
savePatientData();
} catch (Throwable t) { // ❌ Too broad, catches OutOfMemoryError etc.
// Application should crash for serious errors
}
// 4. Commented exception handling
try {
authenticate();
} catch (Exception e) {
// e.printStackTrace(); // ❌ Commented out, no error handling
}
Specific Examples:
// clsShow_Alarm.java - Lines 42-48
try {
// Complex alarm logic
} catch (Exception e) {
e.printStackTrace(); // ❌ User never knows alarm failed
}
try {
// More logic
} catch (Exception e) {
e.printStackTrace();
}
try {
// Even more
} catch (Exception ex) {
// No action at all! ❌
}
// CareProvider/Schedule/CalendarCareProviderView.java - Line 948
try {
parseSlotData(jsonArray);
} catch (Throwable t) { // ❌ Catching Throwable
// Silent failure
}
// PaymentWebActivity.java - Lines 125-126
try {
processPaymentResponse();
} catch (Exception e) {
e.printStackTrace(); // ❌ Payment failure not communicated
}
// LoginActivity.java - Lines 521-522 (Commented)
//} catch (Exception e) {
// e.printStackTrace(); // ❌ Exception handling removed, not replaced
//}
Impact:
- Silent failures in payment processing
- Lost patient data without user notification
- Impossible debugging in production
- HIPAA violation (no audit trail of errors)
Recommended Fix:
// Specific exception handling
try {
performCriticalOperation()
} catch (NetworkException e) {
showError(R.string.network_error)
logError("NetworkError in performCriticalOperation", e)
} catch (ValidationException e) {
showError(R.string.validation_error)
logError("ValidationError", e)
} catch (Exception e) {
// Last resort - log and notify user
showError(R.string.unexpected_error)
Crashlytics.logException(e)
logError("Unexpected error", e)
}
// Custom logging utility
object ErrorLogger {
fun logError(message: String, throwable: Throwable) {
Log.e(TAG, message, throwable)
if (BuildConfig.DEBUG) {
// Show detailed error in debug
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
// Send to crash reporting
FirebaseCrashlytics.getInstance().recordException(throwable)
// Audit log for HIPAA compliance
AuditLogger.logError(message, throwable)
}
}
Refactoring Priority: HIGH - Fix immediately for critical paths (payment, patient data)
🟠 HIGH: No Error Recovery Strategies¶
Issue: When errors occur, application doesn’t attempt recovery.
Example:
// Typical API call pattern
AndroidNetworking.post(url)
.addHeaders(Utils.getHeaders())
.build()
.getAsJSONObject(new JSONObjectRequestListener() {
@Override
public void onResponse(JSONObject response) {
// Happy path only
parseAndDisplay(response);
}
@Override
public void onError(ANError error) {
// ❌ Just hide loading, show generic error
progressDialog.dismiss();
Toast.makeText(context, "Error occurred", Toast.LENGTH_SHORT).show();
// No retry, no offline cache, no fallback
}
});
Better Approach:
class RetryInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var attempt = 0
var response: Response
do {
try {
response = chain.proceed(chain.request())
if (response.isSuccessful) return response
} catch (e: IOException) {
if (attempt >= MAX_RETRIES) throw e
}
attempt++
delay(RETRY_DELAY_MS * attempt)
} while (attempt < MAX_RETRIES)
return response
}
}
// ViewModel with error handling
class PatientViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState
fun loadPatientData(patientId: String) {
viewModelScope.launch {
_uiState.value = UiState.Loading
try {
val data = repository.getPatientData(patientId)
_uiState.value = UiState.Success(data)
} catch (e: NetworkException) {
// Try cache
val cachedData = repository.getCachedData(patientId)
if (cachedData != null) {
_uiState.value = UiState.Success(cachedData, isOffline = true)
} else {
_uiState.value = UiState.Error(
message = "Network error",
canRetry = true
)
}
} catch (e: Exception) {
_uiState.value = UiState.Error(
message = e.message ?: "Unknown error",
canRetry = false
)
Crashlytics.logException(e)
}
}
}
}
Code Duplication¶
🟠 HIGH: Permission Handling Duplication (3 Identical Classes)¶
Issue: Three nearly identical classes for permission handling.
| File | Purpose | LOC | Duplication |
|---|---|---|---|
clsCameraPermission.java |
Camera permission | 150 | 95% duplicate |
clsRecordingPermission.java |
Audio permission | 150 | 95% duplicate |
clsStoragePermission.java |
Storage permission | 150 | 95% duplicate |
clsDrawOverAppPermission.java |
Overlay permission | 150 | 80% duplicate |
Example - Identical Code in All Three:
// clsCameraPermission.java
public static final int REQUEST_CAMERA_PERMISSION = 200;
public static String[] permissions = {Manifest.permission.CAMERA};
public static void Check(Context context, Button btnAction, int opt) {
if (hasPermissions(context, permissions)) {
// Grant action
} else {
// Request permission
}
}
public static boolean hasPermissions(Context context, String... permissions) {
if (context != null && permissions != null) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(context, permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
// ❌ EXACT SAME CODE in clsRecordingPermission.java with different permission
// ❌ EXACT SAME CODE in clsStoragePermission.java with different permission
Refactored Solution:
// Single permission manager
class PermissionManager(private val activity: Activity) {
enum class Permission(val manifest: String, val requestCode: Int) {
CAMERA(Manifest.permission.CAMERA, 200),
AUDIO(Manifest.permission.RECORD_AUDIO, 201),
STORAGE(Manifest.permission.WRITE_EXTERNAL_STORAGE, 202),
OVERLAY(Manifest.permission.SYSTEM_ALERT_WINDOW, 203)
}
fun checkPermission(
permission: Permission,
onGranted: () -> Unit,
onDenied: (shouldShowRationale: Boolean) -> Unit
) {
when {
hasPermission(permission) -> onGranted()
shouldShowRationale(permission) -> {
onDenied(true)
}
else -> {
requestPermission(permission)
onDenied(false)
}
}
}
private fun hasPermission(permission: Permission): Boolean {
return ContextCompat.checkSelfPermission(activity, permission.manifest)
== PackageManager.PERMISSION_GRANTED
}
private fun shouldShowRationale(permission: Permission): Boolean {
return ActivityCompat.shouldShowRequestPermissionRationale(
activity, permission.manifest
)
}
private fun requestPermission(permission: Permission) {
ActivityCompat.requestPermissions(
activity,
arrayOf(permission.manifest),
permission.requestCode
)
}
}
// Usage
val permissionManager = PermissionManager(this)
permissionManager.checkPermission(
Permission.CAMERA,
onGranted = { startCamera() },
onDenied = { shouldShowRationale ->
if (shouldShowRationale) {
showPermissionRationale()
} else {
showPermissionSettings()
}
}
)
Estimated Savings: Delete 450 LOC, replace with 80 LOC
🟡 MEDIUM: Duplicated WebViewClient Classes¶
Issue: Two identical YXWebViewClient classes in different activities.
// ViewDocWebActivity.java - Line 53
public class YXWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
}
// AddWalletAmountWebActivity.java - Line 120
public class YXWebViewClient extends WebViewClient { // ❌ EXACT DUPLICATE
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
}
Solution: Create single shared class in Stats/ package.
🟡 MEDIUM: Repeated API Header Code¶
Issue: API header construction duplicated across 100+ files.
// Pattern repeated everywhere:
.addHeaders("Authorization", "Bearer " + Utils.TokenPsyter)
.addHeaders("DeviceId", mpref.GetDeviceID())
.addHeaders("Language", Language)
.addHeaders("Culture", Culture)
Solution:
object ApiHeaderProvider {
fun getDefaultHeaders(context: Context): Map<String, String> {
val prefs = MySharedPreferences(context)
return mapOf(
"Authorization" to "Bearer ${Utils.TokenPsyter}",
"DeviceId" to prefs.GetDeviceID(),
"Language" to prefs.GetAppLanguage(),
"Culture" to getCurrentCulture()
)
}
}
// Retrofit interceptor (even better)
class HeaderInterceptor(private val context: Context) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val builder = original.newBuilder()
ApiHeaderProvider.getDefaultHeaders(context).forEach { (key, value) ->
builder.header(key, value)
}
return chain.proceed(builder.build())
}
}
Naming Conventions¶
🟡 MEDIUM: Hungarian Notation & Poor Class Names (20+ Files)¶
Issue: Inconsistent and non-standard naming conventions.
| Current Name | Issue | Recommended |
|---|---|---|
clsCameraPermission |
Hungarian notation (cls prefix) |
CameraPermissionManager |
clsRecordingPermission |
Hungarian notation | AudioPermissionManager |
clsStoragePermission |
Hungarian notation | StoragePermissionManager |
clsDrawOverAppPermission |
Hungarian + unclear | OverlayPermissionManager |
clsShow_Alarm |
Hungarian + underscore | AlarmDisplayManager |
mpref |
Abbreviation | sharedPreferences |
mAdapter |
Hungarian (m prefix) |
adapter |
MySharedPreferences |
Generic “My” prefix | AppPreferences |
MyFirebaseMessagingService |
Generic “My” | AppMessagingService |
CPPreferencesColumn |
Unclear abbreviation | CareProviderPreferences |
Example - Hungarian Notation Throughout:
// Current (bad)
public class clsCameraPermission {
public static final int REQUEST_CAMERA_PERMISSION = 200;
private static String mPermission = Manifest.permission.CAMERA;
public static void Check(Context ctx, Button btnAction, int opt) {
// ...
}
}
// Recommended
public class CameraPermissionManager {
companion object {
const val REQUEST_CODE = 200
const val PERMISSION = Manifest.permission.CAMERA
}
fun checkPermission(context: Context, button: Button, option: Int) {
// ...
}
}
Android Studio Inspection Settings:
<!-- Add to .idea/inspectionProfiles/Project_Default.xml -->
<profile>
<inspection_tool class="MethodNamingConvention" enabled="true" level="WARNING">
<option name="m_regex" value="[a-z][a-zA-Z0-9]*" />
<!-- No Hungarian notation -->
</inspection_tool>
<inspection_tool class="ClassNamingConvention" enabled="true" level="WARNING">
<option name="m_regex" value="[A-Z][a-zA-Z0-9]*" />
<!-- No prefixes like cls, My, etc. -->
</inspection_tool>
</profile>
🟡 MEDIUM: Inconsistent Variable Naming¶
Issue: Mix of camelCase, snake_case, and Hungarian notation.
// Found in same file:
private MySharedPreferences mpref; // Hungarian
private String user_id = "userid"; // snake_case
private TextView day1, day2, day3; // numeric suffix (should use array)
private LinearLayout day1Lay, day2Lay; // Abbreviated
private ImageView weekIcon, monthIcon; // Good camelCase
private Calendar c, cal, cal1; // Inconsistent abbreviations
Recommended:
private val sharedPreferences: AppPreferences // Full name
private val userId: String // camelCase
private val dayTextViews: Array<TextView> // Array instead of day1, day2...
private val dayLayouts: Array<LinearLayout> // Full words
private val weekIcon: ImageView // Consistent
private val currentCalendar: Calendar // Descriptive
private val displayCalendar: Calendar // Clear purpose
Architecture & Design Issues¶
🔴 CRITICAL: Global Mutable State (15+ Static Variables)¶
Issue: Extensive use of static mutable variables for shared state.
Examples:
// Utils.java - Global mutable state (BAD)
public class Utils {
public static String TokenPsyter = ""; // ❌ Auth token
public static String TokenScheduling = ""; // ❌ Another token
public static String UserType = ""; // ❌ User type
public static String DocumentURL = ""; // ❌ Document URL
public static String userId = ""; // ❌ User ID
public static int slotDurationId = 0; // ❌ Slot duration
public static boolean isFromClient = false; // ❌ Navigation state
// Plus 50+ more static variables...
}
// RegisterVerification.java - Mutable static flags
public static boolean isChangeNeeded = false; // ❌ State flag
public static boolean moveToEmail = false; // ❌ Navigation flag
public static boolean moveToNumber = false; // ❌ Another flag
Problems:
- Thread safety - no synchronization
- Memory leaks - static references never garbage collected
- Testing impossible - state persists between tests
- Race conditions - multiple activities modifying same static state
- Debugging nightmare - who modified the state?
Proper Solution:
// 1. Singleton with proper scoping (for app-level data)
object SessionManager {
private var _authToken: String? = null
val authToken: String?
get() = _authToken
fun setAuthToken(token: String) {
_authToken = token
}
fun clearSession() {
_authToken = null
}
}
// 2. ViewModel for screen-level state (better)
class DocumentViewModel : ViewModel() {
private val _documentUrl = MutableLiveData<String>()
val documentUrl: LiveData<String> = _documentUrl
fun setDocumentUrl(url: String) {
_documentUrl.value = url
}
}
// 3. Navigation args for activity communication (best)
// From Activity A
val intent = Intent(this, DocumentActivity::class.java)
intent.putExtra("DOCUMENT_URL", documentUrl)
startActivity(intent)
// In Activity B
val documentUrl = intent.getStringExtra("DOCUMENT_URL")
// Or with Navigation Component
val action = MainFragmentDirections.actionMainToDocument(documentUrl)
findNavController().navigate(action)
🟠 HIGH: AsyncTask Usage (Deprecated Since API 30)¶
Issue: Using deprecated AsyncTask instead of Kotlin Coroutines.
Found in:
// CalendarCustomView.java - Line 324
private class DoCalculationTask extends AsyncTask<String, Void, Void> {
@Override
protected Void doInBackground(String... params) {
// Complex calculation on background thread
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
// Update UI
}
}
// Usage
new DoCalculationTask().execute(param1, param2);
Migration to Coroutines:
class CalendarViewModel : ViewModel() {
fun performCalculation(param1: String, param2: String) {
viewModelScope.launch {
// Switch to IO dispatcher
val result = withContext(Dispatchers.IO) {
// Background work
complexCalculation(param1, param2)
}
// Automatically back on Main thread
updateUI(result)
}
}
private suspend fun complexCalculation(p1: String, p2: String): Result {
// Heavy computation
return Result()
}
private fun updateUI(result: Result) {
// Update LiveData/StateFlow
}
}
🟠 HIGH: Tight Coupling to Activities¶
Issue: Business logic embedded in Activities instead of ViewModels/Presenters.
Example:
// LoginActivity.java - 1500+ LOC
public class LoginActivity extends AppCompatActivity {
// ❌ API logic in Activity
private void performLogin() {
AndroidNetworking.post(BaseURLS.BaseURL + "api/Login")
.addBodyParameter("username", username)
.addBodyParameter("password", password)
.build()
.getAsJSONObject(new JSONObjectRequestListener() {
@Override
public void onResponse(JSONObject response) {
// ❌ JSON parsing in Activity
try {
String token = response.getString("token");
String userId = response.getString("userId");
// ❌ SharedPreferences access in Activity
mpref.SetAuthClaimToken(token);
mpref.SetUserID(userId);
// ❌ Navigation logic mixed with data logic
if (userType.equals("1")) {
startActivity(new Intent(this, CareProviderMain.class));
} else {
startActivity(new Intent(this, ClientMain.class));
}
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onError(ANError error) {
Toast.makeText(LoginActivity.this, "Error", Toast.LENGTH_SHORT).show();
}
});
}
}
Proper MVVM Architecture:
// 1. Data Layer - Repository
class AuthRepository(
private val api: AuthApi,
private val preferences: AppPreferences
) {
suspend fun login(username: String, password: String): Result<User> {
return try {
val response = api.login(username, password)
preferences.saveAuthToken(response.token)
preferences.saveUserId(response.userId)
Result.Success(response.user)
} catch (e: Exception) {
Result.Error(e)
}
}
}
// 2. ViewModel - Business Logic
class LoginViewModel(
private val repository: AuthRepository
) : ViewModel() {
private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
val loginState: StateFlow<LoginState> = _loginState
fun login(username: String, password: String) {
viewModelScope.launch {
_loginState.value = LoginState.Loading
val result = repository.login(username, password)
_loginState.value = when (result) {
is Result.Success -> LoginState.Success(result.data)
is Result.Error -> LoginState.Error(result.exception.message)
}
}
}
}
sealed class LoginState {
object Idle : LoginState()
object Loading : LoginState()
data class Success(val user: User) : LoginState()
data class Error(val message: String?) : LoginState()
}
// 3. Activity/Fragment - UI Only (<200 LOC)
class LoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
observeLoginState()
setupClickListeners()
}
private fun observeLoginState() {
lifecycleScope.launch {
viewModel.loginState.collect { state ->
when (state) {
LoginState.Idle -> hideLoading()
LoginState.Loading -> showLoading()
is LoginState.Success -> navigateToMain(state.user)
is LoginState.Error -> showError(state.message)
}
}
}
}
private fun setupClickListeners() {
binding.loginButton.setOnClickListener {
viewModel.login(
binding.usernameInput.text.toString(),
binding.passwordInput.text.toString()
)
}
}
private fun navigateToMain(user: User) {
val intent = when (user.type) {
UserType.CARE_PROVIDER -> Intent(this, CareProviderMain::class.java)
UserType.CLIENT -> Intent(this, ClientMain::class.java)
}
startActivity(intent)
finish()
}
}
Memory & Performance¶
🟠 HIGH: Memory Leaks from Static Context References¶
Issue: Storing Activity/Context in static variables.
// Utils.java
public class Utils {
public static Context context; // ❌ MEMORY LEAK
public static void setContext(Context ctx) {
context = ctx; // ❌ Activity reference never released
}
}
// Usage in Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
Utils.setContext(this); // ❌ This Activity will never be garbage collected
}
Impact: Activities remain in memory after destruction, causing OutOfMemoryError.
Fix:
// Use Application context for singletons
class MyApplication : Application() {
companion object {
lateinit var instance: MyApplication
private set
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
// Or use dependency injection
@Singleton
class Utils @Inject constructor(
@ApplicationContext private val context: Context // Application context, not Activity
) {
// ...
}
🟡 MEDIUM: Excessive findViewById Calls¶
Issue: Repeated findViewById without caching.
// Typical pattern - called multiple times
public void updateUI() {
((TextView) findViewById(R.id.title)).setText("Title"); // ❌ Find view
((TextView) findViewById(R.id.subtitle)).setText("Subtitle"); // ❌ Find again
((ImageView) findViewById(R.id.icon)).setImageResource(R.drawable.ic_icon);
}
public void clearUI() {
((TextView) findViewById(R.id.title)).setText(""); // ❌ Find view again
((TextView) findViewById(R.id.subtitle)).setText("");
}
Solution - ViewBinding:
class MyActivity : AppCompatActivity() {
private lateinit var binding: ActivityMyBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMyBinding.inflate(layoutInflater)
setContentView(binding.root)
updateUI()
}
private fun updateUI() {
binding.title.text = "Title" // Direct reference, no findViewById
binding.subtitle.text = "Subtitle"
binding.icon.setImageResource(R.drawable.ic_icon)
}
}
Code Smells¶
🟡 MEDIUM: Magic Numbers & Strings¶
Issue: Hardcoded values throughout code.
// Magic numbers everywhere
if (userType.equals("1")) { // ❌ What is "1"?
// Care provider logic
} else if (userType.equals("0")) { // ❌ What is "0"?
// Client logic
}
if (status == 2) { // ❌ What does 2 mean?
// Approved
} else if (status == 3) { // ❌ What does 3 mean?
// Rejected
}
// Magic strings
String url = BaseURLS.BaseURL + "api/Login"; // ❌ Hardcoded endpoint
Solution:
// Constants
object UserType {
const val CARE_PROVIDER = "1"
const val CLIENT = "0"
}
enum class AppointmentStatus(val code: Int) {
PENDING(1),
APPROVED(2),
REJECTED(3),
CANCELLED(4)
}
object ApiEndpoints {
const val LOGIN = "api/Login"
const val REGISTER = "api/Register"
const val GET_APPOINTMENTS = "api/Appointments"
}
// Usage
if (userType == UserType.CARE_PROVIDER) {
// Clear and self-documenting
}
when (AppointmentStatus.fromCode(status)) {
AppointmentStatus.APPROVED -> handleApproved()
AppointmentStatus.REJECTED -> handleRejected()
else -> handlePending()
}
🟡 MEDIUM: Commented-Out Code (50+ Blocks)¶
Issue: Large blocks of commented code throughout the codebase.
Examples:
// MyFirebaseInstanceIdService.java - Lines 19-51 (ENTIRE SERVICE commented)
//public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {
// private static final String TAG = "MyFirebaseIIDService";
// @Override
// public void onNewToken(@NonNull String s) {
// String CurrentToken = FirebaseInstanceId.getInstance().getToken();
// ... 30 more lines ...
// }
//}
// ViewDocWebActivity.java - Line 36
//settings.setJavaScriptEnabled(true); // ❌ Dangerous if uncommented
// LoginActivity.java - Lines 521-522
//} catch (Exception e) {
// e.printStackTrace();
//}
// Many more instances...
Action Required:
- Delete all commented code - use Git for history
- If code might be needed, create feature flag instead
🟡 MEDIUM: Inconsistent TODO Comments (50+)¶
Issue: 50+ “TODO Auto-generated method stub” comments never addressed.
// Repeated pattern (auto-generated, never removed):
@Override
public void onClick(View v) {
// TODO Auto-generated method stub // ❌ Template comment, no actual TODO
}
@Override
public void onError(ANError error) {
// TODO Auto-generated method stub // ❌ Should have error handling
}
Better Approach:
// Remove placeholder comments
override fun onClick(view: View) {
when (view.id) {
R.id.login_button -> performLogin()
R.id.register_button -> navigateToRegister()
}
}
// Or if truly incomplete:
override fun onError(error: ANError) {
// TODO: Implement proper error handling for network failures
// Priority: High, Assigned: @username, Ticket: PSY-123
showGenericError()
}
Deprecated & Dead Code¶
🟡 MEDIUM: Entire Commented Service Classes¶
File: MyFirebaseInstanceIdService.java
Status: Entire file commented out (50+ lines)
//public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {
// // Entire service implementation commented...
//}
Action: Delete file, functionality moved to MyFirebaseMessagingService.
🟡 MEDIUM: Legacy WebRTC Code Still Present¶
File: Collaboration/Presence/CollaborationMain.java
Status: Old video call implementation (deprecated)
// Old WebRTC implementation still in codebase
public class CollaborationMain extends AppCompatActivity {
// ❌ Legacy code - replaced by VideoSDK (OneToOneCallActivity.kt)
// Still occupies ~500 LOC
}
Files to Delete:
- Collaboration/Presence/CollaborationMain.java
- Collaboration/Presence/WebAPICall.java (hardcoded test credentials)
- Collaboration/Presence/NaiveSSLContext.java (disables SSL verification!)
- Collaboration/Presence/StatusAdapter.java
- Collaboration/Presence/IncomingCall.java
Estimated Cleanup: Remove ~2,000 LOC of dead code
Documentation Issues¶
🟠 HIGH: No JavaDoc for Public APIs¶
Issue: Public methods lack documentation.
// Current (no documentation)
public class Utils {
public static String ConvertDate(String dob) { // ❌ What format? What does it convert to?
// Complex date conversion logic
return convertedDate;
}
public static void showAlertDialog(Context context, String title, String message, String type) {
// ❌ What is "type"? What values are valid?
}
}
Recommended:
/**
* Converts a date string from one format to another.
*
* @param dob Date of birth in format "dd/MM/yyyy"
* @return Formatted date string in "yyyy-MM-dd" format
* @throws ParseException if input date format is invalid
*/
fun convertDate(dob: String): String {
val inputFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
val outputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val date = inputFormat.parse(dob) ?: throw ParseException("Invalid date format", 0)
return outputFormat.format(date)
}
/**
* Displays an alert dialog to the user.
*
* @param context Android context for dialog creation
* @param title Dialog title text
* @param message Dialog body message
* @param type Dialog type: "success", "error", "warning", or "info"
*/
fun showAlertDialog(
context: Context,
title: String,
message: String,
@DialogType type: String
) {
// Implementation
}
@StringDef(TYPE_SUCCESS, TYPE_ERROR, TYPE_WARNING, TYPE_INFO)
@Retention(AnnotationRetention.SOURCE)
annotation class DialogType
companion object {
const val TYPE_SUCCESS = "success"
const val TYPE_ERROR = "error"
const val TYPE_WARNING = "warning"
const val TYPE_INFO = "info"
}
Refactoring Recommendations¶
Priority 1: Critical Refactoring (Do Now)¶
| Task | Impact | Files Affected |
|---|---|---|
| Break up God Classes | High | 5 files (2,500+ LOC → 500 LOC each) |
| Fix Exception Handling | Critical | 100+ files |
| Remove Deprecated AsyncTask | Medium | 10+ files |
| Delete Dead Code | Low | 15 files (2,000 LOC) |
Week 1:
- Refactor CalendarCustomView.java (2,963 → 500 LOC)
- Extract CalendarViewModel
- Extract CalendarRepository
- Extract DateUtils
Week 2:
- Refactor WeeklyScheduleFragment.java (2,824 → 500 LOC)
- Extract ScheduleViewModel
- Extract SlotManager
- Extract ValidationUtils
Week 3:
- Fix exception handling in critical paths
- Payment processing
- Patient data operations
- Authentication flows
Week 4:
- Remove deprecated code
- Delete legacy WebRTC files
- Remove commented code
- Delete unused classes
Priority 2: High Priority (Do Next)¶
| Task | Impact |
|---|---|
| Consolidate Permission Classes | Medium |
| Migrate to ViewBinding | Medium |
| Remove Static State | High |
| Add Proper Error Logging | High |
| Implement Retry Logic | Medium |
Priority 3: Long-term Improvements (Plan)¶
| Task | Impact |
|---|---|
| Full Kotlin Migration | High |
| Migrate to Jetpack Compose | Medium |
| Implement MVVM Throughout | High |
| Add Unit Tests | High |
| Setup CI/CD with Quality Gates | Medium |
Recommended Tools & Metrics¶
Static Analysis Tools¶
# SonarQube
./gradlew sonarqube
# Android Lint
./gradlew lint
# Detekt (Kotlin static analysis)
./gradlew detekt
# Checkstyle
./gradlew checkstyle
Quality Gates (CI/CD)¶
# GitHub Actions / Azure Pipelines
quality_gates:
- code_coverage: >= 60%
- duplicated_code: < 3%
- critical_issues: 0
- high_issues: < 5
- code_smells: < 50
- technical_debt_ratio: < 10%
Metrics to Track¶
| Metric | Current | Target | Deadline |
|---|---|---|---|
| Lines of Code | ~150,000 | ~120,000 | 3 months |
| Files >500 LOC | 20+ | <5 | 2 months |
| Code Coverage | ~10% | 60% | 6 months |
| Code Duplication | ~30% | <5% | 3 months |
| Cyclomatic Complexity | High | Medium | 3 months |
| Technical Debt Ratio | ~20% | <5% | 6 months |
Conclusion¶
The AndroidCareProvider codebase suffers from significant technical debt accumulated over time. The primary issues are:
- God Classes - Files exceeding 2,000 LOC need immediate refactoring
- Poor Error Handling - 100+ generic exception catches hiding bugs
- Code Duplication - 30%+ duplicated code (permission classes, API calls)
- Static Mutable State - 15+ static variables causing memory leaks and race conditions
- Deprecated APIs - AsyncTask, old Firebase services still in use
Immediate Actions (This Sprint):¶
✅ Week 1: Fix critical exception handling in payment & auth
✅ Week 2: Refactor CalendarCustomView.java (extract ViewModel + Repository)
✅ Week 3: Delete dead code (legacy WebRTC, commented services)
✅ Week 4: Consolidate permission classes into single manager
Document Status: Complete
Next Steps: UX Review
Maintained By: Engineering Team
Version: 1.0