One code base, two applications on Android
It was my understanding that application package name and the internal java package names in your Android project were distinct. Application package names need to be globally unique, internal package names can be whatever the hell you like. So, if you are using the same code base (with various resources) to build multiple applications, this all should be fairly painless ... you would think.
Turns out, this is a massive pain in in the arse. If you change the package attribute for the manifest element in your AppilcationManifest.xml (this is the application package name, the one that needs to be unique) to something other than the package containing your Application object, you will quickly enter a world of hurt. In my case I am trying to use net.cannonade.glossary for my internal java package name and net.cannonade.ballet for my application package name.
As this post notes, once you change the application package name you will discover that all your source files will compain because they can no longer locate the resources that they were previosly getting from net.cannonade.glossary.R.
At this point I probably should have stopped. If I am writing script to run through all my source and modify code from net.cannonade.glossary to net.cannonade.ballet, then my ideal of having generic source and application specific resources seem unattainable. But I had commited to trying to figure this out, so I stumbled blindly onward.
So you go through all your source files and add:
import net.cannonade.ballet.R;
This resolves all the compile errors because my application classes can now find the generated resource files. Seems painfull, but all working so far. In fact, I run the app at this point and it all seems to work fine.
Everything was not as it seemed. It seems Eclipse gets very confused about which app it is debugging if there are any previously installed apps on the emulator (with different appilication package names). So it seemed like it was working fine, because it was running my previous build with the old package name. So you need to start fresh and delete those old binaries from the emulator. To do this you need to dig through a alarming number of settings screens:
Application Screen/Settings/Applications/Manage Applications/app/uninstall/OK/OK
Snarky side note - To do this on the iPhone, I would have to:
long press app/X/OK
Ok. So once I figured this out and was debugging the right binary, I discovered a problem that I have not been able to figure out.
When I need to show a new activity in my app, I use the following piece of code:
Intent newIntent = new Intent();
newIntent.setClassName("net.cannonade.glossary", "net.cannonade.glossary.GlossaryRoot");
startActivity(newIntent);
The first time I do this is from my android.intent.category.LAUNCHER activity, SplashScreen.java. My splash screen code just shows my company logo for a short timeout and then starts my root activity (using the code above).
Another snarky side note - To do this in XCode, create default.png
My problem is that startActivity throws an ActivityNotFoundException:
Unable to find explicit activity class {net.cannonade.glossary/net.cannonade.glossary.GlossaryRoot}
My intial thought was that, as a result of the changed application package name, the package name was different in some way. I thought the best way to determine what the new package name/class name should be is to update my application manifest to use GlossaryRoot as my android.intent.category.LAUNCHER activity and then check what those two strings should be by querying them from the loaded activity in the onCreate method:
String thisPackage = this.getClass().getPackage().getName();
String thisClass = this.getClass().getName();
So my app loads up with GlossaryRoot as the new LAUNCHER activity and I expected the thisPackage and thisClass variables to contain something other than net.cannonade.glossary and net.cannonade.glossary.GlossaryRoot respectively.
To my dismay I discovered that when queried from the launched activity, the package name and class name matched what I was passing into my intent at run time. So basically if the Activity is loaded based on the manifest LAUNCHER attribute, it is fine. If I try and load it at run time, with exactly the same package name and class name, it fails to find the activity.
I am stuck and have decided, given that I have to modify all my source anyway, I will just update my script to use net.cannonade.ballet everywhere. When I need to ship a new app with different resources, I will update my script to change it to that new package name.
So perhaps I am doing something fundamentally wrong here. Perhaps there is simple way of getting around this problem. If there is an Android gronk out there with some answers, I would love to hear from you.
Permalink - Comments - Tags: Development,Android
Tingo Teams on This Week in Startups
I worked up some courage and went to the local This Week in Startups meetup hosted by Matt Stone of simXchange and organized by Leo Perez of Pure Nutrition. It was very interesting meeting up with other Sydney startups and talking about their projects.
I was very excited (and terrified) to get a chance to pitch to Jason Calacanis, Tyler Crowley and Lon Harris and I was blown away by the positive reaction to our product and the awesome feedback that we got.
To top of the experience, when I got back to my desk I watched the end of the show to see Tingo Teams chosen as the best pitch. This was 100% down to the awesome product and very little to do with my abilities as a salesman, which is definitely better than the other way around.
I can probably stop talking about this now.
Permalink - Comments - Tags: Development,iPhone
Hacking my phone bill
Because I am a huge nerd, I sat down for five minutes in the local Telstra shop and wrote a python script to analyse an old mobile bill to work out what cap plan I should get.
f = open ("bill.txt", "r")
lines = f.readlines()
# flagfall in cents
flagfall = 37
# cost per thirty seconds in cents
perthirty = 40
#total in cents
total = 0
#total seconds logged
total_seconds = 0
#calls made
calls = 0
for line in lines:
vec = line.split(":")
if (len(vec) != 3):
continue
print vec
seconds = int(vec[0]) * 60
seconds += int(vec[1])
total += flagfall
total += perthirty * (seconds / 30.0)
total_seconds += seconds
calls += 1
# number of sms that month
sms = 74
sms_cost = 74 * 25
total += sms_cost
print "$" + str(total/100.0) + " for " + str(calls) + " calls, " + str(total_seconds/60) + " mins, " + str(total_seconds%60) + " secs"
f.close()
It assumes a bill.txt file with the following format:
0:30:00
0:30:00
0:30:00
0:30:00
0:30:00
0:30:00
Permalink - Comments - Tags: Development
Significant Change Map
At TingoFamily we have been doing some testing with the new Apple iOS4 Significant Change API. The following image is a map (slightly offset to protect the innocent) of position changes over the weekend:
The fact that the user in question never went west of the body of water in the center of the map leads me to wonder about the utility of the data from this API. I am going to try out doing a full GPS query when the app gets woken up on significant change to see if I can get better results.
Permalink - Comments - Tags: Development,iPhone
Testing iOS 4.0 Location APIs on the iPhone
In the course of investigating the new iOS 4.0 location APIs, I have found that the documentation was not entirely clear when describing the various cases associated with suspended and terminated apps on the device. Combining this lack of clarity with the inability to set location through the iPhone simulator, resulted in a reasonable amount of frustration.
I found the only way to effectively test this stuff is to get on a train and travel around with your app in the debugger.
This is not exactly ideal, so I built a simple test harness to switch between the various location APIs and log all the responses that the app receives. You are welcome to download, use, modify, etc at your own risk. It is pretty straightforward, but might save you some time building something to test this stuff out. If there are any glaring errors, please let me know.
Update 17 August 2010
I have found testing the app that when I am woken up from a terminated state, I get very little time to do anything. My first cut logging code was fairly inefficient so I have updated the app to do the bare minimum to write out log messages.
I also found a bug in my wake up code that instantiated a CLLocation and assigned it to a CLLocationManager, this compiles without no warnings because init returns an id which you can happily assign to anything you like, but fails when the app tries to set a delegate on the object. I have fixed this in the current version so you should get all your wake up log messages.
Update 18 August 2010
With some more testing of the wake from terminate case, I discovered that I was adding the view controller to the window in the wrong place. Adding it in didFinishLaunchingWithOptions is fine if you aren't using background location services, because there isn't a case where you don't want to create the UI. In the background location processing case, you want to be able to start up and not do all the UI overhead. So I have moved that code into applicationDidBecomeActive. That way when the app gets started by the user it adds the subview if the UI has been initialized (by checking if the window has any subviews).
Update 20 August 2010
Thanks to Daniel's suggestion I have added a Github repo for the project.
Permalink - Comments - Tags: iPhone,Development
[First Page] [Prev] Showing page 13 of 24 pages [Next] [Last Page]