Thursday, September 9, 2010

Adventures with Android Library Projects

It's been a long time, although I have continued my life as a hobbyist Android developer !

Today, I will share my wonderful adventures with Library Projects !

Library Projects are a new feature in Froyo, that I was really excited about (see here) !
Library Projects allow you to share some whole parts of your applications, resources included. The main and immediate use is to create several versions of the same application.
For instance, if you have a payed version and an ad supported free version, you need to have 2 differents applications, but with very few differences.
In my case, I have two different games "Word Prospector" and 'Chasseur de mots", that are, basically, the same game, but with different data ( the dictionary : it is a english and a french version of the same game ). As the dictionary is 50% of the apk weight, I didn't want to have only one application with two dictionaries.
But, let's face it : until now, I dealed with those two versions as badly as I could : when I made a development in one of the game, I had then to report it in the other game.
I could not even copy / paste all the files, as some divergence appeared. For instance, some key values for middleware ( Flurry for instance ).

So I was really impatient to try this new Android feature !

The start :
Reading the Google doc, it seems really easy to use : make a library project, click "Is Library" in the project properties, then in the application projects, just select this library as "used library".

First step, first success...
The first step was creating the library. I saw in the documentation that you can convert an existing project into a library project, with just one click ! But this arises a question : is this library still considered as a legit application ?
So I tried to launch this library version... And it worked perfectly !
It wouldn't last :)

Second step, first fail !
Once I had my library, in just a matter of seconds, I tried to use it in my french version of the game.
I started to copy my whole project (just in case thing would be funkier than expected ! ).
Then I removed one activity from this game, expecting Eclipse to find it in the library.
So I deleted this activity file, and in the manifest, I change its declaration, by adding the path to the library activity.
So I was changing :
 <activity android:name="ScorePage"
                  android:label="@string/app_name"
                  android:theme="@android:style/Theme.NoTitleBar"
                  >
into :
 <activity android:name="com.alocaly.EnglishVersionLibrary.ScorePage"
                  android:label="@string/app_name"
                  android:theme="@android:style/Theme.NoTitleBar"
                  >


Looks quite simple, for at this moment, I really enjoyed it !
Then I try to compile it, and Eclipse started complaining : it didn't know about this activity...
Gasp... After different tries, I search on the Internet to find the solution :
You have to close and re-start Eclipse !
Ok, it just worked perfectly.
...
But, for some reasons, my confidence started to vanish...

I continued with all my activities until I had a very simple project : an application, and some data files.
All the activities were coming from the library...
The manifest was OK with this config, but I still had some compilation issue.

Next Step : dealing with Attr
As I'm using AdMob in my game, I have a custom AdMob view, and some custom attributes for this view, defined in an attr.xml file.
Trouble is : the game using the library couldn't find the csutom attributes.
Adding another attr.xlm file in the game using the library didn't help : the compiler complaining that some attributes being defined twice (aarggghhh.. )
Searching on the Internet, I found this discussion here where Xavier Ducrochet, Android SDK Tech Lead, said "At this time, the only solution is to remove the layout from the library and move it into the apps and edit the namespace to match the application package".
Ok, so I tried to do that, but couldn't manage to have a compiling solution.

So I took Admob off, while waiting for a good idea...
( my goal at this point was to have something working as soon as possible, if necessary with less features. )

Getting the application from Activities
As I said somewhere in the intro, the code for the two games is the same, but for some keys values for middlewares ( Flurry and Scoreloop ).
So I decided to put the keys in the application ( as each game already has its own application ), and let the activities get them from the application.
So I had some code like that in the (common) activities :

Application CurrentApp = getApplication();
LetterGameApp CurApp = (GameApp) CurrentApp;
MyMiddlewareKey = CurApp.GetMyMiddlewareKey();


But it just did not work : the activities are in the library, so the GameApp cast is interpreted as a
GameLibrary.GameApp - the library application-.
And in my game using the library getApplication returns a GameUsingLibrary.GameApp, that is not related to a GameLibrary.GameApp and the cast fails !

My Solution at this point was to make the GameUsingLibrary application inheriting from the GameLibrary application.
This way, the cast is still valid.

Note that if it were only for the key values, I could have stored them in some xml files. But I'm also using the application to gather some informations on the user behaviour, to help me for debugging crashes, so I call some application methods from all over my code !

Final step, final fail...
At this step, I had everything working correctly.
Both the game used as a library and the game using the library were working correctly.
It was quite late, and I was looking forward to go to bed.
I made a last test on the game using the library ( you should never make a last test !! ), and found something really strange : a breakpoint being triggered on a empty line !! And then I could step throw some comment lines. WTF ???
So I decided to clean the two projects, to rebuild them.
I then tried to launch both of them... And when launching the game used as a library, Eclipse said 'Could Not Find EnglishGame.apk'.... ???
I cleared the project once again. Same message. I modified the code a little... Same message...
I restarted Eclipse. Same message.
I reboot my computer. Same message.
I'm sure all of you have already feel the same when a very last little modification ruins your entire development day. I felt a little distressed, tired, with a hard desire to kick my computer ( and I perhaps had some bad thoughts for Google engineers... Sorry guys... ).

Finally, I unchecked the "Is A library" checkbox, and the .apk could at least be generated !

Dealing with attr, slight return
As light as they can be, my revenues with AdMob are the only income I get from my android games, and at least as a symbol, I really want to keep it, so I wanted to fix the attr issue and have AdMob back before publishing the version.

I couldn't find the solution for the attr issue, but I finally could fix my problem :
I got rid of my attr file, and of the custom attributes of the AdMob view.
And set this custom attribute values by code, and no more in the activity layouts.


Last comments
Finally, I now need to uncheck the 'Is A Library' checkbox when I want to make the first game, and recheck it when I want to make the second game.
This is a complete non sense, and I feel now like having a library that is also a legit application was just plain wrong. But the documentation is not completely clear on this point, and Eclipse partly let you do it, so it's easy to feel like it's possible when it's not really.
I really should have a library project, and two application projects using this library.

After some time using this configuration, I can tell you that Library Projects are really nice to use.
There are still some place to make this feature better, there are still some confusing things. For instance, debugging is quite strange : you debug a kind of fake file, with a path like 'MyProject/PathInTheLibrary/FileInTheLibrary'.
and this file really does not exist !
Modifying this fake file really modifies the file in the library, so every thing seems to fit perfectly in place, but it still is quite strange, and several times, I launched the wrong game.

There are still some issues - hopefully corrected in the next SDK version - but once your setup is done, and most of your issues solved, having this solution to have your code in only one place is a huge confort gain !

14 comments:

Sergi said...

Man, you really saved my life!
This "close eclipse open again" to make the manifest find the activities was a day-saver after 4 hours trying to make it work!

AndroidBlogger said...

I know exactly what you mean :)

Thanks for your comment !

TiGeR said...

very interesting piece of info...
I am struggling with AddMob in a Project Library as well, so I guess I will be looking into the code-wise solution you are mentioning... Btw is this code in the Project Library itself or in the "depending" project ?

TiGeR said...

after some more struggling I am getting that the "depending" project doesnt' recognize the AdView class :(
I did import the library within the Project Library, which also recognizes it...
Btw in the layout files did you remove the
xmlns:app="http://schemas.android.com/apk/res/my.package.myCodeBase.baseplayer"
which is needed by AddMob ?

TiGeR said...

sorry gonna be the last quiestion :P
in which Manifest file did you place the needed Addmob meta-data tag ?

AndroidBlogger said...

Hi Tiger,

My code to set up the information on the adview is in my library project ( as the setup is the same in both project : same color, refresh rate, ...)

On the other hand, the information in the manifest are in the depending project libraries ( as I have two different Admob ID, and I want different lines in admob backend tool ).

Concerning your problem, I would try :
* To make sure the Admob lib is also in the depending project, so it would know the adview
* If it's not enough, set the adview in the library project, in a generic fashion. Then call this generic filler with specific info that you put in each specific application object.

Hope it helps,

Anonymous said...

I've never used Scoreloop, but for Flurry, I do this in my game (Gem Breakers):

FlurryAgent.onStartSession(this, getResources().getString(R.string.flurry_key));

I just store the flurry_key in the strings.xml. It makes things pretty simple as the flurry_key entries in your Application projects override the flurry_key in the library project.

androidblogger said...

@anonymous :
Yes, I thought about this solution... after having used my solution, and I vaguely mention it in my post ( I said 'Note that if it were only for the key values, I could have stored them in some xml files.' ).

...
Rethinking about it, I think you're right, it would be cleaner to store the keys in the string.xml file ( or any other data file ).
I would avoid me to make my own application in the project using the library...

android programmer said...

very interesting piece of info...
Thanks.

John said...

You can fix the Android.app.Application ClassCastException issue by specifying the android:name property in the manifest file. Use the fully qualified class path to the application object that is part of the library.

Anonymous said...

This seems pretty daunting.

Can you access assets in the library project from the dependent project? I just tried and failed, even though I can access resources (layout, icon).

Have you considered using eclipse links from your "main" project(s) to files in other projects? I have used this for source code, haven't tried it for assets, etc. It also sometimes causes SVN weirdnesses, but Eclipse SVN is so sick I'm sorry I ever started using it.

Anonymous said...

Thanks so much! Closing Eclipse to enable it to find a dependency was the last thing I thought of. These kind of idiosyncrasies make me so tired.

Beppe said...

We shared the same pain, mate! :)

Drakot said...

Greetings,

I have a free and a paid app and I want a library to use the shared classes, res and stuff.
I've already done it, but there is one class in particular that i want to override the library's.

The thing is that It always initiate the library one and if I put it in a package with the same name It doesn't even compile! I thought about using the full name of the class including the package to call the activity but since an activity is calling a fragment which is calling the activity there is no way to do that without overriding ALL the classes involved which It would be absurd because I'd have to duplicate a lot of Code.

What can I do to solve this? The layout overide works fine but Classes...

Thank you for your time

bye