Third party library of dynamic feature module cannot access resources - android

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.

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.

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 #AutoService — AutoService 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.

For the resources, this code part can be usage
R.id.settings would be:
getResources().getIdentifier("settings", "id", "com.library.package");

Related

Dao in Usecase. MVVM or Clean Architecture anti-pattern?

In our "SearchUsecase" we have access to "ShowFtsDao" directly.
Does it violate the Clean Architecture principles? Does it violate the MVVM architecture?
Assuming our intention is to develop a well-built, standard structure, is there anything wrong with this piece of code?
class SearchUsecase #Inject constructor(
private val searchRepository: SearchRepository,
private val showFtsDao: ShowFtsDao,
private val dispatchers: AppCoroutineDispatchers
) : SuspendingWorkInteractor<SearchShows.Params, List<ShowDetailed>>() {
override suspend fun doWork(params: Params): List<ShowDetailed> {
return withContext(dispatchers.io) {
val remoteResults = searchRepository.search(params.query)
if (remoteResults.isNotEmpty()) {
remoteResults
} else {
when {
params.query.isNotBlank() -> showFtsDao.search("*$params.query*")
else -> emptyList()
}
}
}
}
data class Params(val query: String)
}
I believe your use case handles more logic than it needs to.
As a simple explanation I like to think about the components this way:
Sources: RemoteSource (networking), LocalSource (db), optionally MemorySource are an abstraction over your database and networking api and they do the IO thread switching & data mapping (which comes in handy on big projects, where the backend is not exactly mobile driven)
Repository: communicates with the sources, he is responsible for deciding where do you get the data from. I believe in your case if the RemoteSource returns empty data, then you get it from the LocalSource. (you can expose of course different methods like get() or fetch(), where the consumer specifies if it wants the latest data and based on that the repository calls the correct Source.
UseCases: Talk with multiple repositories and combine their data.
Yes, it does.
Because your domain module has access to the data module and actually you've violated the dependency rule.
That rule specifies that something declared in an outer circle must
not be mentioned in the code by an inner circle.
Domain layer must contain interfaces for details (Repositories) which are implemented in the data layer,
and then, they could be injected into the UseCases (DIP).

How to pass in parameters to a dagger module from a activity or fragment at runtime

My software specifications are as follows:
Android Studio 3.4
dagger-android 2.16
I have the following class that passes a MapboxGeocoder that will execute and return a response.
class GeocodingImp(private val mapboxGeocoder: MapboxGeocoder) : Geocoding {
override fun getCoordinates(address: String, criteria: String): AddressCoordinate {
val response = mapboxGeocoder.execute()
return if(response.isSuccess && !response.body().features.isEmpty()) {
AddressCoordinate(
response.body().features[0].latitude,
response.body().features[0].longitude)
}
else {
AddressCoordinate(0.0, 0.0)
}
}
}
However, the MapboxGeocoder is generated in a dagger module at compile time. So I have to specify the string for the address and TYPE_ADDRESS.
#Reusable
#Named("address")
#Provides
fun provideAddress(): String = "the address to get coordinates from"
#Reusable
#Provides
#Named("geocoder_criteria")
fun provideGeocoderCriteria(): String = GeocoderCriteria.TYPE_ADDRESS
#Reusable
#Provides
fun provideMapboxGeocoder(#Named("address") address: String, #Named("geocoder_criteria") geocoderCriteria: String): MapboxGeocoder =
MapboxGeocoder.Builder()
.setAccessToken("api token")
.setLocation(address)
.setType(geocoderCriteria)
.build()
#Reusable
#Provides
fun provideGeocoding(mapboxGeocoder: MapboxGeocoder): Geocoding =
GeocodingImp(mapboxGeocoder)
my component class:
interface TMDispatchMobileUIComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: TMDispatchMobileUIApplication): Builder
fun build(): TMDispatchMobileUIComponent
}
fun inject(application: TMDispatchMobileUIApplication)
}
In the main activity I would use this like this as the user can enter in a different address or change the criteria to something else. But as the module are compiled I cannot pass any parameters to them at runtime:
presenter.getAddressCoordinates("this should be the actual address", GeocoderCriteria.TYPE_ADDRESS)
For my injection into the Activity I use the following:
AndroidInjection.inject(this)
Is there any solution to this problem?
The problem you have can be solved using "Assisted injection" approach.
It means that you need a class to be built both using dependencies provided from the existing scopes and some dependencies from the instance's creator, in this case, your main activity. Guice from Google has a nice description of what it is and why it is needed
Unfortunately, Dagger 2 does not have this feature out from the box. However, Jake Wharton is working on a separate library that can be attached to Dagger. Moreover, you can find more details in his talk on Droidcon London 2018, where he dedicated a whole talk section for this question:
https://jakewharton.com/helping-dagger-help-you/
You can recreate your whole component at runtime if you wish, where you'd then pass in the parameters to your module as a constructor parameter. Something like:
fun changeAddress(address: String) {
val component = DaggerAppComponent.builder() //Assign this to wherever we want to keep a handle on the component
.geoModule(GeoModule(address))
.build()
component.inject(this) //To reinject dependencies
}
And your module would look like:
#Module
class AppModule(private val address: String) {...}
This method may be wasteful though, if you're creating many different objects in your component.
A different approach compared to the already given answers would be to get a "Factory" via dagger dependency injection called GeoModelFactory which can create new instances of GeoModel for you.
You can pass the address and type to the factory which creates your instance. For optimization you can either store references for all different address/types that have already been requested (can result in a memory leak if there are a lot of different ones if old ones are not removed) or it could also be enough if you store only the latest instance and in other parts of the code to simply ask the factory to provide you with the GeoModel that has been created last.
The MapboxGeocoder are dynamically constructed at runtime, in this case, dagger doesn't help much as its objective is to help you construct the object graph at compile time like you hand write the code.
So in my opinion, you should create a MapboxGeocoder inside getCoordinates().

Is this method of data binding in Android suitable for production use? Does it contain hidden problems?

I'm working on an Android app and I want to implement the MVVM pattern, which is pretty much the standard pushed by Google, however, I'd like to avoid using Android Data Bindings library if possible, since I hate autogenerated XML magic.
I've tried to implement something essentially akin to databinding in RxJava (Kotlin) using Jake Wharton's data binding library, plus some helpful extension methods.
My question is, is this the right way to go about things? Is this good enough to use in production? Are there potential problems I'm not seeing with this approach that will pop up later?
Essentially, I've implemented it like this:
I have a MvvmFragment (there is a similar class for activities) which takes care of setting up and managing the lifecycle of a CompositeDisposable object.
Then, in my ViewModel (part of the android Arch ViewModel package) I have all of the fields that will be bound to declared like this:
var displayName = BindableVar("")
var email = BindableVar("")
var signInProvider = BindableVar<AuthProvider>(defaultValue = AuthProvider.PASSWORD)
(Side note - Since Rx doesn't allow null values, I'm not sure how to handle the case of defaults for objects where the concept of a default doesn't really make sense, such as the AuthProvider above)
The BindableVar class is implemented like this:
class BindableVar<T>(defaultValue: T) {
var value: T = defaultValue
set(value) {
field = value
observable.onNext(value)
}
var observable = BehaviorSubject.createDefault(value)!!
}
Using Jake Wharton's RxBindings library, I have created some helpful extension methods on top of that, such as:
fun Disposable.addTo(compositeDisposable: CompositeDisposable): Disposable {
compositeDisposable.add(this)
return this
}
fun TextView.bindTextTo(string: BindableVar<String>): Disposable {
return string.observable.subscribe(this.text())
}
fun View.bindVisibilityTo(visibility: Int) {
// ... not shown
}
fun ImageView.bindImageUriTo(
src: BindableVar<Uri>, #DrawableRes placeholder: Int? = null
): Disposable {
return if (placeholder == null) {
src.observable.subscribe {
GlideApp.with(context).load(it).into(this)
}
} else {
src.observable.subscribe {
GlideApp.with(context).load(it).placeholder(placeholder).into(this)
}
}
}
Using these extension methods, I then obtain the ViewModel instance on Fragment initialization, and call a method initBindings(), which looks something like this:
item_display_name_value.bindTextTo(viewModel.displayName).addTo(bindings)
item_email_address_value.bindTextTo(viewModel.email).addTo(bindings)
item_profile_picture_view.bindImageUrlTo(viewModel.avatarUrl).addTo(bindings)
I want to avoid getting a week into fleshing out this architecture and then suddenly realizing there is some critical problem that can't be solved easily, or some other hidden gotcha. Should I just go with XML based data binding? I've heard a lot of complaints about the difficulty of unit-testing it, and the difficulty of reusing code with it.

Return internal class calling object function

I'm testing with Kotlin and I'm writing a small library to be imported and used by a test App project.
In the library project I marked my classes as internal because I don't want them to be visible for the App project, but I would like to have a single entry point for the library, and for that I am using a Kotlin object like shown below
LIBRARY
object Library {
fun getComponent() = AwesomeComponent()
}
internal class AwesomeComponent() {
// some implementation
}
TEST APP
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val component = Library.getComponent()
}
}
The problem is that this doesn't compile because the function in the Library object returns an internal type and therefore need to be marked as internal as well, but doing so would hide the function from the TestApp.
Another option would be to not have the internal modifier at all so the TestApp can see the Library method, but then it can also see the classes inside the Library project
Is there an easy solution that I am overlooking here or does it need to go through re-planning of packages and structure of the Library project? (not sure how to do it in that case)
You have to publish some sort of public API for the app module to be able to use the component that the getComponent() method returns. If you want to publish minimal information about your library, you can have it return an interface that contains only the publicly available method calls to the library, and make your class implement that interface:
object Library {
fun getComponent(): IAwesomeComponent = AwesomeComponent()
}
interface IAwesomeComponent {
// methods you want to call on the component in the app module
}
internal class AwesomeComponent(): IAwesomeComponent {
// implementations of the interface methods
}

How to inject test overrides into the default dependency graph?

I would like to inject mocked overrides into my Android instrumentation tests using Kodein. I don't know which is the optimal approach to do this. Here's what I have in mind:
My app uses a KodeinAware application class. The served Kodein instance holds all dependencies required by my app.
In my tests I would like to inject mocked overrides for specific dependencies to test behavior of the app in various situations.
Overrides should be different for each test, and should be injected before/while the test runs.
Is the configurable Kodein extension sensible in this situation, or is there a simpler, better suited approach (and if so, which)?
If your test is given a Kodein instance (meaning that it can use a different Kodein object than the one held by your Application), then the recommended approach is to create a new Kodein object that extends the one of the app and overrides all necessary bindings.
val testKodein = Kodein {
extend(appKodein())
bind<MyManager>(overrides = true) with singleton { mock<MyManager>() }
}
The configurable Kodein option is recommended only if you're using a static "one true Kodein". Using it prevents the possibility to run you're tests in parallel (because they all access the same Kodein instance), and forces you to clear the ConfigurableKodein between each tests and re-declare every time different overrides.
I am now using the ConfigurableKodein inside my custom App class.
class App : Application(), KodeinAware {
override val kodein = ConfigurableKodein()
override fun onCreate() {
super.onCreate()
// A function is used to create a Kodein module with all app deps.
kodein.addImport(appDependencies(this))
}
}
// Helper for accessing the App from any context.
fun Context.asApp() = this.applicationContext as App
Inside my AppTestRunner class, I declare the configuration to be mutable. That way I can reset it's configuration between each and every test.
class AppTestRunner : AndroidJUnitRunner() {
override fun callApplicationOnCreate(app: Application) {
app.asApp().kodein.mutable = true
super.callApplicationOnCreate(app)
}
}
I have created a JUnit rule that reset the dependency graph before every test.
class ResetKodeinRule : ExternalResource() {
override fun before() {
val app = InstrumentationRegistry.getInstrumentation().targetContext.asApp()
app.kodein.clear()
app.kodein.addImport(appDependencies(app))
}
}
In my tests I can now retrieve the App.kodein instance and inject mocks that override dependencies of the original graph. The only thing that needs to be guaranteed is that the tested activity is launched after configuring mocks, or behavior is not predictable.

Categories

Resources