captaindroid
captaindroid

Reputation: 2928

Third party library of dynamic feature module cannot access resources

I have a application which has a dynamic feature module. In dynamic feature module, there is a form with images, input fields and also it has a buttton which access another third-party library.

Third-party library has a activity and fragment. While opening fragment inside activity, I am receiving below error, although there is container in activity's layout:

No view found for id 0x7f080053 (com.app.sample:id/container) for fragment SampleFragment{eed53f7 (5e4c0693-09a2-4725-a6de-1df49dd818f0) id=0x7f080053}

When accessing drawables in this third-party library, getting below error:

java.lang.NoSuchFieldError: No static field ic_back of type I in class Lcom.third.library/R$drawable; or its superclasses (declaration of 'com.third.library.R$drawable' appears in /data/app/com.app.sample-QtC8XuamC1fHEVU4FUpWaA==/split_thirdparty.apk)

It is fine when I use this library in a application without dynamic feature module. enter image description here

Upvotes: 3

Views: 2225

Answers (3)

Martin Zeitler
Martin Zeitler

Reputation: 76669

Generally, when SplitCompat.installActivity(this) isn't called in Activity2, this won't work. While not having the source code, you'd have to extract the package and re-package it properly, because the Activity2 (or even the whole library package) likely isn't compatible with DFM.

After you enable SplitCompat for your base app, you need to enable SplitCompat for each activity that your app downloads in a dynamic feature module.

Here's another answer of mine, which demonstrates access through reflection.

Upvotes: 3

mrtcnkryln
mrtcnkryln

Reputation: 324

For the resources, this code part can be usage

R.id.settings would be:

getResources().getIdentifier("settings", "id", "com.library.package");

Upvotes: 0

Pavlo Ostasha
Pavlo Ostasha

Reputation: 16699

Dynamic Delivery is relatively new feature so it has a lot of limitations. One of those limitations it that you cannot access code and resources of a Dynamic Module in a conventional way, thus it cannot be a dependency for other modules. Currently you can access Dynamic Module via reflection and having dynamic features defined through public interfaces in a common library module and loading their actual implementations (located in the dynamic feature modules) at runtime with a ServiceLoader. It has its performance downsides. They can be minimized with R8 using ServiceLoaderRewriter but not completely removed.

While using reflection is very bug prone we can minimize it either with @AutoServiceAutoService is an annotation processor that will scan the project for classes annotated with @AutoService, for any class it finds it will automatically generate a service definition file for it.

Here is small example of how it is done

// All feature definitions extend this interface, T is the dependencies that the feature requires
interface Feature<T> {
    fun getMainScreen(): Fragment
    fun getLaunchIntent(context: Context): Intent
    fun inject(dependencies: T)
}

interface VideoFeature : Feature<VideoFeature.Dependencies> {
    interface Dependencies {
        val okHttpClient: OkHttpClient
        val context: Context
        val handler: Handler
        val backgroundDispatcher: CoroutineDispatcher
    }
}

internal var videoComponent: VideoComponent? = null
    private set

@AutoService(VideoFeature::class)
class VideoFeatureImpl : VideoFeature {
    override fun getLaunchIntent(context: Context): Intent = Intent(context, VideoActivity::class.java)

    override fun getMainScreen(): Fragment = createVideoFragment()

    override fun inject(dependencies: VideoFeature.Dependencies) {
        if (videoComponent != null) {
            return
        }

        videoComponent = DaggerVideoComponent.factory()
                .create(dependencies, this)
    }
}

And to actually access code of Dynamic Feature use


inline fun <reified T : Feature<D>, D> FeatureManager.getFeature(
        dependencies: D
): T? {
    return if (isFeatureInstalled<T>()) {
        val serviceIterator = ServiceLoader.load(
                T::class.java,
                T::class.java.classLoader
        ).iterator()

        if (serviceIterator.hasNext()) {
            val feature = serviceIterator.next()
            feature.apply { inject(dependencies) }
        } else {
            null
        }
    } else {
        null
    }
}

Taken from here. Also there a lot more info there so I would recommend you to check it.

Generally I just would not recommend to use Dynamic Feature as dependency and plan your app architecture accordingly.

Hope it helps.

Upvotes: 0

Related Questions