I first heard those words from a gadget loving iPhone fan-boy whom also happens to be a very good friend of mine. We don't often discuss the pluses or minuses of the 2 platforms because he's not a geek. He is, by all accounts, in love with anything Apple is willing to put on the shelves for us to consume.
He does ask how my application development is going and is at least interested in that I'm interested. He asked me one day about all the viruses in the Android App Store and when refuted by me had the evening news on his side. He did concede that they weren't necessarily viruses in the traditional sense, but rather poorly written applications that cause performance problems, consume more data than they should, etc. I exclaimed that if that were the classification for a virus then Apple's own machines are the preferred platform for the world's leading virus ... Photoshop. :)
His comment and the opinion of the main stream media stuck with me. I can't do anything about it, but it did get me wondering about the complete lack of an approval process for Android developers' submissions. Apple has one. Windows Phone 7's is unbelievably thorough. I have had 2nd hand experiences with both. Android has none. That helps keep a low cost to entry, but it does open the door for some poor code to end up on users' phones.
Thank goodness there are a few good developers out there writing quality code; like me! But wait, I wrote a "virus." It's true. I downloaded the Android SDK, the Eclipse ADT plugin, Eclipse and even the Subclipse SVN plugin. I have source repositories setup to store my code change by change and I maintain this blog to discuss the application, coding in general, etc. I can Google anything and figure out how to make something work. That doesn't mean that's how it should or needs to work. Therein lies the problem. I can write some fancy code, but you don't know what you don't know!
So, why was my app a virus? Several versions ago I switched the UI to a tabbed view. There was major refactoring involved in doing so. Shortly thereafter I was experimenting with JSON string parsing and how that might benefit my app. I was debugging something in the JSON app I was building and had left the DDMS perspective in Eclipse open while I was researching. I kept noticing screen updates in my peripheral vision but it wasn't until I stopped what I was doing to investigate that I realized I had a coding mistake. My other app had been opened to do some other work and it was the culprit. It was requesting a new ad from AdMob even though the application was not in the foreground. It should have been paused and had no activity going on. In all the refactoring I had lost my onPause() method. After getting that back into my code things were normal again. Thank goodness it was for something as small as an ad!
That spurred me to start doing some reading on the Application Life Cycle. There's fabulous documentation on it provided by Google.
So is the Android Market really full of viruses? I can tell you that I've seen some really crappy code over the years. I can also tell you that I take special care (certainly more than most!) to understand my code and do extensive testing. There are no formal checks and balances and even with the best of intentions my code was consuming more data and thusly more battery power than it should have been. Whether it's full of viruses or not I know that it has 1 less as of several weeks ago and I will be much more conscience of how my phone performs after I install something new.
AYDABTU Development
Wednesday, May 11, 2011
Tuesday, May 3, 2011
What Goes Into Source Control, Android?
From the very beginning of my Android App development I knew I would be releasing it into the market. As such I wanted to keep it in source control. I'll explain my reasons for that in another post, but here I want to talk about what exactly goes into source control, what to do if you inherit a project that needs cleaned up and finally, how to coerce Eclipse into helping you long term.
I had never created a project outside of Eclipse's new Android Project Wizard so what was part of it's overhead versus what was needed for someone else to build this project on a difference machine? I asked a few guys around the office and Stack Overflow. As is always the case I got, "whatever isn't required to build the project doesn't go into source control." Thanks geniuses. At least one of them actually pointed me to the official documentation from which I could start to cherry pick the fluff out of my project. Here's the official documentation on what is required for your project: http://developer.android.com/resources/faq/commontasks.html#filelist
My development environment is Eclipse with the Subclipse SVN plugin and I'm using online repositories from both BeanStalk and Assembla. A quick scan of my repository and I was a little bummed to find several things in there that did not need to be:
I had never created a project outside of Eclipse's new Android Project Wizard so what was part of it's overhead versus what was needed for someone else to build this project on a difference machine? I asked a few guys around the office and Stack Overflow. As is always the case I got, "whatever isn't required to build the project doesn't go into source control." Thanks geniuses. At least one of them actually pointed me to the official documentation from which I could start to cherry pick the fluff out of my project. Here's the official documentation on what is required for your project: http://developer.android.com/resources/faq/commontasks.html#filelist
My development environment is Eclipse with the Subclipse SVN plugin and I'm using online repositories from both BeanStalk and Assembla. A quick scan of my repository and I was a little bummed to find several things in there that did not need to be:
- bin/
- gen/
- .classpath
- .project
- default.properties
- proguard.cfg
A friend and longtime developer asked me why I wouldn't want .classpath included? Our office has a mix of both Windows and Linux development platforms so I've tried to mimic that as often as possible when doing personal development so I'm familiar with the challenges faced by my colleagues. Everytime I would sync my local machine after the alternate environment had updated the repo I had .classpath issues. Some of this could be my own inexperience so if you know the way around this please share ;)
Back to the point ... I did some research and found that I needed a local SVN client in order to accomplish what I wanted to do. The command is:
svn rm --keep-local {filename/folder}
Execute that for each of the files or folders you wish to get out of source control, but keep your local copy. I had read on SO that you needed the trailing / after folder names, but I've found that to be inaccurate. Simply listing the folder "bin" works and actually adding the trailing / does not.
After running the svn command to remove (rm) each of the items I wanted out of source control I executed a commit:
svn commit -q -m "Removing unwated files/folders from source control" .
The next thing I did was set the filesystem level properties to keep them from accidentally ending up in Source Control in the future, but it is leading somewhere. You can execute the following command from your project folder to add the items to the ignore list:
svn pe svn:ignore .
Just add each item, 1 per line, as above again excluding the trailing / on folder names.
I had tried to do all these things in the SVN perspective from within Eclipse but it would not let me set file/folder properties. That's what lead me through the exercise outline above. My next goal was to start with a fresh project and get things into source in the easiest way possible. As I dug through the entire process I found that I can actually add the ignore property from within the Team Sync perspective! That's huge as it keeps me (and you) from dinking around at the command line unnecessarily. After you've "shared" your project go into the Team Sync perspective, right click the project and choose synchronize. In the left pane you will be presented with a list of files that will be placed under source control. You can right click them and choose to ignore them. From here I can control exactly what is and is not going to get into my repo. That was a big win and almost perfect, but curiosity had me wondering if I could go further.
Eclipse can help by default! If you look at the Team section in Eclipse Preferences you can add the items to the "Ignored Resources." I added each item and again created a new project. This time after sharing it and going to the Team Sync perspective I had only the items listed that were identified in the Android documentation. Obviously you can start by just doing this and save yourself all the hoop jumping that I went through.
Sunday, March 20, 2011
Splitting Text At A Fixed Length
As part of my message app I had originally been splitting my message text "by hand." I found a method in the SmsManager that does the job for you. Again, it's still a good bit of code so here it is. Hope you find it helpful.
This will split the message into 160 character chunks to include anything up to 160 characters at the end of the message.
This will split the message into 160 character chunks to include anything up to 160 characters at the end of the message.
protected ArrayList<String> splitMsg(SmsMessage smsMessage) { ArrayList<String> smt; Pattern p = Pattern.compile(".{1,160}"); Matcher regexMatcher = p.matcher(smsMessage.getMsgBody()); smt = new ArrayList<String>(); while (regexMatcher.find()) { smt.add(regexMatcher.group()); } return smt; }
Sending a Single SMS to Multiple Addressees
The net-net-net of this, if you're wondering is, it can't be done. Hear me out. I don't want to send 10 SMS messages to 1 addressee. I can do that just fine. I was trying to send a single message to 10 addressees in order to get 1,000 addressees the same message rather than Android's 100 addressees/messages. I learned a valuable lesson about the SDK documentation. When it says it accepts a "String" it doesn't mean you can trick it to accept a String [].
I spent a lot of time yesterday getting the code in place to provide "destinationAddress" as a comma (as well as space, semicolon, and new line) delimited list. No messages get sent unless your list contains only 1 item. *sigh*
But, I do like the code I came up with so I'm storing it here in the hopes that it might someday be useful in another context.
public void sendDataMessage (String destinationAddress, String scAddress, short destinationPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent)
I spent a lot of time yesterday getting the code in place to provide "destinationAddress" as a comma (as well as space, semicolon, and new line) delimited list. No messages get sent unless your list contains only 1 item. *sigh*
But, I do like the code I came up with so I'm storing it here in the hopes that it might someday be useful in another context.
protected void sendMsg(Context context, SmsMessage smsMessage) { SmsManager smsMgr = SmsManager.getDefault(); ArrayList<string> smsMessageText = smsMgr.divideMessage(smsMessage.getMsgBody()); PendingIntent sentPI = PendingIntent.getBroadcast(context, 0, new Intent("SMS_SENT"), 0); PendingIntent deliveredPI = PendingIntent.getBroadcast(context, 0, new Intent("SMS_DELIVERED"), 0); int AddresseesPerMessage = 10; StringBuilder builder = new StringBuilder(); String delim = ""; for (ContactItem c:smsMessage.getAddresseeList()) { // For every phone number in our list builder.append(delim).append(c.getPhoneNumber().toString()); delim=";"; if (((smsMessage.getAddresseeList().indexOf(c)+1) % AddresseesPerMessage) == 0 || smsMessage.getAddresseeList().indexOf(c)+1 == smsMessage.getAddresseeList().size()) { // using +1 because index 0 mod 9 == 0 for(String text : smsMessageText){ // Send 160 bytes of the total message until all parts are sent smsMgr.sendTextMessage(builder.toString(), null, text, sentPI, deliveredPI); } builder.setLength(0); delim=""; } } }
Sunday, March 6, 2011
Android: Working with Sqlite Cursors & Queries
If you're like me you learn to code by example. One of the things I was having trouble with was selecting data from sqlite. Not that I couldn't figure out how to get all the data, but how could I get just the data I needed.
The generic getContentResolver.query(Groups.CONTENT_URI, null, null, null, null); just seemed wasteful. Here's what I've gleaned from other posts around the internet and from the SDK documentation:
Further, if you want to just get a single row by it's _ID you can do this:
Hope this is helpful and saves you some grief ;)
The generic getContentResolver.query(Groups.CONTENT_URI, null, null, null, null); just seemed wasteful. Here's what I've gleaned from other posts around the internet and from the SDK documentation:
ContentResolver cr = getContentResolver(); Cursor groupCur = cr.query( Groups.CONTENT_URI, // what table/content new String [] {Groups._ID, Groups.NAME}, // what columns "Groups.NAME NOT LIKE + 'System Group:%'", // where clause(s) null, // ??? Groups.NAME + " ASC" // sort order );
Further, if you want to just get a single row by it's _ID you can do this:
ContentResolver cr = getContentResolver(); Uri myGroup = Uri.withAppendedPath(Groups.CONTENT_URI, "16"); Cursor groupCur = cr.query(myGroup, new String [] {Groups._ID, Groups.NAME}, null, null, null);
Hope this is helpful and saves you some grief ;)
Thursday, March 3, 2011
Android: Tracking Version Usage
I had wondered and I've heard it asked, "How do I know what versions of my Android application people are using?" The quick answer that you'll get is, "You don't." I found a way using Google Analytics to track a couple of things: 1) you can track the number of people upgrading and/or 2) you can track the specific version usage!
I created a pop-up dialog that announces the recent changes to the user after an upgrade. Inside that logic I put a trackEvent call to Google Analytics with a label of the Application Version and a value of 1 so each time that dialog is presented I get a +1 for that label. Here's how:
getAppVerName() is just a helper method that does the following:
On your application's main activity screen you can also add a trackEvent to help keep track of which versions are being executed like so:
Google Analytics will record 2 events for you: one called Upgrade and the other called HomeScreen. Both will have a label of the version making the call and a counter. Also of note; in the recent changes logic I also set the Accepted Usage Agreement to false allowing me to re-display the usage agreement on upgrade.
I created a pop-up dialog that announces the recent changes to the user after an upgrade. Inside that logic I put a trackEvent call to Google Analytics with a label of the Application Version and a value of 1 so each time that dialog is presented I get a +1 for that label. Here's how:
if (!appPrefs.getAppVer().equals(getAppVerName())) { ... tracker.trackEvent("Upgrade", "Click", getAppVerName(), 1); appPrefs.saveAppVer(getAppVerName()); appPrefs.saveAcceptedUsageAggrement(false); }
getAppVerName() is just a helper method that does the following:
public String getAppVerName() { String text; try { text = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; } catch (NameNotFoundException e) { text = "Version Not Found"; } return text; }
On your application's main activity screen you can also add a trackEvent to help keep track of which versions are being executed like so:
public class MyActivity extends Activity { GoogleAnalyticsTracker tracker; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tracker = GoogleAnalyticsTracker.getInstance(); tracker.start("UA-12345678-1", this); tracker.trackPageView("/HomeScreen"); tracker.trackEvent("HomeScreen", "Click", getAppVerName(), 1); tracker.dispatch(); setupViews(); } }
Google Analytics will record 2 events for you: one called Upgrade and the other called HomeScreen. Both will have a label of the version making the call and a counter. Also of note; in the recent changes logic I also set the Accepted Usage Agreement to false allowing me to re-display the usage agreement on upgrade.
Tuesday, March 1, 2011
My First Troll!
I, or my App rather, have/has arrived!
1-star
by rudo (March 1, 2011)
1-star
by rudo (March 1, 2011)
It sucks, does not work at all and freezes m phone
I figure if I'm not doing something right I wouldn't have hate mail. Internet trolls are an odd bunch. The Android Developer Console doesn't report any Force Close or Freeze issues so my best guess is a competitor not liking how well my app is doing :)
1,400+ downloads with ~400 active installs totaling more than 13,000 activity views since February 12. There hasn't been a reported error from the app or from the Android Developer Console in over a week (since version 2.2.3).
The best feature to date is very close. If you text groups of people frequently and are, or are not a direct marketer you're going to really like the next update.
Subscribe to:
Posts (Atom)