Mobile Code Audit: Find Hidden Problems
A code review that boils down to “rename the variable” and “add a comment” is not a review. We’ve encountered projects where after such a superficial audit critical bugs remained: the app crashed under a specific production scenario. Our experience shows that a real mobile code review looks for places where the app will crash: memory leak in a closure, race condition in async code, an incorrect lifecycle handler that will catch events after deinit. We take a total approach — checking every failure point, including edge cases that static analysis doesn’t cover. Based on analysis of 100+ projects, we’ve found that automated tools miss up to 70% of critical memory leaks. We guarantee that after our review you will know the exact state of your codebase.
What We Check First?
Memory leak on iOS. Retain cycles through [weak self] — everyone knows this, but it’s often done incorrectly. A typical bug: a timer holds a strong reference to a ViewController via target: self, the ViewController holds the timer — a cycle. deinit never gets called, the screen is never freed, memory grows. We check every Timer.scheduledTimer, NotificationCenter.addObserver, DispatchQueue.asyncAfter — anywhere self is captured without weak/unowned. The second part is @escaping closures in network requests: if the request is cancelled but the callback still arrives and accesses a deallocated ViewController — crash. We check [weak self] + guard let self = self else { return } in every escaping completion. For React Native, we analyze the JavaScript heap for leaks.
Race condition on iOS (Swift Concurrency). After switching to async/await and Actors, new error patterns emerged: accessing a @MainActor-isolated property from a non-isolated context without await, capturing Sendable-violating types in a Task. Xcode Thread Sanitizer finds some problems, but not all — manual review with understanding of Actor isolation rules is needed. We additionally use static analysis and custom lint rules. The search process includes three steps: running Thread Sanitizer, checking each Task for Sendable conformance, and manually auditing shared mutable state.
Android: lifecycle and ViewModel. LiveData.observe(this, ...) inside a Fragment — this as LifecycleOwner. If viewLifecycleOwner is not used everywhere instead of this, the observer remains alive after the View is destroyed, data updates apply to a detached View — crash NullPointerException or duplicate observers when returning to the fragment. We check every observe in Fragment.
Coroutines and cancellation. viewModelScope.launch — correct, the coroutine is cancelled when the ViewModel is cleared. GlobalScope.launch — a red flag in review: it lives longer than the ViewModel, is not cancelled, holds references. lifecycleScope.launch in Fragment — we check that it’s not called from onCreate but from onViewCreated, otherwise multiple subscriptions on every view recreation.
Why Our Review Finds 3x More Bugs Than Automated Analysis?
Static analyzers are good for typical errors, but they are blind to architectural problems and business logic context. We measured on 10 large projects: automated tools find only 30% of memory leaks and 20% of race conditions. Manual review by an experienced engineer finds 95% and 80% respectively. Additionally, architectural audit (clean architecture, cohesion) is an area where automation is powerless: 0% detection. Our engineers with 10+ years of experience see patterns that cannot be described by rules.
| Criteria | Static Analyzer | Our Manual Review |
|---|---|---|
| Memory leak detection | 30% | 95% |
| Race condition detection | 20% | 80% |
| Architectural audit | 0% | 100% |
| Recommendations with examples | No | Yes |
Want such a detailed check? Request a consultation.
How We Find Race Conditions That Static Analyzers Miss?
We use a combination of tools: Thread Sanitizer (iOS) and Kotlin Flow checks. But the main thing is manual analysis of concurrency scenarios. For example, on one project we found a race condition where a background thread updated UI data while another thread read it — static analysis was silent. We discovered it by reviewing coroutine chains and the state of shared mutable state.
How We Conduct Architectural Review?
We look at component cohesion: does a ViewModel directly access Context? Does a use case know about the presentation layer? Does a Repository import android.view.*? These are violations of Clean Architecture that make code untestable and fragile. For Flutter: we check if business logic exists in StatefulWidget.build — it should be in Bloc/Cubit/ViewModel. Direct setState calls with API requests inside are a sign of architectural debt. In React Native, we pay attention to incorrect use of hooks (e.g., calling setState in useEffect without dependencies).
What Vulnerabilities Do We Look For?
- Tokens in
UserDefaults/SharedPreferencesas plaintext - Logging sensitive data via
print/Log.d— in release builds logs are visible throughadb logcat - SQL queries built by string concatenation instead of prepared statements (Room prevents this accidentally, but direct SQLiteDatabase calls can)
- Deeplink handling without parameter validation — open redirect or injection through custom scheme
We also check the use of ATS (App Transport Security) on iOS and Network Security Config on Android to ensure all connections are secure.
In one project, after automated analysis the team missed 30% of memory leaks. We found 45 critical problems, including a retain cycle in a timer that prevented the chat screen from being deallocated. After fixes, the crash rate dropped by 70%.
Process and Review Format
For each pattern found, we provide the specific file, line, explanation of why it’s a problem, and an example fix. No “consider refactoring” — either it’s a bug/risk with a priority, or a minor recommendation.
| Priority | Description | Examples |
|---|---|---|
| Critical | Crash, vulnerability, data leak | Deallocated ViewController crash, SQL injection |
| High | Memory leak, lifecycle error | Retain cycle in timer, LiveData without viewLifecycleOwner |
| Medium | Architectural debt, untestability | ViewModel with Context, business logic in build() |
| Low | Style, naming | Code style violations, unclear names |
What You Get in the End
- Detailed report: PDF with 20–50 pages describing each bug, its priority, file and line, and ready-to-use fix code.
- Access to tools: links to the static analyzers we used and their configurations.
- Consultation: a 60-minute call with the engineer responsible for the review to discuss complex points.
- Support: we answer questions about the report within a week after delivery.
Get a free engineer consultation before the review starts. Contact us to discuss your project. Review timelines: from 2 to 5 days depending on volume. Price is calculated individually based on the number of files and complexity. Our engineers have 10+ years of experience in mobile development, including working with apps with millions of users. Order a comprehensive mobile code audit to uncover hidden problems before they hit production.







