Our mobile app is central to users experiencing Simpl — helping people make payments, track expenses, manage their pay-later cycles and discover merchants. To keep the user journey smooth, secure, and compliant with platform requirements, we periodically upgrade our core technology stack.
We recently moved the target and compile SDK versions from 34 to 35 in our Android app, which necessitated movement from React Native 0.75.2 to 0.79.2.
This upgrade was unexpectedly involved for us. This article describes why we needed to do the upgrade, the difficulties that we encountered and we solved those difficulties.
Why We Had to Upgrade
First, let's see the basic outline of Simpl mobile app.
80-85% of the mobile app code of Simpl is written in react native, and we use native android and iOS code for the core functionalities like SIM binding, camera and contact list related functionalities.
We recently leapt from React Native 0.75.2 → 0.79.2. On paper, it looks like a small version bump. In reality, it meant fixing critical crashes, staying ahead of Google Play Store's policy deadlines and future proofing our app for Android 15.
Crashes & Compliance
For most of the year, we were busy in implementing new features in the app, such as Select - which is the loyalty program for Simpl app, Quest - which is a rewards program, and OTM (one time mandate) using which users who don't have a khata with Simpl can block the money in their bank account and then get a khata with Simpl.
As a result, stability and compliance took a back seat. However, when the problems became sufficiently severe and some deadlines set by Google Play Store were on the horizon, we moved first to fix our app to resolve various stability and compliance related problems.
Firstly, there was a stability problem with the app. We used Digio SDK in our app to help us with eKYC of the users. Digio app used camera of the device to accomplish eKYC and that functionality required Android API level 35, so our app must be compileSdkVersion = 35
. However, we we were at compileSdkVersion = 34
, and this was leading to 2-3% our users being hit by a crash as they tried to do their eKYC.
Secondly, Google Play Store mandated that all apps must target the API level 35 from August 31, 2025. Our app was still on API level 34, which meant were getting close to non compliance.
Thirdly, Play Store also mandated that starting November 1, 2025, all new apps and updates to existing apps submitted to Google Play and targeting Android 15+ devices must support 16 KB page sizes on 64-bit devices. This needed react native version upgrade to 0.77.
We kept minSdkVersion = 24
(Android 7.0). So yes, that trusty Moto G from 2016 still gets love ❤️.
What’s Changing Under the Hood
To better understand the changes, let's first get clarity on what is meant by minSdkVersion
, targetSdkVersion
and compileSdkVersion
.
minSdkVersion
is the minimum supported android framework version in the device. E.g. if minSdkVersion = 34
, then our app won't install on a device which has android framework version of 33
.
compileSdkVersion
is the version whose API has been used to compile the app. E.g. if compileSdkVersion = 35
, then our app can use APIs introduced in Android 15. However, using an API higher than the device version requires a runtime check.
targetSdkVersion
is the highest API level with which the app has been designed, tested, and confirmed compatible. This is the version where the app is expected to behave "as intended."
In the above measures, following table summarizes the state of our app before and after the upgrade
Setting | Before | After |
minSdkVersion | 24 | 24 (unchanged) |
targetSdkVersion | 34 | 35 |
compileSdkVersion | 34 | 35 |
buildToolsVersion | 34.0.0 | 35.0.0 |
React Native | 0.75.2 | 0.79.2 |
We could have taken the “safe” route and inched forward through 0.76.x and 0.77.x. Instead, we made a leap straight to 0.79.2.
Firstly, we decided the bigger jump would lead to fewer upgrade cycles ahead, saving time and effort in the near future. Secondly, moving to 0.79.2 gets us the bug fixes done in that version.
Our Upgrade Process
Step 1: The Checklist
We ran npm outdated
to get the outdated project dependencies. Other than that we verified that all our development environments (node, ruby, JDK, Xcode) met the new target version's need.
Besides, we made a list of critical user flows using native code so that we make sure to test them post development to ensure that nothing is broken.
With the audit complete and our risks mapped, we safely isolated the work:
git checkout -b feature/rn-upgrade-0.79.2
Step 2: Core React Native Upgrade
For the Core package update, we relied heavily on the React Native Upgrade Helper. This tool provided a necessary side-by-side diff that eliminated hours of guesswork.
We systematically applied the changes it recommended, focusing on stabilizing the native build environment. Key configuration files that required careful manual merging included:
- Android:
android/build.gradle
andandroid/app/build.gradle
(for SDK and Gradle alignment). - iOS:
ios/Podfile
(for dependency). - Metro:
metro.config.js
(for bundler updates).
Step 3: Dependency and Environment Stabilization
This is where the real challenges begin. A React Native upgrade is never about updating the framework alone—it’s a massive dependency alignment project where every third-party library demanded attention.

The core challenge wasn't just version bumping; it was deep-diving into native module behavior. We faced two major bottlenecks:
The Firebase Failures
Updating the core framework broke critical components of RN Firebase (Push Notifications, Remote Config, etc.), severely impacting stability.
The Linear Gradient Layout Bugs
The react-native-linear-gradient
library, a key aesthetic component, was plagued by two issues stemming from the new RN 0.79 layout engine.
By fixing these critical issues and aligning all our core Build Tools (Gradle to 8.x, CocoaPods to 1.15.2), we finally achieved a stable, working build on the new React Native foundation.
Dev challenges that we faced
During the implementation of above mentioned upgrades, we faced the following problems.
1.Build errors due to cached dependencies
Old caches caused build errors and the inevitable "works on my machine" arguments. A thorough cache flush was a mandatory step to eliminate residual artifacts and ensure a clean, stable environment for the new React Native version.
At the build time as well as at the run time we did the following -
- Native Builds: We deleted local build artifacts (
rm -rf android/.gradle android/build
). - Dependencies: We removed JavaScript dependencies (
rm -rf node_modules
). - iOS Environment: We cleaned the CocoaPods setup (
cd ios && pod cache clean --all && rm -rf Pods && pod install
). - Bundler: Finally, we reset the Metro bundler (
npm start -- --reset-cache
).
2. Backward Compatibility
Even while targeting new SDKs (like Android 15), we were committed to supporting our older user base. We achieved this stability by implementing conditional logic throughout our app, ensuring that new native features and API calls always had a stable fallback path:
import { Platform } from 'react-native';
if (Platform.OS === 'android' && Platform.Version >= 35) {
// Android 15 features
} else {
// Older versions fallback
}
- Common Challenges & Quick Fixes
Here are some of more challenges that faced and how to got over them.
Firstly, for build failures, we ensured that we update gradle and AGP (android gradle plugin). A good fraction of build failures we solved by using updated gradle and AGP.
Secondly, for problems related to third party libraries, we read the changelogs of third party libraries and read the github issues of those libraries. For instance, for the padding problem that we described above, on searching in the github for the problem, we found the patch that had not yet been merged. So, we merged the patch in our codebase to solve the problem.
From Planning to Rollout
Team Workflow & Execution
This entire upgrade was managed primarily by a single dedicated engineer, ensuring clear ownership and efficient decision-making. The core goal was to minimize disruption, and we succeeded: the rest of the team continued shipping new features without major interruptions. When a dependency required deeper, specialized knowledge, we brought in module experts. When stability required verification, the QA team executed focused regression testing. By keeping ownership clear and collaboration focused, the upgrade progressed efficiently in the background.
Post-Upgrade Validation
After the upgrade was complete, the app was thoroughly validated across three layers: stability, performance, and compatibility.
- Automated Testing: Our Jest and snapshot test suites ran successfully, confirming the integrity of the JavaScript logic and component rendering.
- Manual QA: The QA team meticulously verified all critical user flows—including payments, login, and checkout—across a wide range of devices, from older Moto G models to the latest Pixel 8, ensuring broad OS and device compatibility.
- Performance & Monitoring: Performance benchmarks confirmed a noticeable improvement in startup times. Real-time monitoring through Firebase Crashlytics and Sentry provided the final proof, confirming that no regressions or new stability issues were present in the upgraded build.
This multi-layered validation strategy provided the confidence needed to proceed with the final rollout.
Rollout plan
To minimize user impact and risk, we adopted a phased rollout strategy. Each stage was carefully monitored, allowing any issues to be caught and fixed rapidly before impacting the wider user base. This active monitoring was crucial for ensuring a stable, controlled deployment.
The rollout plan looked like this:

(Crash monitoring + performance tracking at each stage ensured stability before moving forward.)
Upgrade Results
The upgrade went smoothly and proved to be stable. Users experienced fewer crashes, and the app maintained reliability across both Android and iOS. It fully met Google Play’s latest compliance requirements, and new Android 15 protections strengthened user security.

Performance improved noticeably — the app started faster, felt more responsive, and the bundle became leaner. At the same time, backward compatibility was preserved, so even users on older devices continued to enjoy a smooth and stable experience. Overall, the upgrade not only enhanced performance and reliability but also positioned the app to better handle future platform updates.
Lessons Learned
A few key practices made the upgrade successful. Having a single owner for the process created clear accountability, while documenting each step improved transparency. Validating changes in small increments helped reduce risk, and continuous monitoring ensured that rollout decisions were backed by real data.
The process also highlighted areas for improvement. Starting dependency upgrades earlier would prevent last-minute blockers, running smoke tests on older devices sooner would catch regressions faster, involving QA earlier would spread testing more evenly, and documenting native changes in real time would make handoffs smoother.
The Way Forward
With the upgrade complete, the app is in a much stronger position. Build times are faster, long-standing technical blockers have been resolved, and improved startup time. This foundation enables the team to adopt upcoming React Native features more easily and stay aligned with future platform requirements.
The journey was complex but worthwhile. Our careful planning, rigorous dependency management, and thorough device validation helped us improve stability, performance, and compliance for every user. Beyond the immediate gains, the process has strengthened the entire engineering team, equipping us with better practices for a smoother, faster, and more predictable next iteration.