Conducting Code Review for Mobile Apps
Code review that amounts to "rename the variable" and "add a comment" isn't really a review. Real mobile code review finds places where the app will crash in production: memory leak in closure, race condition in async code, incorrect lifecycle handler that catches events after deinit.
What to Check First
Memory management on iOS. Retain cycles through [weak self] — everyone knows this, but often gets it wrong. Typical bug: timer holds strong reference to ViewController via target: self, ViewController holds timer — cycle. deinit never gets called, screen doesn't free, memory grows. Check all Timer.scheduledTimer, NotificationCenter.addObserver, DispatchQueue.asyncAfter — everywhere self is captured without weak/unowned.
Second part — @escaping closures in network requests: if request is canceled but callback still arrives and accesses deallocated ViewController — crash. Check [weak self] + guard let self = self else { return } in every escaping completion.
Concurrency and data races on iOS (Swift Concurrency). After transitioning to async/await and Actors, new error patterns emerged: accessing @MainActor-isolated property from non-isolated context without await, capturing Sendable-violating types in Task. Xcode Thread Sanitizer finds some issues, but not all — manual review with understanding of Actor isolation rules is needed.
Android: lifecycle and ViewModel. LiveData.observe(this, ...) inside Fragment — this as LifecycleOwner. If viewLifecycleOwner isn't used consistently, observer stays alive after View destruction, data updates apply to detached View — crash NullPointerException or duplicate observers on returning to fragment. Check every observe in Fragment.
Coroutines and cancellation. viewModelScope.launch — correct, coroutine cancels on ViewModel cleanup. GlobalScope.launch — red flag in review: outlives ViewModel, not canceled, holds references. lifecycleScope.launch in Fragment — check not launching from onCreate but from onViewCreated, otherwise multiple subscriptions on every view recreation.
Architectural Patterns
Look at component coupling: ViewModel directly accessing Context? Use case aware of presentation layer? Repository importing android.view.*? These are Clean Architecture violations making code untestable and fragile.
For Flutter: check no business logic in StatefulWidget.build — should be in Bloc/Cubit/ViewModel. Direct setState calls with API requests inside — sign of architectural debt.
Security and Typical Vulnerabilities
- Tokens in
UserDefaults/SharedPreferencesplaintext - Logging sensitive data via
print/Log.d— in release build logs are visible throughadb logcat - SQL queries via string concatenation instead of prepared statements (Room prevents this accidentally, but direct SQLiteDatabase calls can)
- Deeplink handling without parameter validation — open redirect or injection via custom scheme
Review Format
For each found pattern — specific file, line, explanation why it's a problem, and example fix. No "should consider refactoring" — either it's a bug/risk with priority, or minor recommendation.
Prioritization: Critical (crash, vulnerability, data leak), High (memory leak, wrong lifecycle), Medium (architectural debt, untestability), Low (style, naming).
Timeline — 2–3 days on medium-sized project (50–100 files). Large codebases (200+ files) — up to 5 days.







