icerockdev / moko-resources

Resources access for mobile (android & ios) Kotlin Multiplatform development
https://moko.icerock.dev/
Apache License 2.0
1.07k stars 120 forks source link

0.24.1 Change Causing -> iOS "Bundle with identifier *** not found" Error #747

Open mattjung opened 1 month ago

mattjung commented 1 month ago

Hi, we are experiencing the following error:

kotlin.IllegalArgumentException: bundle with identifier *** not found

We think it's due to the following changes introduced in 0.24.1:

https://github.com/icerockdev/moko-resources/pull/715/files#diff-74b2961a5d6d48ad53d5ab6e4529f3688bdae02391f7d66628bdb6086a06854eL14-L16

This change limits the use of bundles to be in the main bundle only.

We encounter the crash when attempting to access MR.strings() in a framework while running unit tests against the framework target. We are using copyFrameworkResourcesToApp in a build phase for the framework target using $CONTENTS_FOLDER_PATH, which, in this case, points to the framework product and not the path for the main bundle.

Could you revert the line change from the PR or make it possible to inject a path to the moko-generated bundle via the moko plugin or some other method?

Egi10 commented 1 month ago

I am also experiencing the same issue with the error kotlin.IllegalArgumentException: bundle with identifier ***.main not found.

Alex009 commented 1 month ago

This change limits the use of bundles to be in the main bundle only.

hi @mattjung ! thx for analysis. can you please create reproducer of itssue from some sample? to deploy fix we should reproduce issue first

3moly commented 1 month ago

Hello, @Alex009 Here is copy project of moko resources and reproduction of the bug in folder samples/ios-cocoapods-static-framework https://github.com/3moly/moko_bundle_bug

I reproduced this error in my work project and also in ios-cocoapods-static-framework.

So the main step is to create build configuration for xcode project, for example Debug_beta, Release_beta. And then write in gradle cocoapods something like this: xcodeConfigurationToNativeBuildType["Debug_beta"] = NativeBuildType.DEBUG xcodeConfigurationToNativeBuildType["Release_beta"] = NativeBuildType.RELEASE

Switching to Debug_beta and get this error. I hope this helps. I struggled with this bug in a long time, but didn't find time to create project with this error.

mattjung commented 1 month ago

I also want to add that we have the same issue when using SwiftUI previews and running unit tests. In these cases, the main bundle isn't the expected $CONTENTS_FOLDER_PATH that is used for copyFrameworkResourcesToApp. Therefore the same error is thrown when accessing MR.strings() due to not finding the bundle in the main bundle.

It would be great if we could configure the bundle path for these scenarios. And if the bundle path isn't provided then default to the main bundle.

Egi10 commented 1 month ago

If you need another example, you can find it here: https://github.com/Egi10/resources. I have also experienced the same issue with this.

nunoonun commented 1 month ago

I also think this is related with my appExtension being broken. Since the change introduced by this change seems to force the bundle only to load if the mainBundle is the main application. If you're running an appExtension the mainBundle is not the app bundle, it's the extension bundle.

MAX-POLKOVNIK commented 2 days ago

I face same issue with SwiftUI previews in Framework Projects (bundle with identifier *** not found). App runs ok and all resources available.

My projects structure (using framework, not cocoapods)

- shared (dynamic framework)
- resources (dynamic framework)
- feature1
- feature2
- androidApp
- iosApp
-- iosAppWorkspace
-- iosApp (previews working by default)
-- framework1 (previews not working)
-- framework2 (previews not working)

Nothing helps. Also adding resources as described in README.md not working.

I written workaround for my project: In kotlin module where MR generated need to override method loadableBundle:

package dev.icerock.moko.resources.utils // must be same as in moko-resources to override method

import platform.Foundation.NSBundle
import platform.Foundation.NSDirectoryEnumerator
import platform.Foundation.NSFileManager
import platform.Foundation.NSURL
import platform.Foundation.pathExtension

var nsBundle: NSBundle = NSBundle.mainBundle // <-- this is where we should looking for resources, by default mainBundle

fun NSBundle.Companion.loadableBundle(identifier: String): NSBundle {
    val bundlePath: String = nsBundle.bundlePath // <-- path where we should search for bundle with resources
    val enumerator: NSDirectoryEnumerator = requireNotNull(NSFileManager.defaultManager.enumeratorAtPath(bundlePath))
    while (true) {
        val relativePath: String = enumerator.nextObject() as? String ?: break
        val url = NSURL(fileURLWithPath = relativePath)
        if (url.pathExtension == "bundle") {
            val fullPath = "$bundlePath/$relativePath"
            val foundedBundle: NSBundle? = NSBundle.bundleWithPath(fullPath)
            val loadedIdentifier: String? = foundedBundle?.bundleIdentifier

            if (isBundleSearchLogEnabled) {
                println("moko-resources auto-load bundle with identifier $loadedIdentifier at path $fullPath")
            }

            if (foundedBundle?.bundleIdentifier == identifier) return foundedBundle
        }
    }

    throw IllegalArgumentException("bundle with identifier $identifier not found")
}

var isBundleSearchLogEnabled = false

In xcode framework define method that will set nsBundle from code above:

public func fixPreview() {
    if ProcessInfo.processInfo.processName == "XCPreviewAgent" {
        MokoResourcesPreviewWorkaroundKt.nsBundle = Bundle.init(for: KotlinBase.self)
    }
}

And just call fixPreview() in SwiftUI previews:

#Preview {
    let _ = fixPreview()

    return AboutContentView(
        content: AboutContentKt.AboutContent_PreviewData() // <-- there we are using MR
    )
}

That's all. Now previews are working in frameworks. P.S. I would be grateful if someone could tell me how get ObjcClass from KClass in kotlin module. That will help to remove swift side with Bundle.init(for: KotlinBase.self)

Alex009 commented 13 hours ago

issue also can be related to https://github.com/icerockdev/moko-resources/issues/762