# Overriding Bundle.module for loading resources from Android assets

**URL:** <https://forums.swift.org/t/overriding-bundle-module-for-loading-resources-from-android-assets/78507>\
**Category:** Android\
**Tags:** android, foundation, bundle, resources\
**Created:** [March 13, 2025, 7:22pm UTC](https://forums.swift.org/t/overriding-bundle-module-for-loading-resources-from-android-assets/78507 "2025-03-13T19:22:34Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![marcprux](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/marcprux/32/12098_2.png) [@marcprux](https://forums.swift.org/u/marcprux)\
**Post date:** [March 13, 2025, 7:22pm UTC](https://forums.swift.org/t/overriding-bundle-module-for-loading-resources-from-android-assets/78507/1 "2025-03-13T19:22:35Z")

</div>

A Foundation Bundle expects resources to be present as a plain file on disk. For example, the call `Bundle.module.url(forResource: "file", withExtension: "json")` will return something like:

```swift
file:/…apppath…/Contents/Resources/ModuleName.bundle/Contents/Resources/file.json

```

This works fine for Darwin apps (iOS, macOS, etc.), because their contents are expanded on disk. This is not the case for Android apps: an `app.apk` file – the zip of an app's code and resources – is not expanded on disk when the app is installed, but instead remains zipped up. Android expects app resources to be stored in an assets/ folder of the `.apk` archive and accessed via an "Asset Manager" API.

This presents a problem for code that relies on Foundation conventions to access resources. In Skip, we handle this by adding resources to a module-specific folder in the `/assets/` section of the archive and intercepting calls to `Bundle.module.url(…)` to return a custom URL like:

```swift
asset:/module-name/Resources/file.json

```

We handle the "asset" protocol by registering a custom URL protocol handler in both Java (using `java.net.URL.setURLStreamHandlerFactory`) and Foundation (using `URLProtocol.registerClass`) to process these requests with the Java SDK's [`android.content.res.AssetManager`](https://developer.android.com/reference/android/content/res/AssetManager) or the NDK's [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset), respectively.

This works for single-shot loading of data (e.g., `Data(contentsOf: Bundle.module.url(forResource: "file.json")`), and in theory could even work to provide random-access to large assets with their support for asset-aware file descriptors (with [`AssetManager.openFd`](https://developer.android.com/reference/android/content/res/AssetManager#openFd(java.lang.String)) and [`AAsset_openFileDescriptor64`](https://developer.android.com/ndk/reference/group/asset#group___asset_1ga123a44a575f85d91a00a8456dab7bd0a)).

All this works fairly well, but the way we have to implement it is ugly: the client code needs to `import SkipFuse`, which allows us to `typealias Bundle = AndroidAssetBundle` so we can override `Bundle.module` and `Bundle.main`. In order to make this work transparently, we would need a way to hook into the Bundle loading system. A couple ideas we have for this:

1. Use `@_dynamicReplacement` to intercept calls to `Bundle.url` and the like. This would require that Android's Foundation module be built with `-enable-dynamic-replacement`, which may have performance implications.
2. Add some SPI capabilities to Bundle like `Bundle.registerBundleProvider(handler: (String) -> Bundle?)` and alter SwiftPM's generated `resource_bundle_accessor.swift` to first try `Bundle.bundleProvider?(handler: "BundleName")` to see if a custom provider has been registered.

Does anyone have any other ideas?

---

<div class="post-metadata">

**Author:** ![dschaefer2](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/dschaefer2/32/46516_2.png) [@dschaefer2](https://forums.swift.org/u/dschaefer2)\
**Post date:** [May 10, 2026, 5:10am UTC](https://forums.swift.org/t/overriding-bundle-module-for-loading-resources-from-android-assets/78507/2 "2026-05-10T05:10:54Z")

</div>

Other platforms actually look for the bundle directory next to the executable and that's how it's currently working on Android. However the executable is `/system/bin/app_process64` so that's very wrong.

I'm using SDL3 which does use the AssetManager as you suggest. That's probably the right approach. Which points to the need for this to be an extension point provided by the platform.

On piece I think you missed is that the `Bundle.module` extension is generated by the build system into a file called `resource_bundle_accessor.swift`. It might be interesting for platforms to provide their own generator for that. It could then generate maybe a subclass of Bundle as well that overrides how resources are accessed.

---

<div class="post-metadata">

**Author:** ![marcprux](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/marcprux/32/12098_2.png) [@marcprux](https://forums.swift.org/u/marcprux)\
**Post date:** [May 11, 2026, 1:50am UTC](https://forums.swift.org/t/overriding-bundle-module-for-loading-resources-from-android-assets/78507/3 "2026-05-11T01:50:41Z")

</div>

> [@dschaefer2](#):
>
> On piece I think you missed is that the `Bundle.module` extension is generated by the build system into a file called `resource_bundle_accessor.swift`.

We didn't miss it; Skip's build plugin [generates](https://github.com/skiptools/skipstone/blob/main/Sources/SkipSyntax/Kotlin/KotlinBundleTransformer.swift#L89) a peer function that intercepts the Bundle initializer call from `resource_bundle_accessor.swift` and returns an [`AndroidBundle`](https://github.com/skiptools/skip-android-bridge/blob/main/Sources/SkipAndroidBridge/AndroidBundle.swift) subclass that "does the right thing" and forwards the bundle calls through to a registered URL scheme that passes through to the `AssetManager`.

So calls to `Bundle.module.url(forResource:)` transparently work the same way on Android as on Darwin platforms. But it _is_ fragile and relies on some undocumented assumptions that could break in any release.

> [@dschaefer2](#):
>
> It might be interesting for platforms to provide their own generator for that. It could then generate maybe a subclass of Bundle as well that overrides how resources are accessed.

This is precisely what we need! An official sanctioned extension point for custom bundle loading would enable all platforms (not just Android) to handle bundle resource access the right way for the platform's conventions.

I will note, though, that there are some serious limitations with our approach: Skip's bundle interception works swell for API like [`Bundle.url(forResource:withExtension:subdirectory:)`](https://developer.apple.com/documentation/foundation/bundle/url(forresource:withextension:subdirectory:)), where we return a URL scheme that we can intercept to fetch the bytes for the requested resource (like with [`Data(contentsOf: URL)`](https://developer.apple.com/documentation/foundation/data/init(contentsof:options:))). But API like [`Bundle.path(forResource:ofType:inDirectory:)`](https://developer.apple.com/documentation/foundation/bundle/path(forresource:oftype:indirectory:)-swift.method), which is expected to return a plain file system path, won't ever be able to work, unless we were to handle that in an extreme way like having these calls trigger a side-effect of the extraction of all the (potentially huge) assets to some temporary/cache folder and then return the path to that folder.

Or we could do something _really_ gonzo by having `Bundle.path(forResource:…)` return some _virtual_ filesystem root (like `/var/_foundation-apk/`), which would then be registered through some other `FileManager` extension to intercept those paths through a virtual filesystem which is backed by calls to the `AssetManager`; this would unlock features that are impossible with the simplistic URL scheme interception, like the ability to perform `mmap` or random access on large embedded resources by leveraging [`AAsset_openFileDescriptor`](https://developer.android.com/ndk/reference/group/asset#aasset_openfiledescriptor) to return a virtual native fd for an asset. We haven't yet encountered anyone with this need, but I can imagine it not being an uncommon requirement for certain classes of apps that need to embed very large assets, like games.

---

<div class="post-metadata">

**Author:** ![dschaefer2](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/dschaefer2/32/46516_2.png) [@dschaefer2](https://forums.swift.org/u/dschaefer2)\
**Post date:** [May 11, 2026, 6:08pm UTC](https://forums.swift.org/t/overriding-bundle-module-for-loading-resources-from-android-assets/78507/4 "2026-05-11T18:08:39Z")

</div>

> [@marcprux](#):
>
> This is precisely what we need!

I have added it to my list. I've been playing with building a game using SDL3 to see what's missing from the package manifest and plugins to make that easy. I hit this pretty early when I got it building and running on my Android phone. I'll post more on that experience and proposed additions for SwiftPM in a few days.

---

<div class="post-metadata">

**Author:** ![dschaefer2](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/dschaefer2/32/46516_2.png) [@dschaefer2](https://forums.swift.org/u/dschaefer2)\
**Post date:** [May 11, 2026, 6:11pm UTC](https://forums.swift.org/t/overriding-bundle-module-for-loading-resources-from-android-assets/78507/5 "2026-05-11T18:11:44Z")

</div>

> [@marcprux](#):
>
> But API like [`Bundle.path(forResource:ofType:inDirectory:)`](https://developer.apple.com/documentation/foundation/bundle/path(forresource:oftype:indirectory:)-swift.method), which is expected to return a plain file system path, won't ever be able to work

We probably want to fatalError in that case. That's true for embedded as well where the resources are stored in a flash device without a file system, for example.

But then that speaks to your virtual file system idea, which would be interesting as well. Need to study how that would work. A lot of Swift tooling projects have invented virtual file systems on top of the FileManager one but it would make more sense for that to be a FileManager feature...
