I'm building a sample multi module app where I have 1 library common, which has a MainActivity and a simple TextView. The TextView has no text defined in the xml and the MainActivity has binding.tvText.text = getString(R.string.url).
I have 2 modules google and facebook, where there is no Activity, their manifests include:
<activity android:name="com.example.common.WebViewActivity"
android:exported="true">
and I have listed the library common as a dependency for both modules (implementation project(path: ':common')). The TextView appears blank, when it should show either www.google.com or www.facebook.com, but what's interesting is that if I set the xml's text to #string/url, it works perfectly for both modules. Even changes like textColor aren't reflected in the MainActivity!
This is an issue as I want to load a WebView in the Activity with a different url for each module, but none of the changes are picked up. Any advice?
Each module has it's own R resources class that references the resources in that same module. When you use R.string.url in your runtime code, it is explicitly pulling the com.mybasemodulepackage.R.string.url resource, not some resource that might be defined in some other module.
When you use #string/url in your XML, the layout inflator pulls the resource from the closest resource scope, the one from the package name that the app was built with.
I'm not sure of the best solution, if setting it in XML is not appropriate for your situation. You might have this common activity acquire the URL from the extras passed to it. Then each module can pass the URL when they open the activity.
Related
I am investigating custom annotation processors for Android applications.
I have a use case where I would like to be able to use an annotation processor to read/amend the AndroidManifest.xml and add the following intent section for all Activities mentioned there in?
<intent-filter>
<action android:name="com.my.package.name.my_activity.open"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Is this possible?
It is a very interesting question but I don't think you could achieve such a task with an annotation processor since those work at Kotlin/Java code generation level.
How would you annotate XML #Something and have it still be valid XML?
Take a look at this:
KoltinConf18 - Annotation processing in Kotlin
At 7:18 Zack goes over annotation processing in Java and it basically says:
Happens at compile time
And you cannot modify code, just generate more
So by using barebones annotation processing you can't really modify the already existing AndroidManifest.xml.
An alternative would be writing a Gradle plugin that generates those bits of XML and merges it with the current XML file that already exists within the project.
Something from the top of my head could be:
Create an annotation and mark all activities that you want to introduce that bit of code
On the plugin side; when you are writing the Gradle task; you may use reflection and figure out which classes are annotated by such extension. Or just make the programmer put those activities in a specific directory inside the source folder, which would be way easier
With the fully qualified class names, you may look at the <activity> nodes in the AndroidManifest.xml, filter out the class names that don't match the list of annotated class names
Modify those nodes with the piece of code you would like to inject.
To get started on how to write a Gradle plugin take a look here
A simple example to get you started could be:
Step 1
You create a separate module to write your plugin if it gets too cumbersome but for this simple example I decided to stick it right in the build.gradle.kts. It doesn't need to be a kotlin Gradle file, but I am more proficient in Kotlin than in Groovy :)
As you can see I have created a text testFile.txt in the root of the project.
In code I just navigate to it and read it; print it's content and then modify it.
You could do the very same thing with your AndroidManifes.xml. Then you would need to recursively iterate over the source files from your srcDir looking for all of those activities annotated by your special annotation and store all of the fully qualified class names inside a List<String>. Then do the necessary replacements inside the AndroidManifest
Note that with this basic configuration the Gradle task appears in the Gradle tab inside the others category, to change that is a little bit off of the scope of annotation processing.
Step 2, profit
It works, as you can see the file has been updated and the println statements show the previous content of the file before modifying it
You could have a template AndroidManifest_template.xml then using a gradle task go through the AndroidManifest_template.xml and generate the real AndroidManifest.xml which would be used to build the app.
In other words, AndroidManest.xml would be a transient part of the build and you could use any XML preprocessor you want to take the template and turn it into the real file.
In case you want to add these intents conditionally depending on flavour of your app, you could use gradle flavours and manifest merging to achieve this - read more about flavours at https://developer.android.com/studio/build/build-variants
Also refer to following question for example of using gradle to modify manifest
https://stackoverflow.com/a/22759572/9640177
I created a simple Android project out of my curiosity. It contains two modules: app and lib. Each module has its own package name. I created one string resource in the lib module and one string resource in the app module. Like this:
<string name="my_str">my_str_from_lib</string>
<string name="my_str">my_str_from_app</string>
Then I set the texts to two TextViews in the app module:
fromLib.text = resources.getString(ru.maksim.sample.lib1.R.string.my_str) // here I expected to see the string from the lib.
fromApp.text = resources.getString(R.string.my_str)
In both cases it was my_str_from_app
I ran Lint and thought it would detect the fact of resource overriding. But it didn't.
Is there a chance to detect this situation? Not necessarily with Lint. Other tools suit me too.
Well you can do a global search in your root folder. Find all instances of my_str and make sure its only the string.xml in your app that uses it as name.
The answer here is a script that parses a resulting XML to which all the string from the main app module and all its libraries are merged and finds all the duplicates.
My Problem might not be valid. And the points I mention here might be little incorrect as I am neither perfect nor expert.
I have a shopping application and I want to start building a modular application. Like I want to add Affiliate User( the module which adds certain functionality or this will display some extra pages in an application) in the application.
A similar situation happens in the Frameworks: We add and enable the certain module and in return framework load everything as required.
for this, I want following changes like:
add an entry in the NavigationView displaying "Affiliate Label".
load fragments (just adding one more fragment for one more label/option from navigation view).
Let's say I have a library project that contains a Fragment and all relevant code.
How can I build the application automatically let's say just by writing "true" somewhere in the XML?
Automatically here means label is added, Intents are performed on click of label etc.
<Modules>
<enable>true/false</enable>
</Modules>
This is just the simple scenario.
You could do this (like everywhere when it comes to writing code) in many possible ways.
The "file" way:
Make a new file named something like modules.txt with key value pairs. Load the file and check whether a module is enabled or not.
The "Constant" way:
Make an abstract class which only contains public static final variables which describe your modules.
The "package manager" way:
See create Android Application plugins/extensions (apk)
The "multiple" apk way:
Note that this is not reccomended!
we encourage you to develop and publish a single APK
multiple apk support
To add to codewing's answer, you can also use Gradle's resource management capabilities to accomplish this, so you only ever need to look in one place for an enabled/disabled status.
For this, you have 2 solid options.
The first starts with a boolean which can be split by flavor:
<bool name="module_x_enabled">true</bool>
The second would be to inject your values into a String resource after Gradle merges the resources by adding something like this to your build.gradle file, then comparing that enabled value:
<string name="module_x_enabled">MODULE_X_ENABLED_PLACEHOLDER</string>
android.applicationVariants.all{ variant ->
variant.mergeResources.doLast{
replaceInValues(variant, 'MODULE_X_ENABLED_PLACEHOLDER', MODULE_X_ENABLED)
}
}
def replaceInValues(variant, fromString, toString) {
File valuesFile = file("${buildDir}/intermediates/res/merged/${variant.dirName}/values/values.xml")
String content = valuesFile.getText('UTF-8')
content = content.replaceAll(fromString, toString)
valuesFile.write(content, 'UTF-8')
}
Where MODULE_X_ENABLED would be a setting in your gradle.properties file like:
MODULE_X_ENABLED=true
Edit: or better yet,
Why not pull the settings from some kind of server so that you don't need to rebuild and relaunch to update a client's module?
I have 10 apps based of of one Library Project. I have an activity in the Library Project that shows a list of images. The images are different for every App, and are in the individual project. How can I create a data structure of the image data so that the Library Project activity can loop through it?
CommonsWare's answer seems like a good idea and I would try that first.
Another approach would be to create an empty integer-list inside your library:
<integer-array name="resources">
</integer-array>
and inside every project list drawables that would be actually used:
<integer-array name="resources">
<item>#drawable/res1</item>
<item>#drawable/res2</item>
<item>#drawable/res3</item>
</integer-array>
This way you have a reference to it inside library and can loop over the list, that will override library's empty list.
No code is running on the app that can call any of those APIs or sent anything to with Intents
Then how is your library code ever going to get executed?
If the answer is "I am publishing components that go in the manifest of the hosting app", then use <meta-data> elements in the manifest to allow the hosting app to point you to an XML resource file that contains this configuration information. You can see this with app widgets, searchable activities, and so forth.
I have an Android library MyLib containing everything I need for my app (targeting Android 2.2). This library has an XML resource:
drawable/main_background.xml
In my Application MyApp project I reference MyLib. Here I want to override specific resources (i.e. branding). So I added a background image in MyApp:
drawable/main_background.png
Eclipse keeps giving me this error:
[com.mycom.mylib.myapp] res\drawable\main_background.xml:0: error: Resource entry main_background is already defined.
[com.mycom.mylib.myapp] res\drawable\main_background.png:0: Originally defined here.
How can I override the resource in the library project?
You cannot simply override resource ID (it's the resource ID you are overriding, not the actual file) with a file with different extension in Android SDK. However, you can do the trick by putting in your project xml file with the same name (main_background.xml) and fill it in a proper way to display your new file (main_background.png), which you need to rename earlier. All syntax you need is descibed here:
http://developer.android.com/guide/topics/resources/drawable-resource.html
, in your case it could be simply (assuming you put this in your non-library project as main_background.xml, and you have your new png as main_background_new.png):
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/main_background_new" />
With above solution, you could refer to #drawable/main_background from your project and it should use your file included with that project, instead of a library one.
[com.mycom.mylib.myapp] res\drawable\main_background.xml:0: error: Resource entry main_background is already defined.
[com.mycom.mylib.myapp] res\drawable\main_background.png:0: Originally defined here.
I don't believe you can have the same file name even with different extensions. Try naming the png something else.
Now, i've not used overriding, So this seems odd as you'd expect this to be how you override the asset. However i think you've either got the two assets in your lib named the same. And that in your project it might be ok to have an asset with the same name. I would however check that its ok to have different types. XML is different than png, and if you access the asset from code you could get type errors.
Let me clarify the above point. I understand that a library project can have an item with the same Resource ID as an item in your application.
However the error above suggests that both main_background.png and main_background.xml are in the same project ([com.mycom.mylib.myapp]) which i don't believe is correct.
Further reading
This page describes the various types of project including the library project http://developer.android.com/tools/projects/index.html
Now i don't know where i got the impression from but having looked again it simply doesn't state anywhere that you can override a resource by using the same resource name. God knows why i thought that was a feature.
So no, the same rule applies as far as i can tell, that resources have to be named uniquely even across library projects, otherwise the generated resource ids will conflict. (The error your getting)
What is explained is how resource conflicts are managed.
Resource conflicts Since the tools merge the resources of a library
project with those of a dependent application project, a given
resource ID might be defined in both projects. In this case, the tools
select the resource from the application, or the library with highest
priority, and discard the other resource. As you develop your
applications, be aware that common resource IDs are likely to be
defined in more than one project and will be merged, with the resource
from the application or highest-priority library taking precedence.
The system will use the resource with the highest priority, discarding everything else. Whats odd, is that you would think that a compile error wouldn't occur as the compiler should be discarding the resource. This makes me believe that the original poster had the similarly named assets in the same project, and not across the lib and project.
I haven't read anywhere that this is actually an intended feature. Got any links to say otherwise? (comment them)
So one 'solution' to this problem, which I do not consider to be an answer is the following:
Define an XML document in the library in question (we'll call it bunny.xml), and have it refer to another xml of a similar name (bunny_drawn.xml) with the actual content to be displayed.
Then, in the target project, override bunny.xml with another and use it to refer to an image with a different name instead - bunny_image.png
This does not however solve the problem, firstly because we aren't technically overriding a png with an xml (although the effect is somewhat close to that). Secondly because one of the key features of overriding resources is they are overridden, i.e. they are NOT compiled into the APK:
the tools ensure that the resource declared in the application gets
priority and that the resource in the library project is not compiled
into the application .apk
But the bunny_drawn.xml will still be compiled in! We can sort-of overcome the second point, by not only defining the image to be replaced in the target APP, but also replacing the old target bunny_drawn.xml with a blank xml. (or, as Fenix pointed out, you can have the contents of bunny_drawn.xml inside bunny.xml in the first case - the fact still remains that the resource ID can't be replaced...)
So my final conclusion is that this need to be submitted as a bug in the Developer Tools.