Skip to content

Ios track downloaded splits#258

Merged
balaganesh-juspay merged 1 commit intomainfrom
ios-track-downloaded-splits
Mar 26, 2026
Merged

Ios track downloaded splits#258
balaganesh-juspay merged 1 commit intomainfrom
ios-track-downloaded-splits

Conversation

@Yash02Rajput
Copy link
Copy Markdown
Contributor

@Yash02Rajput Yash02Rajput commented Feb 20, 2026

Summary by CodeRabbit

  • New Features

    • Added methods to query downloaded resources and asset paths in the SDK.
    • Introduced retry mechanism for failed lazy resource downloads.
  • Bug Fixes

    • Improved failure handling for package downloads and lazy-splits initialization.
    • Enhanced resource tracking to ensure accurate on-disk asset state.
  • Improvements

    • Strengthened thread-safety in resource management across platforms.

@semanticdiff-com
Copy link
Copy Markdown

Review changes with  SemanticDiff

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 20, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 70d17b63-488f-4eb6-ac15-361b379dad3c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

These changes enhance lazy-splits download handling across Android and iOS SDKs. Android introduces conditional lazy-splits download logic based on release fetch outcomes. iOS adds centralized tracking of downloaded splits via a new internal set and exposes query methods for downloaded asset paths, including retry mechanisms for failed downloads.

Changes

Cohort / File(s) Summary
Android lazy-splits download control flow
airborne_sdk_android/airborne/src/main/java/in/juspay/airborne/ota/UpdateTask.kt
Introduces new release failure tracking and conditional lazy-splits download logic. On fetch failure, marks newReleaseFailed and forces lazy-splits download path. Converts presult from shadowing val to mutable var for conditional reference. Computes shouldDownloadNewLazy based on failure state, presult type, and timeout conditions.
iOS downloaded splits tracking infrastructure
airborne_sdk_iOS/hyper-ota/Airborne/AirborneObjC/ApplicationManager/AJPApplicationManager.m, airborne_sdk_iOS/hyper-ota/Airborne/AirborneObjC/include/AJPApplicationManager.h
Introduces _downloadedSplits tracking set initialized with existing splits from bundles and lazy resources. Adds public query methods getPathForAssetsInReleaseConfig and getDownloadedSplits. Enhances lazy download flow with retry mechanism and completion callbacks. Updates state transitions to propagate downloaded splits consistently across resource workflows with thread-safe synchronization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Down the lazy-splits path we hop,
Tracking downloads, they never stop!
New retries bloom when failures creep,
Android and iOS sync—promises to keep,
State transitions dance, thread-safe and neat! 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Ios track downloaded splits' accurately summarizes the main objective of the changeset, which introduces tracking of downloaded resource splits across iOS and Android SDKs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ios-track-downloaded-splits

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
airborne_sdk_android/airborne/src/main/java/in/juspay/airborne/ota/UpdateTask.kt (1)

239-259: ⚠️ Potential issue | 🟡 Minor

newReleaseFailed is incorrectly true for Update.Package.NA, causing a misleading log

installPackageUpdate returns false for Update.Package.NA (lines 450–458), which means !didPackageUpdate is true for that case. The combined condition on lines 239–240 therefore sets newReleaseFailed = true when there is simply no new version available — not an actual failure.

Downstream effect: when updateTimedOut = false and presult is Update.Package.NA, shouldDownloadNewLazy evaluates to false (because presult is Update.Package.Finished is also false), and execution falls into the else if (newReleaseFailed) branch on line 257, logging "Skipping new release lazy splits download due to download failure." — a misleading message during normal operation.

The simplest fix is to scope newReleaseFailed to actual download/install failures, excluding NA:

🐛 Proposed fix
-            newReleaseFailed = !updateTimedOut.get() &&
-                (presult is Update.Package.Failed || !didPackageUpdate)
+            newReleaseFailed = !updateTimedOut.get() &&
+                (presult is Update.Package.Failed ||
+                    (presult is Update.Package.Finished && !didPackageUpdate))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@airborne_sdk_android/airborne/src/main/java/in/juspay/airborne/ota/UpdateTask.kt`
around lines 239 - 259, The newReleaseFailed flag is currently set using
"!didPackageUpdate" which makes Update.Package.NA be treated as a failure;
change the assignment of newReleaseFailed (and any logic that uses it) to only
consider actual failure states—e.g., base it on presult being
Update.Package.Failed (and still respect updateTimedOut.get()) rather than
"!didPackageUpdate" or explicitly exclude presult == Update.Package.NA; update
the references to newReleaseFailed and the shouldDownloadNewLazy computation
(which checks presult is Update.Package.Finished) so NA does not trigger the
"Skipping new release lazy splits download due to download failure." branch and
only real failures set newReleaseFailed.
🧹 Nitpick comments (3)
airborne_sdk_android/airborne/src/main/java/in/juspay/airborne/ota/UpdateTask.kt (1)

231-231: Redundant !! after a null-guard on a local var

presult is a local variable and is not mutated between the presult != null guard on the same line and the saveDownloadedPackages call. Kotlin's data-flow analysis smart-casts local vars in this scenario, making !! unnecessary. Prefer an explicit smart-cast or let for clarity:

♻️ Proposed refactor
-            } else if (presult != null && fetched.pkg.lazy.isEmpty()) {
-                saveDownloadedPackages(presult!!, fetched.pkg)
+            } else if (presult != null && fetched.pkg.lazy.isEmpty()) {
+                saveDownloadedPackages(presult, fetched.pkg)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@airborne_sdk_android/airborne/src/main/java/in/juspay/airborne/ota/UpdateTask.kt`
at line 231, The call uses a redundant non-null assertion "presult!!" after a
local null-check; remove the "!!" and rely on Kotlin's smart-cast (or wrap the
use in presult?.let { ... }) so the compiler treats presult as non-null when
calling saveDownloadedPackages; update the call site in UpdateTask
(saveDownloadedPackages and fetched.pkg usage) to either call
saveDownloadedPackages(presult, fetched.pkg) after the null guard or enclose the
call in presult?.let { saveDownloadedPackages(it, fetched.pkg) } for clarity.
airborne_sdk_iOS/hyper-ota/Airborne/AirborneObjC/ApplicationManager/AJPApplicationManager.m (1)

1362-1452: retryFailedLazyDownloads is a copy-paste of retryFailedLazyDownloadsWithCompletion:

The two methods are ~40 lines of near-identical code. retryFailedLazyDownloads can be collapsed to a one-liner that delegates to the new variant.

♻️ Proposed refactor
 - (void)retryFailedLazyDownloads {
-    NSMutableArray<AJPLazyResource *> *failedDownloads = [NSMutableArray array];
-    `@synchronized`(self.package) {
-        for (AJPLazyResource *resource in self.package.lazy) {
-            if (!resource.isDownloaded) {
-                [failedDownloads addObject:resource];
-            }
-        }
-    }
-    if (failedDownloads.count > 0) {
-        [self.tracker trackInfo:@"retrying_failed_lazy_downloads" value:[@{@"count": @(failedDownloads.count)} mutableCopy]];
-        __weak AJPApplicationManager *weakSelf = self;
-        [self downloadLazyPackageResources:failedDownloads version:self.package.version singleDownloadHandler:^(BOOL status, AJPResource *resource) {
-            if (status && [resource isKindOfClass:[AJPLazyResource class]]) {
-                if (weakSelf) {
-                    __strong AJPApplicationManager* strongSelf = weakSelf;
-                    AJPLazyResource *lazyResource = (AJPLazyResource *)resource;
-                    [strongSelf moveLazyPackageFromTempToMain:lazyResource];
-                }
-            }
-            [[NSNotificationCenter defaultCenter] postNotificationName:LAZY_PACKAGE_NOTIFICATION
-                                                                object:nil
-                                                              userInfo:@{
-                                                                    @"lazyDownloadsComplete": `@NO`,
-                                                                    @"downloadStatus": @(status),
-                                                                    @"url": resource.url,
-                                                                    @"filePath": resource.filePath
-                                                              }];
-        } downloadCompletion:^{
-                [[NSNotificationCenter defaultCenter] postNotificationName:LAZY_PACKAGE_NOTIFICATION
-                                                                    object:nil
-                                                                  userInfo:@{@"lazyDownloadsComplete": `@YES`}];
-        }];
-    } else {
-        [self.tracker trackInfo:@"no_failed_lazy_downloads" value:[@{} mutableCopy]];
-    }
+    [self retryFailedLazyDownloadsWithCompletion:nil];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@airborne_sdk_iOS/hyper-ota/Airborne/AirborneObjC/ApplicationManager/AJPApplicationManager.m`
around lines 1362 - 1452, The method retryFailedLazyDownloads duplicates
retryFailedLazyDownloadsWithCompletion:; replace the entire implementation of
retryFailedLazyDownloads to simply call [self
retryFailedLazyDownloadsWithCompletion:nil] (delegate to the completion-taking
variant) to remove the copy-pasted logic and keep a single source of truth
(ensure the selector name retryFailedLazyDownloadsWithCompletion: and the
parameter nil are used exactly as in the existing code).
airborne_sdk_iOS/hyper-ota/Airborne/AirborneObjC/include/AJPApplicationManager.h (1)

178-192: LGTM — optional: align parameter names between header and implementation

The _Nullable annotation and doc comments are correct. The only nit is that the header declares filePath while the implementation uses resourcePath for the same parameter. Obj-C doesn't require them to match, but consistency aids readability.

🔧 Optional: align parameter name
-  - (NSString * _Nullable)getPathForAssetsInReleaseConfig:(NSString *)filePath;
+  - (NSString * _Nullable)getPathForAssetsInReleaseConfig:(NSString *)resourcePath;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@airborne_sdk_iOS/hyper-ota/Airborne/AirborneObjC/include/AJPApplicationManager.h`
around lines 178 - 192, The header and implementation use different parameter
names for the same method: normalize the parameter name for - (NSString *
_Nullable)getPathForAssetsInReleaseConfig:(NSString *)filePath; so it matches
the implementation (or vice versa) — e.g., rename the parameter in the
implementation of getPathForAssetsInReleaseConfig: from resourcePath to filePath
(or rename the header param to resourcePath) and keep the _Nullable annotation
and existing comments unchanged to improve readability/consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@airborne_sdk_android/airborne/src/main/java/in/juspay/airborne/ota/UpdateTask.kt`:
- Around line 251-256: The condition computing shouldDownloadNewLazy can become
true when updateTimedOut is true even if presult is Update.Package.NA, causing
duplicate lazy-split download attempts; update the logic in UpdateTask.kt so
shouldDownloadNewLazy only becomes true when fetched != null, newReleaseFailed
is false, and presult is not an Update.Package.NA (i.e., add a guard like
presult !is Update.Package.NA) before calling downloadLazySplits(tw,
fetched.pkg, ...), ensuring downloadLazySplits is not invoked for the NA case
and avoiding unnecessary TempWriter allocation/callbacks.

In
`@airborne_sdk_iOS/hyper-ota/Airborne/AirborneObjC/ApplicationManager/AJPApplicationManager.m`:
- Around line 259-271: The _downloadedSplits set is populated from self.package
allImportantSplits without verifying files exist on disk; update the bootstrap
so that for each AJPResource returned by [self.package allImportantSplits] you
check disk existence (use NSFileManager fileExistsAtPath: on resource.filePath)
before adding to _downloadedSplits, and similarly ensure AJPLazyResource entries
in self.package.lazy are only added when lazy.isDownloaded is true and the file
actually exists; reference AJPApplicationManager's _downloadedSplits
initialization, AJPResource.filePath, AJPLazyResource, self.package.lazy and
self.resources.resources when making the change (or alternatively adjust the
comment to state the manifest-trust assumption if you intentionally skip the
check).
- Around line 686-709: Replace all `@synchronized`(self->_downloadedSplits) /
`@synchronized`(strongSelf->_downloadedSplits) usages with the shared
collectionsLock to make access to _downloadedSplits consistent with other
guarded collections: wrap reads and writes to _downloadedSplits in
[self.collectionsLock lock] / [self.collectionsLock unlock] (or equivalent)
instead of synchronized blocks so methods like getPathForAssetsInReleaseConfig
and getDownloadedSplits and mutation sites (moveLazyPackageFromTempToMain,
moveResourceToMainAndUpdate, downloadResourcesWithCurrentResources, and the
rebuild block in tryDownloadingUpdate) use the same lock as
_availableLazySplits, _availableResources, and _downloadedApplicationManifest.
- Around line 800-839: The rebuild clears _downloadedSplits after
downloadResourcesWithCurrentResources may have already added unchanged
resources, causing a race; fix by adding the unchanged resources into the
rebuild itself: inside the `@synchronized`(strongSelf->_downloadedSplits) block
(in AJPApplicationManager where you call getResourcesFrom:filtering: to compute
toDownload and pendingLazyPaths), after adding allImportantSplits and the
downloadedLazy entries, also iterate the same source of current/installed
resources (the set used by downloadResourcesWithCurrentResources — e.g.,
strongSelf.currentResources/currentLazy or the collection returned from
getResourcesFrom:filtering: logic) and add any resource.filePath that is not in
pendingLazyPaths to _downloadedSplits so unchanged resources are present
regardless of concurrency with downloadResourcesWithCurrentResources; keep the
rest of the moveLazyPackageFromTempToMain and notification flow intact.

---

Outside diff comments:
In
`@airborne_sdk_android/airborne/src/main/java/in/juspay/airborne/ota/UpdateTask.kt`:
- Around line 239-259: The newReleaseFailed flag is currently set using
"!didPackageUpdate" which makes Update.Package.NA be treated as a failure;
change the assignment of newReleaseFailed (and any logic that uses it) to only
consider actual failure states—e.g., base it on presult being
Update.Package.Failed (and still respect updateTimedOut.get()) rather than
"!didPackageUpdate" or explicitly exclude presult == Update.Package.NA; update
the references to newReleaseFailed and the shouldDownloadNewLazy computation
(which checks presult is Update.Package.Finished) so NA does not trigger the
"Skipping new release lazy splits download due to download failure." branch and
only real failures set newReleaseFailed.

---

Nitpick comments:
In
`@airborne_sdk_android/airborne/src/main/java/in/juspay/airborne/ota/UpdateTask.kt`:
- Line 231: The call uses a redundant non-null assertion "presult!!" after a
local null-check; remove the "!!" and rely on Kotlin's smart-cast (or wrap the
use in presult?.let { ... }) so the compiler treats presult as non-null when
calling saveDownloadedPackages; update the call site in UpdateTask
(saveDownloadedPackages and fetched.pkg usage) to either call
saveDownloadedPackages(presult, fetched.pkg) after the null guard or enclose the
call in presult?.let { saveDownloadedPackages(it, fetched.pkg) } for clarity.

In
`@airborne_sdk_iOS/hyper-ota/Airborne/AirborneObjC/ApplicationManager/AJPApplicationManager.m`:
- Around line 1362-1452: The method retryFailedLazyDownloads duplicates
retryFailedLazyDownloadsWithCompletion:; replace the entire implementation of
retryFailedLazyDownloads to simply call [self
retryFailedLazyDownloadsWithCompletion:nil] (delegate to the completion-taking
variant) to remove the copy-pasted logic and keep a single source of truth
(ensure the selector name retryFailedLazyDownloadsWithCompletion: and the
parameter nil are used exactly as in the existing code).

In
`@airborne_sdk_iOS/hyper-ota/Airborne/AirborneObjC/include/AJPApplicationManager.h`:
- Around line 178-192: The header and implementation use different parameter
names for the same method: normalize the parameter name for - (NSString *
_Nullable)getPathForAssetsInReleaseConfig:(NSString *)filePath; so it matches
the implementation (or vice versa) — e.g., rename the parameter in the
implementation of getPathForAssetsInReleaseConfig: from resourcePath to filePath
(or rename the header param to resourcePath) and keep the _Nullable annotation
and existing comments unchanged to improve readability/consistency.

Comment thread airborne_sdk_android/airborne/src/main/java/in/juspay/airborne/ota/UpdateTask.kt Outdated
@Yash02Rajput Yash02Rajput force-pushed the ios-track-downloaded-splits branch 2 times, most recently from 36d8177 to 01a3003 Compare February 23, 2026 12:12
@Yash02Rajput Yash02Rajput force-pushed the ios-track-downloaded-splits branch 2 times, most recently from c70cc0a to 0578957 Compare March 2, 2026 12:03
@Yash02Rajput Yash02Rajput force-pushed the ios-track-downloaded-splits branch from 414eb55 to eb6f5dd Compare March 26, 2026 07:34
@balaganesh-juspay balaganesh-juspay merged commit 009be46 into main Mar 26, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants