Overview › MainActivity.java — the 5,411-line god-object

MainActivity.java — the 5,411-line god-object

Generated 2026-05-11 · Costco Android

The file that runs everything

Costco/src/main/java/com/costco/app/android/ui/main/MainActivity.java is a single Java class containing 5,411 lines with 100+ public methods across 20+ unrelated responsibilities. It is the single largest file in the app module and the locus of more concrete findings than any other class — concentrated crash risks, lifecycle issues, deep-link handling, navigation, WebView orchestration, biometric flows, and analytics initialization all flow through this one file.

What's in it (sampled responsibilities)

ConcernIndicative methodsShould live in
Tab / bottom-nav coordinationsetupBottomNav, handleTabReselected, switchToTabBottomTabCoordinator
Deep-link routing11+ intent filters → routing dispatch within onNewIntent + helpersDeepLinkRouter
WebView orchestrationgetMainWebViewFragment, refreshWebView, loadWebViewUrlMainWebViewController
Biometric & passkey gateshandleBiometricResult, requestPasskeyAuthGate use case
App-link verificationSecureKeyMembershipAccountVerification handlingMembershipVerificationFlow
Analytics + Adobe initAdobe Marketing Cloud bootstrap callsAnalyticsInitializer
Geofence registrationPermission flow + geofence client setupGeofenceCoordinator
Pharmacy embedded WebViewshowPharmacyWebView, intent routingPharmacyFlow
Misc Handler.postDelayed UI tweaks~6 inline Handler().postDelayed sites

Findings concentrated here

The static review identified 10+ findings rooted in this file:

Code excerpt — concentrated risk

// MainActivity.java — illustrative composite of patterns observed

// Line 1864: Handler with no Looper, no cleanup
new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        // captures Activity strongly; runs after activity destroyed if user backs out
        refreshSomething();
    }
}, 1000);

// Line 3901: 3-second deferred work — survives orientation change
new Handler().postDelayed(() -> {
    showOnboardingPrompt();
}, 3000);

// Line 4489: NPE chain (getExtras() may be null)
String tab = getIntent().getExtras().getString(KEY_TAB);

// Line 1266: index 0 on possibly-empty list
ShortcutInfo first = items.get(0);

// Line 3891: Dynamic URL handed to ACTION_VIEW
startActivity(new Intent(ACTION_VIEW, Uri.parse(inboxUrl)));

Refactor strategy — incremental, not rewrite

A 5,411-line file cannot be safely rewritten in one PR. Extract concerns one at a time, each behind an interface, with the existing tests as the safety net. Recommended sequence:

  1. Sprint 1 — Extract DeepLinkRouter (the largest cluster). MainActivity holds only router.handle(intent). New router has its own unit tests.
  2. Sprint 2 — Extract BottomTabCoordinator + MainWebViewController. Replace direct fragment manipulation with delegate calls.
  3. Sprint 3 — Extract HandlerCleanupHost — a tiny helper that owns all Handler.postDelayed calls and clears them in onDestroy. Closes the 6 leak findings in one PR.
  4. Sprint 4 — Migrate MainActivity.java to MainActivity.kt. With most concerns extracted, the conversion is straightforward and unlocks Kotlin idioms (sealed classes, scope functions, nullable types).
  5. Sprint 5 — Move analytics + geofence + biometric initialization to androidx.startup.Initializer. Reduces cold-start work and eliminates onCreate bloat.

Refactor recipe — extracting deep-link routing

// Before — in MainActivity
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    String action = intent.getAction();
    if (Intent.ACTION_VIEW.equals(action) && intent.getData() != null) {
        Uri data = intent.getData();
        if (data.getHost() != null && data.getHost().contains("costco.ca")) {
            // 200 lines of dispatch logic ...
        }
    }
}

// After — extracted
class DeepLinkRouter @Inject constructor(
    private val authGate: AuthGate,
    private val tabCoordinator: BottomTabCoordinator,
    private val webViewController: MainWebViewController,
    private val analytics: AnalyticsClient
) {
    fun handle(intent: Intent): RoutingResult {
        val data = intent.data ?: return RoutingResult.NoOp
        if (!isCostcoHost(data.host)) return RoutingResult.External
        return when {
            data.path?.contains("/SecureKeyMembership") == true ->
                authGate.handleMembershipVerification(data)
            data.path?.contains("/warehouse/locator") == true ->
                tabCoordinator.switchToWarehouse(data)
            else -> webViewController.loadUrl(data.toString())
        }
    }

    private fun isCostcoHost(host: String?): Boolean =
        host != null && ALLOWED_HOSTS.any { host.endsWith(it) }

    companion object {
        private val ALLOWED_HOSTS = listOf("costco.com", "costco.ca", "m.costco.com", "m.costco.ca")
    }
}

// MainActivity becomes:
@Inject lateinit var router: DeepLinkRouter

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    when (router.handle(intent)) {
        RoutingResult.NoOp -> { /* no-op */ }
        RoutingResult.External -> { /* let system handle */ }
        is RoutingResult.Handled -> { /* tab/webview already updated */ }
    }
}

Verification (how to confirm the refactor lands cleanly)

Why fixing this matters more than other files

Refactoring MainActivity.java unlocks the entire app module. New engineers stop being intimidated. Code review on changes becomes possible (rather than rubber-stamping). Test coverage rises because the extracted pieces are testable. And five separate categories of finding (crash, lifecycle, deprecated, performance, code quality) all see their counts drop simultaneously. This is the single highest-leverage refactor in the Android codebase.

Costco Android · Code Review Report · Generated 2026-05-11 · 626 machine-curated findings