I have a suite of libraries distributed as 3 separate aars. Is there any way to be able to obfuscate each library independently, but keep the ability to call methods between modules internally?
E.g. if libA needs to call myMethod from libB, but I don't want myMethod exposed to clients integrating my libraries (yet all libraries must be obfuscated).
Right now I'm forced to make myMethod public and exclude it from obfuscation so it can be called from libA. Is there a better solution to this problem that won't expose myMethod to clients while still be obfuscated?
I am unsure if you have already found a solution to your problem but I have played a little and find out a working solution. I made a small HelloWorld app to show how it is done: https://github.com/jmineraud/multi-module-android-app.
It contains the repo for the lib as a git submodule (https://github.com/jmineraud/multi-module-android-lib)
The trick is to obfuscate the libraries independently, but not to optimize them (we do not want to remove any function) and that all dependent submodule must reuse the same mapping.txt file.
For instant in my submodule proguard file, I added:
-dontshrink
-dontoptimize
#-useuniqueclassmembernames # Option does not work with R8
-repackageclasses "com.your.package" # I do not want to obfuscate the whole hierarchy to keep my obfuscated library class separate and avoid obfuscation problems when the lib is used in the app project (which will be in turn obfuscated)
-applymapping "../lib-core/build/outputs/mapping/release/mapping.txt"
The classes I want to keep are annotated with #Keep from androidx.annotation.Keep
It's a best practice that library expose only interface and not Objects.
Also, interface must respect interface segregation principle.
If your client do not need to use the method, your library too. Your libA is the first customer of your LibB.
Your libraries must be compliant with single responsibility principle.
I think you have to re-work library and extract interface for your classes and expose only interfaces.
I think that this will be a tiny task, a quick solution is to add a proguard rules for each protected method if your are using java.
-keep public class mypackage.MyPublicClass {
protected void myProtectedMethod();
}
Sample case for refactoring:
//libA
class LibA {
lateinit var libB:LibB
fun serviceA(){
libB.checkLicence()
}
}
//libB
class LibB{
fun serviceB(){}
//this method is needed by libA, due to obfuscation visibility is public instead of internal
//internal fun checkLicence(){}
fun checkLicence(){}
}
the refactored code maybe:
//new library distributed only internally not to customer
// LibInternal
interface InternalLib {
fun checkLicence()
}
internal class InternalLibImpl : InternalLib {
override fun checkLicence() {
}
}
object InternalLibFactory {
fun create(): InternalLib = InternalLibImpl()
}
//LibB
interface LibB {
fun serviceB()
}
class LibBImpl(private val internalLib: InternalLib) : LibB {
override fun serviceB() {
internalLib.checkLicence()
}
}
object LibBFactory {
fun create(): LibB = LibBImpl(InternalLibFactory.create())
}
//LibA
interface LibA {
fun serviceA()
}
internal class LibAImpl(private val libB: LibB, private val internalLib: InternalLib) {
fun serviceA() {
internalLib.checkLicence()
libB.serviceB()
}
}
object LibAFactory {
fun create(): LibB = LibBImpl(InternalLibFactory.create())
}
Related
I'm quite new to building a custom annotation processor,
I've followed some tutorials & guides over here on SO too but been stuck on adding a condition for the annotated method.
The problem:
I have a custom annotation directed only for methods,
say, #RUN(isDebug: Boolean) & the method would be:
#RUN(isDebug = true)
private fun runInDebugOnly() {
....
}
#RUN(isDebug = false)
private fun runInReleaseOnly() {
....
}
So in my Annotation Processor,
is it possible to execute these functions with a condition?
I know the concept of generating a custom class & methods inside it,
But how to exactly intercept the method & use the generated method instead.
Any guidance would be appreciated.
An annotation processor only runs at compile time, and is usually used to generate new classes or code.
Sounds to me like you want to generate a new method at compile time which will call the correct annotated method depending on the build type.
e.g. when you run a debug build you want to have
fun myNewMethod() {
runInDebugOnly()
}
but when you run a release build you want to have
fun myNewMethod() {
runInReleaseOnly()
}
The rest of your app will just call myNewMethod() and it won't care about the implementation.
You could achieve this another way without using an annotation processor
fun myNewMethod() {
if (Build.DEBUG) {
runInDebugOnly()
} else if (Build.RELEASE) {
runInReleaseOnly
}
}
Is this the kind of thing you're after?
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");
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
}
I created two modules in single android project.
app module(which is default my app module)
and another added library module.
Now app module have many java classes. i want to access .Java class of app module in library module.
Module app has a class DatabaseHelper in package xyz
Now I want to import class DatabaseHelper in Library module.
DatabaseHelper is not recognized by android.
Questions,
Is it possible to import Class from a app module to another module?
any other way.
MyFiles
build.gradle(app)
compile project(':materialList')
setting.gradle
include ':app', ':Library'
Is it possible to import Class from a app module to another module?
This won't be possible as this will be creating a circular dependency.
However there is a pattern that can be utilized in this scenario:
Define the data type : DatabaseHelperContent in the library module
Define an interface DatabaseHelperI in the library module the implementer of which is supposed to provide the data.
Create a DatabaseHelperImpl class implementing the DatabaseHelperI interface, providing the data (this class is in the app module)
Initialize the interface as the instance of class ABC while referring to it in the Application class, so that the data from the class can be dynamically passed to the sub-module.
This would become even simpler with some dependency-injection framework
like Dagger where you can just specify provider of the interface in
the #Module class and use the injected data from the common provider everywhere.
No, there is no way. Rethink your design. Maybe move DatabaseHelper into library project?
In your design, there would be a circular dependency between app module and library module.
The purpose on other modules is to separate completely independent pieces of code and move them to external place. And use them in another modules.
It's quite an old question and I am sure the author has found a solution, but I think the question lacks this answer which many people would like to know.
So, actually, as suggested in other answers, this often might be caused by an issue with your architecture.
But sometimes it may be reasonable. For instance, if you need to access your application id inside a library or in many other cases.
So, if you need to access a resource from the app module in a library module,
it can easily be done with help of dependency injection.
For instance, with Dagger it can be done like this:
1.Create a module that will provide a shared resource.
Let's call it the Integration module.
#Module()
class IntegrationModule {
#Provides
#Named("foo")
fun provideFoo() = "Hey bro"
}
2.Include it in your App component
#Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
IntegrationModule::class,
YourLibraryModule::class
])
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun app(app: Application): Builder
#BindsInstance
fun context(context: Context): Builder
fun build(): AppComponent
}
}
3.Inject it somewhere in your library
#Inject #Named("foo") lateinit var foo: String
That's it.
Base Dagger implementation code is omitted for simplicity.
I have a library project/module that is used by both Android apps and regular java apps.
In Dagger 1 this project/module has property complete = false. Within there is an #Inject field that is not satisfied by any class implementation or #Provides method. The idea is to force the "top" module(s) which has complete = true to provide system specific implementation
Just for the sake of example: In the library project I have ActLogin activity that have field #Inject #Named("app version") mAppVersion. The value of this field is used when logging in into a server. ActLogin is used by several apps that use this library. Each app's module has complete = true and provides value with #Provides #Named("app version") provideAppVersion()
Documentation for migration of Dagger 2 (http://google.github.io/dagger/dagger-1-migration.html) states:
Dagger 2 modules are all declared as complete = false and library = true
and in the same time the "main" documentation page (http://google.github.io/dagger/) states:
The Dagger annotation processor is strict and will cause a compiler error if any bindings are invalid or incomplete.
The latter is obviously the correct one because when trying to build with unsatisfied inject error is produced (error: java.lang.String cannot be provided without an #Provides- or #Produces-annotated method).
The question is: is it possible to migrate this approach (deferring providing inject) to Dagger 2 and how?
P.S. Initially I thought as a dirty workaround to provide some dummy values in the library's #Module but then again - you cannot have module overrides in Dagger 2 (which is kind of WTF(!!!). Module overrides were the most useful feature for me when creating unit tests). Probably I am missing something very basic and I hope that someone can point it out :-).
It turns out that there is dedicated construct for this but it takes some time to find it out.
If you need to have a component that have a module which contains unsatisfied inject(s) - make it #Subcomponent. As documentation clearly states:
That relationship allows the subcomponent implementation to inherit the entire binding graph from its parent when it is declared. For that reason, a subcomponent isn't evaluated for completeness until it is associated with a parent
So in my case, my library project needs to be a dagger subcomponent. When I use it in my app project, my app dagger component have to include the lib subcomponent.
In code:
The library subcomponent:
#Subcomponent(modules = Mod1.class)
public interface MyLibraryComponent {
void inject(Mod1Interface1 in);
}
The app component:
#Component(modules = Mod2.class)
#Singleton
public interface MyAppComponent {
void inject(MainActivity act);
MyLibraryComponent newMyLibraryComponent();
}
Please note the MyLibraryComponent newMyLibraryComponent(); - that is how you tell dagger that your component contains that subcomponent.
Graph instantiation:
MyAppComponent comp = DaggerMyAppComponent.builder().build();
Please note that contrary to using component composition with dependencies (#Component's property) in this case you don't have to "manually" construct your subcomponent. The component will "automatically" take care for that in case the subcomponent's modules don't need special configuration (i.e. constructor parameters). In case some subcomponent's module requires configuration you do it trough the component instantiation like this:
MyAppComponent comp = DaggerMyAppComponent.builder().
mod2(new Mod2SpecialConfiguration()).
build();
For android there is a special twist if your library project contains activities because each activity have to be separately injected "on demand" contrary to regular java application where you usually inject the whole application once at start up time.
For the sake of example let's say our library project contains login activity "ActLogin" that we use as common for several applications.
#Subcomponent(modules = Mod1.class)
public interface MyLibraryComponent {
void injectActLogin(ActLogin act);
void inject(Mod1Interface1 in);
}
The problem is that in Android we usually create our dependency graph in the Application object like this:
public class MyApplication extends Application {
private MyAppComponent mAppDependencyInjector;
#Override
public void onCreate() {
super.onCreate();
mAppDependencyInjector = DaggerMyAppComponent.builder().build();
}
public MyAppComponent getAppDependencyInjector() {
return mAppDependencyInjector;
}
}
and then in your activity you use it like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
((MyApplication) getApplication()).getAppDependencyInjector().inject(this);
// ...
}
but our ActLogin activity is part of the library project (and dagger component) that is not event aware of what application will it be used in so how are we going to inject it?
There is a nice solution but please note that I am not sure it is canonical (i.e. it is not mentioned in the documentation, it is not given as an example by the "authorities" (afaik))
Project's source can be found at github.
First you will have to extend the library dagger component in you app component:
public interface MyAppComponent extends MyLibraryComponent {
That way your app component will contain all the inject methods from the subcomponent so you will be able to inject it's activities too. After all, top component is in fact the whole object graph (more precisely the Dagger generated DaggerMyAppComponent represent the whole graph) so it is able to inject everything defined in itself + in all subcomponents.
Now we have to assure that the library project is able to access it. We create a helper class:
public class MyLibDependencyInjectionHelper {
public static MyLibraryComponent getMyLibraryComponent(Application app) {
if (app instanceof MyLibraryComponentProvider) {
return ((MyLibraryComponentProvider) app).getMyLibraryComponent();
} else {
throw new IllegalStateException("The Application is not implementing MyLibDependencyInjectionHelper.MyLibraryComponentProvider");
}
}
public interface MyLibraryComponentProvider {
MyLibraryComponent getMyLibraryComponent();
}
}
then we have to implement MyLibraryComponentProvider in our Application class:
public class MyApplication extends Application implements
MyLibDependencyInjectionHelper.MyLibraryComponentProvider {
// ...
#Override
public MyLibraryComponent getMyLibraryComponent() {
return (MyLibraryComponent) mAppDependencyInjector;
}
}
and in ActLogin we inject:
public class ActLogin extends Activity {
#Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
// ...
MyLibDependencyInjectionHelper.getMyLibraryComponent(getApplication()).
injectActLogin(this);
// ...
}
}
There is a problem with this solution: If you forget to implement the MyLibraryComponentProvider in your application you will not get an error at compile time but at runtime when you start ActLogin activity. Luckily that can be easily avoided with simple unit test.