Android: Widgets and Library projects

On a recent project I’m working on with my AppJour team, we chose to use the DateSlider widget for date selection in lieu of the standard DatePicker and TimePicker views provided in the Android SDK. The installation instructions involve copy-pasting various layouts and java files in with our source. While that may work, it clutters our res folders and introduces source code that we don’t want to take explicit ownership of. Having it available as a library project that we could simply reference would be a much better solution.

The Problem

The repo provides a demo project that I assumed could simply be turned into a library via the project’s Android properties. Turning into a library was as simple as putting a check into a check box and a clean-rebuild resulted in a still-stable DateSlider library project.

I then attempted to reference the new library project from my project. Auto-rebuild kicked in and my console screen immediately filled with red complaints about resources referencing invalid attributes. e.g.:

res/layout/altdateslider.xml:10: error: No resource identifier found for attribute 'labelerClass' in package 'com.googlecode.android.widgets.DateSlider'

After some research, I came across this Android bug report. The tl;dr version of it is this happens because of the way that the R.java resource class is produced when referencing a library: All xml from the library project is transferred into the same xml namespace as the main project. This causes the layout files in the library project to become invalid as they’re now referring to an attribute (defined in attrs.xml) that no longer exists in the library’s namespace but instead exists in the main project’s namespace.

Until the Android resource generator gets smarter about including libraries (and it still wasn’t in r14 released earlier this week), there doesn’t seem to be a very elegant way to deal with it. The bug report comments suggest that until a better solution is provided by Google, you should just copy-paste the layout files from the library to your project and change the xmlns line that references the library to instead reference your own project. In the case of DateSlider, every layout file would need the following change made on line ~4:

WAS: xmlns:app="http://schemas.android.com/apk/res/com.googlecode.android.widgets.DateSlider"
IS: xmlns:app="http://schemas.android.com/apk/res/com.appjour.android.OurAwesomeApp"

This solution would work, but we would still be taking ownership of the code and would need to work around these changes when updates to the library were made.

The Solution

Instead of manually copy-pasting, editing and checking in all DateSlider layouts, I chose to instead create an ant task to do it for me and then clean up after itself. The deploy task is run before any of the Android build tasks execute and executes the clean after all android build tasks complete. See below for the ant file:

When the deploy task is run, the layout files are copied from the library into the main project and their namespace reference changed to the main project. The clean task simply deletes them from the main project. These tasks need to be executed from the root of the main project and assume that the library project root is at the same level in the file system.

So far it’s worked great and if DateSlider changes in the near future, we have minimal work to do on our part. Ideally, we’ll just do a pull and everything will work. Worst case, we may need to update our Ant script if namespaces change or layouts are added / removed.

3 thoughts on “Android: Widgets and Library projects”

  1. Thanks for your solution but I’m unable to get it work.
    Replacing xmltoken cause that my activity class go to search for DateSlider in my package instead of the library project.
    It give me an error like this:

    01-19 18:16:49.414: E/AndroidRuntime(3831): android.view.InflateException: Binary XML file line #2: Error inflating class it.liguria.asl4.android.widgets.DateSlider.SliderContainer
    01-19 18:16:49.414: E/AndroidRuntime(3831): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:581)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at android.view.LayoutInflater.inflate(LayoutInflater.java:386)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:209)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at android.app.Dialog.setContentView(Dialog.java:421)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at com.googlecode.android.widgets.DateSlider.DateSlider.onCreate(DateSlider.java:95)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at android.app.Dialog.dispatchOnCreate(Dialog.java:307)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at android.app.Activity.createDialog(Activity.java:886)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at android.app.Activity.showDialog(Activity.java:2557)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at android.app.Activity.showDialog(Activity.java:2524)
    01-19 18:16:49.414: E/AndroidRuntime(3831): at it.liguria.asl4.android.activity.RicercaActivity$2.onClick(RicercaActivity.java:108)

    Any idea? Thanks Andrea

  2. Sorry but I think you didnt get the point. I try explain better. I have already copied above files in my project and substitute the xmlns:app attribute with the correct url/patj but I get this error. To make the example clear android look for Dateslider in com.appjour.android.ourawesomeapp.DateSlider.
    Any Idea?

  3. Sorry Update. This morning everything is ok. Damned cache, If I have a dollar for every time I became crazy around a problem that simply become solved for magic after restart the phone or my machine I would be rich! 🙂
    Thank you for your solution it work like a charm now. Bye

Comments are closed.