Declare attributes of library module which can be modified by app - android

I am trying to create Android library module (API 21+) for my application, which will implement some of the business logic shared across various applications.
The issue is that I want to declare attributes in this library module, for which I will be able to set values in app modules of aforementioned projects.
The funny thing is, that my current solution works on emulator but not on physical devices.
Here is how I have it now (simplified, irrelevant parts omitted):
Library's attrs.xml:
<resources>
<attr name="lib_Background" format="reference"/>
<attr name="lib_Logo" format="reference"/>
</resources>
Applications styles.xml:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="lib_Background">#drawable/back</item>
<item name="lib_Logo">#drawable/logo</item>
</style>
It does not matter whether I try to access these resources from xml of library's layout like this:
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="?attr/lib_Background"
android:scaleType="centerCrop"/>
or programatically like this:
fun Context.getDrawableFromAttributes(idOfAttribute: Int): Drawable? {
val a = this.theme.obtainStyledAttributes(intArrayOf(idOfAttribute))
val resourceId = a.getResourceId(0, 0)
a.recycle()
return this.resources.getDrawable(resourceId, this.theme)
}
Still I have this image properly loaded on emulator, but "null" on physical device.
Is there some better or correct way how to achieve this?

I have kinda forgot about this question.
The issue was that I have used broken PNG for one particular density which caused it to be null. So it was not and error in implementation at all and this approach is absolutely valid.

Related

Meaning of 'android:' prefix within attribute value (e.g. "android:imageButtonStyle")

I want to style all my ImageButtons in a theme. After searching for quite some time I found the solution to my problem. But I don't know why it works like it does.
My main layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<ImageButton
android:id="#+id/imageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#mipmap/ic_launcher_foreground" />
</LinearLayout>
This is my original theme that didn't work. It styles my TextView but ignores the ImageButton. The result is shown in the screenshot below.
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:imageButtonStyle">#style/redBackground</item>
<item name="android:textViewStyle">#style/redBackground</item>
</style>
<style name="redBackground">
<item name="android:background">#FF0000</item>
</style>
</resources>
And here's the theme that works:
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="imageButtonStyle">#style/redBackground</item>
<item name="android:textViewStyle">#style/redBackground</item>
</style>
<style name="redBackground">
<item name="android:background">#FF0000</item>
</style>
</resources>
The only difference is the missing 'android:' prefix in front of the 'imageButtonStyle' attribute.
So my questions are:
What is the difference between imageButtonStyle and android:imageButtonStyle?
Why does android:textViewStyle work but not android:imageButtonStyle? They are both defined the plattforms 'attrs.xml'.
Why is there no textViewStyle (without android prefix)? Removing the prefix yields an error.
Where are the attributes defined that have no prefix? Apparently not in the plattforms 'attrs.xml'.
Where can I find proper documentation for the whole style stuff? Of course I halve already read the respective Google docs (https://developer.android.com/guide/topics/ui/look-and-feel/themes.html). But still i have basic questions like this one.
Interestingly, it seems like the 'android:imageButtonStyle' version has worked some years ago: How to apply an style to all ImageButtons in Android?. I haven't tested that myself, though.
And here's the post that proposed removing the android prefix. Including unanswered comments that ask why it works: buttonStyle not working for 22.1.1
android tag that you use is used for attribute coming from Android SDK.
app tag is used if you are using the support library.app is just a namespace for any custom parameters for a custom View.
This can be anything but if you see the root element there's probably a line xmlns:app="http://schemas.android.com/apk/res-auto" that assigns the namespace
You may also see other namespaces if you are using custom views (of your own or form a library).
In case that anyone else stumbles across the question: I've found the answer in this Droidcon talk: https://youtu.be/Jr8hJdVGHAk?t=21m12s
The topic is handled in a minute starting at 21:12.
As I understand it, specifying no namespace results in the global namespace being searched which seems to include the support libraries attributes. And indeed both, the SDK's R.attr as well as the support library's R.attr define the imageButtonStyle attribute (with slightly different descriptions). However, the support library does not define a textViewStyle attribute. So that explains why you can't omit it's android: prefix.
To answer my last question concerning the documentation: Despite the Google guide and the R.attr classes' documentation, the video mentioned above and this Google I/O talk are quite informative: https://www.youtube.com/watch?v=TIHXGwRTMWI
So the only question that is left open is why the SDK's imageButtonStyle does not work.

Extend an external application Style/Theme

I want to define an android style that extends one defined in a different Application/package not imported as library.
From the definition of an xml reference to an android resource here:
#[<package_name>:]<resource_type>/<resource_name>
seems that is possible to specify an external where the resource is defined ( as used to reference system resource #android:string/name )
Now, i have my main app ( package: com.example.test ) that define an attribute and a theme like this:
<declare-styleable name="TestAttrs">
<attr name="testColor" format="color" />
</declare-styleable>
<style name="TestTheme" parent="#android:style/Theme">
<item name="testColor">#FFFF0000</item>
</style>
if in a second, different app I try to use the previous package to define a style using the syntax shown above I get an error saying that the resouce can't be found.
<style name="SubTheme" parent="#com.example.test:style/TestTheme">
<item name="com.example.test:testColor">#FF00FF00</item>
</style>
is it possible to tell the compiler where to look for the package?
do i have to define my initial style/attributes as shared?
Did you declare the namespace:
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:com.example.test="...external resource's namespace">
package_name does not refer to external applications on the device, but other library-project packages you are building against (each have their own resource table, which is then merged).
To access private resources in external applications you can use PackageManager.getResourcesForApplication() or expose a ContentProvider and do it yourself.

Why can't I use this attribute in my android theme?

To start with some context, I'm trying to style the background color of a SearchView widget. A really insightful so answer to this problem has already been posted, and I learned immensely from it.
There is one gap in my understanding though, and I'm hoping someone can explain it to me. When I create a theme, such as the following:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="MyCustomTheme" parent="android:Theme">
<item name="android:searchViewTextField">#android:color/white</item>
</style>
</resources>
Eclipse compiles with an error saying it doesn't know about the attribute:
error: Error: No resource found that matches the given name: attr 'android:searchViewTextField'.
However, if I re-declare the attribute:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<declare-styleable name="CustomSearchView">
<attr name="android:searchViewTextField" format="reference" />
</declare-styleable>
</resources>
Eclipse responds with an error saying:
error: Attribute "android:searchViewTextField" has already been defined
Eclipse seems to be aware of the attribute, but conveniently forgets about that attribute when I want to use it. (I wonder if there is some context switching going on in the background)
At any rate, if I delete the problem code then I can see my custom theme inheriting searchViewTextField from its parent. I just don't understand why I can't supplant it with my own.
(The other answer mentions android.R.stylable, but that file is obsolete in api 16)
Thanks in advance.
Relevant Android sources:
themes.xml, attrs.xml, and search_view.xml (sorry, two link limitation).
The answer in the SO answer you linked explicitly says that you cannot specify searchViewTextField in your own theme because it is not a stylable resource. You need to modify it's value in code as per the other answer.
The question was, why can't I use the theme attribute "android:searchViewTextField"?
Out of suspicion that the compiled Android.jar file was not in tune with the published Android source, I ripped open the jar and started decompiling (a first for me). After poking around, it does appear that someone went into R$style.class with a machete and hacked out a massive chunk of the resources. As far as I can ascertain, it must have been done deliberately and perhaps even manually.
This kind of compiled source modification is not without precedent in other similar frameworks, but it sure as hell is confusing to anyone trying to debug the framework.
Ironically, auto-generated R files all have a header that says,
/* AUTO-GENERATED FILE. DO NOT MODIFY.
Kinda pointless when you make it routine procedure to modify the files.

Application branding

I would like my application to be available under different brandings - this means that it must be possible to easily change background bitmaps in few places, but also change text resources.
My plan is to use themes and styles, I know they can be used for switching bitmaps but will they allow me to change also texts in TextViews for example?
Is it also possible to specify in style or theme a text identifier and later dynamically read it in code?
[EDIT]
After some investigation, of course texts can be substituted with styles. Another problem that comes with application branding is the need to change package name - otherwise Google Play wont accept our branded application.
[EDIT]
After more investigation, below I include small sample on how to add two switchable themes that will allow to substitude drawable in activity layout, and text resource in textview. Whats left is to call setTheme(R.style.Theme1); or setTheme(R.style.Theme2); in onCreate before setContentView.
<-- attrs.xml />
<resources>
<attr name="ProductName" format="reference"/>
<attr name="ProductBackground" format="integer"/>
</resources>
<-- styles.xml />
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="Theme1" parent="android:Theme.Light">
<item name="ProductName">#string/s_product_1_name</item>
<item name="ProductBackground">#drawable/back_1_vga</item>
</style>
<style name="Theme2" parent="android:Theme.Light" >
<item name="ProductName">#string/s_product_2_name</item>
<item name="ProductBackground">#drawable/back_2_vga</item>
</style>
</resources>
<-- my activity layout />
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?ProductBackground"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
tools:context=".MainActivity"
android:background="#ff0000"
android:text="?ProductName"
/>
</RelativeLayout>
There are two aproaches to make different brandings of you app:
1) Split your app into multiple library projects, common code is in lets say common_lib library project, and all your brandings are in separate projects using common_lib but changing some of the resources, like strings, some drawables, also package name. This is the aproach we have choosen in our app.
2) Use gradle product flavors: http://developer.android.com/sdk/installing/studio-build.html, I think this should now be the prefered solution.

Use different icons with different Android SDK versions

I have icons for my Android menu. On Android 3+ I'm using a black ActionBar so the icons are white. However, on Android 2.x the menu is inherently white which means the icons are nearly invisible. How can I use different menu icons for different versions? I'm assuming I can do it using different drawable directories like res/drawable-mdpi-v11, but I'm wondering if there is another way so I don't have to create a bunch of different directories as I add versions or pixel densities.
EDIT: I put dark versions in res/drawable-mdpi and res/drawable-hdpi for use with Android 2.x and I put light versions in res/drawable-mdpi-v11 and res/drawable-hdpi-v11 for use with Android 3.x and higher, but my Android 2.1 (sdk 7) emulator is still showing the light version.
Any idea why?
You can Select a theme based on platform version, as outlined in the Styles and Themes dev guide. Define a style in your res/values/styles.xml like this:
<style name="ThemeSelector" parent="android:Theme.Light">
...
</style>
Then in a res/values-v11/ folder, select your theme (probably Holo, if you're dark)
<style name="ThemeSelector" parent="android:Theme.Holo">
...
</style>
Then add icons to that style. For instance, here's a snippet from the styles.xml file from the HoneycombGallery sample application.
<style name="AppTheme.Dark" parent="#android:style/Theme.Holo">
...
<item name="menuIconCamera">#drawable/ic_menu_camera_holo_dark</item>
<item name="menuIconToggle">#drawable/ic_menu_toggle_holo_dark</item>
<item name="menuIconShare">#drawable/ic_menu_share_holo_dark</item>
</style>
The bottom 3 elements are all icons in the drawable directories. You'll still need at least one folder per resolution-specific set of icons, but you can combine the light & dark icons into the same folder, but you won't have to have different folders of icons for each platform version. Also, you'll need to list them as references in the values/attrs.xml file, like this:
<resources>
<declare-styleable name="AppTheme">
<attr name="listDragShadowBackground" format="reference" />
<attr name="menuIconCamera" format="reference" />
<attr name="menuIconToggle" format="reference" />
<attr name="menuIconShare" format="reference" />
</declare-styleable>
</resources>
At which point you'll be able to refer to them within your layout XML using the "?attr/NameOfYourDrawable" dereference, like this:
<item android:id="#+id/menu_camera"
android:title="#string/camera"
android:icon="?attr/menuIconCamera"
android:showAsAction="ifRoom" />
Found on the android dev site: http://developer.android.com/guide/practices/ui_guidelines/icon_design_menu.html
Warning: Because these resources can change between platform versions, you should not reference these icons using the Android platform resource IDs (i.e. menu icons under android.R.drawable). If you want to use any icons or other internal drawable resources, you should store a local copy of those icons or drawables in your application resources, then reference the local copy from your application code. In that way, you can maintain control over the appearance of your icons, even if the system's copy changes. Note that the grid below is not intended to be complete.
/res/drawable-hdpi (for Android 2.2 and below)
/res/drawable-hdpi-v# (for Android 2.3 and above)
Have you also tried testing this on a 2.1+ phone and not an emulator? If you don't have a phone, try creating another AVD? I'm afraid that you're going to need the separate folders.
Hopefully this helps.

Categories

Resources