Friday, December 23, 2011

Comments on my Android App

My first android app (Pomodoro - originally was 'My Pomodoro' but android market search wasn't liking the 'my') has only 4 ratings. One nice person ranked it 5 stars, I am sure they were being nice. Two people ranked it at 3 stars, one of them added some comments and I actually even used some of their comments for future releases.
Today, I got my first one star rating. And here is the comment:

Decepção! Ruim de ordenar. Não deleta tarefas Não tem como marcar tempo das pausas. Sem aviso sonoro. Ruim de ordenar as tarefas. E não consegui deletar as tarefa da lista. Decepção!

My first guess was that this was Spanish, though I dont know why I thought that. I know some Spanish and its not Spanish. My next thought was maybe Italian and then I was pretty sure it was Portugese. Here is the translation I get from Babelfish:


Disillusionment! Bad to command. Not deleta tasks It does not have as to mark time of the pauses. Without sonorous acknowledgment. Bad to command the tasks. E I did not obtain to deletar the task of the list. Disillusionment!
Still hard to understand but it looks like they are unhappy that they can't delete tasks from the main screen. This is true. As my documentation (which I only wrote in English unfortunately for this user) states this but points out that one can delete a task once it is started. I think they are also state that I don't keep track of pauses. That is also true. Anyhow, it was fun to get a comment from someone presumably in Portugal or Brazil. Its a great thing that Google lets a lowly person release an app which can, in theory, be used by anyone in the world. I wish their experience had been better of course. Sadness!

Friday, October 7, 2011

Backup to USB Drive - Data Corruption

An important part of any developer's life is the BACKUP. Planning for the rainy day where our computer is lost, damaged or stolen. To that end, we take important time out of our days to backup our code, notes, executables to a safe medium.

So imagine my total disgust and desolation to find that the backups I have been saving to a 8Gbytes Cruzer USB flashdrive are CORRUPT!! I really could not believe it. Here are some symptoms:
1) Zip files copied to and back from the USB disk could not be opened.
2) But sometimes they could be opened but the contents were corrupt.
3) Copying folders themselves worked slightly better, but sometimes individual files were corrupt.
4) The corruptions tended to be 3-6 blocks of translocated data. But sometimes the entire file was fragmented with differences.

I can NOT even tell you the sick feeling in my stomach to do a difference between a 400Mbytes backup folder and the original folder only to find literally 100's of differences. I never saw this with Windows XP or Vista, but now I have Windows 7.

However, here is some good news: the issue did now seem to show up on  my smaller, older USB drives (e.g. 500 Mbytes).

Unfortunately, it was an issue on my Toshiba 1Tbyte external hard drive. I almost cried. I turned off Write caching on my hard drive, but that did not seem to help. Googling found other people with complaints but not the large outcry which I would expect. This makes me worry that there is something wrong with my system or maybe a virus. But I still have to check for perhaps a better USB driver.

This issue may be correlated with size. A 400Mbyte binary seemed to always be corrupted while I was able to transfer two 200Mbyte binaries without a problem. However, I first noticed the problem with zip files as small as 120Mbytes and even 2Mbytes.

Tuesday, September 6, 2011

Bitten by the Gingerbread Man

My phone just got updated to Android 2.3.3 (Gingerbread) and I noticed an issue with my apps. It serves me right for not testing under the 2.3 emulator. Anyhow, what I noticed is that control values (e.g. EditView text) are cleared when the phone is tilted and the orientation changes. I knew that some things were lost in this (for example, the item in a list which is selected), but in my testing and on my phone, simple controls like EditView kept their state.

No more. Also, I have to say that the emulator does not work so well under 2.3. Yuch.

Monday, August 29, 2011

Android Listview and OnItemClickListener GOTCHA


Android ListView and Custom View Gotchas


I first used the Android ListView to attach to some a list of objects and display them using the Android default/simple list view:
int layoutID = android.R.layout.simple_list_item_1;
ArrayList<Location> myList = fillLocationList();
ArrayAdapter<Location> aaBasic = new ArrayAdapter<Location>(this, layoutID, myList);

In order for it to show the string that I wanted, I had to override Location's toString, thats all fine. I also wanted to perform an action when the user selected an item in the list. This also worked well.
this.locationListView.setOnItemClickListener(new OnItemClickListener()
        {
			@Override
			public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
					long arg3)
			{
				onLocationSelected(locations.get(pos));
				
			}      	
        });  

Next I created my own ArrayAdapter and my own view. That view had multiple TextView's so that I could show more information in each list element. Inside of my ArrayAdapter, I load my custom view and set the text of each TextView to a property of my object. This also works fine and the user can still select an item and my listener gets called.
int layoutID = R.mylocationitem;
ArrayList<Location> myList = fillLocationList();
MyArrayAdapter aa = new MyArrayAdapter(this, layoutID, myList);
I'm not showing the details of MyArrayAdapter here. But it is the standard example that you see. It loads the layout and accesses the widgets inside the inflated view and sets the widget properties to match those of my object (Location). So now instead of a simple string list, I have a more complicated list.

Next, I decide that I want my custom view to contain both a TextView and a RatingBar. I want the user to see a name of a location as well as its "rating". The ratingbar is readonly so I have to disable it. (Since then, I found out that RatingBar has a property named 'isIndicator' and when this is true, the rating bar is readonly. This is better than disabling it but does not change the issue here.)

Problem 1 - my listener stops working as soon as I add the RatingBar.


I know that what is happening is that my RatingBar is forcing the view to take focus so that the user can interact with the rating bar. Thus the custom view sees the user click rather than the list adapter. But I am not able to "tip off" android that I dont want this to happen. I see that some android templates specify that a list item control should not be "Clickable" but this does not work for me. Neither does the disabling of the rating bar. I tried things like this:
view.setFocusable(false);
//			ratingBar.setFocusable(false);
//			ratingBar.setFocusableInTouchMode(false);
//			ratingBar.setClickable(false);

But none of that works. Next I give up on getting OnItemClick to work and decide to handle the click event in my item view element itself. Naively, I first try to use aOnClickListener.
			ratingBar.setOnClickListener(new OnClickListener()
			{
This was naive, reading the android documentation more and I realize it is the processing of user-touch events which lead to a click event. The user has to tap down and then up to cause a click. But the widgets are handling the touch events. So I need to handle touch events instead.
			OnTouchListener touchListener =		new OnTouchListener()
			{
				@Override
				public boolean onTouch(View arg0, MotionEvent arg1)
				{
					HomeActivity ha = (HomeActivity) getContext();
                                        ha.OnItemSelected(position);
					return true; //If I dont return true, I stop getting Touch events
				}
			
			};
                       newView.setOnTouchListener(touchListener);
This is starting to look good. Note that my listener is now on the entire view rather than any of the individual widgets. Ideally, I'd like to raise the OnItemSelected event myself but I dont know how to do that, so I just access the parent activity directly and make a call. Okay, dont yell at me yet, I know this isnt right but lets make some comments:
  • position is a final member of the adapter. I have to make it final so that I can access it in the anonymous listener class.
  • I added a method named "OnItemSelected" to my calling activity. This takes place of the OnItemSelected listener which I could not get to work.
  • NOTE: In testing, I found that if I returned false from onTouch, I would no longer get any user touch events. So I return true.
  • Okay, this code basically accomplishes the goal: now when the user taps an item in my list, the above listener gets called and my activity gets notified and it goes ahead and works correctly. This happens when the user taps an item. 'BUT' it also happens if the user drags his/her finger (e.g. when scrolling the list). OOPS!

Problem 2 - now my item select code works, but the user cannot scroll

Ooops, it seems I am not paying attention to the MotionEvent argument. Here are the applicable touch events:
  1. MotionEvent.ACTION_DOWN - the user has pressed down on the screen.
  2. MotionEvent.ACTION_UP - the user has taken their finger off the screen.
  3. MotionEvent.ACTION_MOVE - the user is dragging
  4. MotionEvent.ACTION_CANCEL - in my experience, I see CANCEL once the user drags outside of the window. At the part, the list starts to scroll and I get anACTION_CANCEL. And I get no further messages after this.
  5. MotionEvent.ACTION_OUTSIDE - I did not see this in my testing.

So, I change my logic to look for both a ACTION_DOWN and a ACTION_UP before I perform the OnItemSelected action. In testing, I found that no matter how crisply I 'tapped', I always got some intermediate ACTION_MOVE events. So I used logic to allow at most 8 of these. If the users does any more than that, then I assume they are just screwing around. HA.

Okay, so now things work well. And I'm looking for a nice way to centralize this code so that it easy for me to reuse this code. But in testing, I found one more problem.

Problem 3 - unexpected list events. I would see that often when I closed the activity which held the list, my OnItemSelect logic was being called. In my app, that meant that a new activity was launched. Spooky. I caught this in the debugger and it seemed that indeed list custom view events were being fired from a routine named "Die" (I think it is Activity.Die - no kidding). Strange, but really after my activity is no longer active, I should not listen to these events any more and this was an easy fix:

  1. In Activity.OnPause - I deactivate my event notifications (alternatively you can let them fire and just ignore them)
  2. In Activity.OnResume - I reactivate/activate my event notifications.

Wednesday, August 24, 2011

Android Development Gotchas

Android Gotchas

Here are some Android development gotchas which I have found


ResumeException

I got this because I overrided onResume but forgot to call super.onResume() DUHH
@Override
protected void onResume() {
super.onResume(); //this was missing
}

Strange exception when starting app.

I added a new activity and found my entire app would not start! And I wasn't even using that app! Things to check
  1. Be sure that your resource ids are not duplicated. This causes an error when the app starts, even though it might not yet use the resource.
    1. I have started every resource id with the activity name (e.g. id=myactivity_btnOk)
  2. Look for other errors in your resource layout files. If you just made a change, back out the change.

Error loading my new activity

  1. Oops, the activity was not declared in the manifest
  2. Or oops, it was declared but I did not use the correct namespace

I can't get a non-null location manager

  1. If you do not have the correct privileges in your manifest, there is no error but no manager comes back.

Some activity launches when I navigate away from a screen.

  1. Check your event handlers to see if they are causing it. I am trying to deregister event handlers when my activity pauses.

My app raises an exception but I am having trouble seeing why.

  1. Yuch, I dont love the exception reporting. Try running LOGCAT from ABD. You get much more details.
ABD LOGCAT

LOGCAT is clipping my text.

  1. Run it from ABD, then the text all wraps

I put data into an intent, but its different when I pull it out!! The reason: makek sure that you know the type of data that you are storing and use the correct get routine to retrieve it. In my case, I was storing a long but using getIntExtra instead of getLongExtra. Duh.

Intent data = new Intent();
      data.putExtra("NumberOfCats", numberOfCats);
//
//
     int numStored = data.getIntExtra("NumberOfCats"); //gets wrong value because numberOfCats is a long and not an int!

I made a custom list adapter with EditText and such but now it doesn't select or scroll.

Yuch, I am going to blog on this. If the view inside the list can accept clicks/touches, then it screws up the OnItemSelected listener. So you are better off to use things likeTextViews. But I got this to work - see upcoming blog entry.

Monday, August 8, 2011

Fine Tuning the App

Well, I am now on version 3. Version 2 came maybe four days after the initial release and Version 3 came almost right away. Here are some comments:


  1. I love testing/debugging directly on my phone rather than using the emulator. BUT I have to remember that there are big differences between a plugged in phone and a disconnected phone. When I debug on my phone, the cable is attached to my USB port and the phone is charging. So it does not dim or go to sleep and so I was missing those test cases. The end result is that when I finally tested release 2 on my disconnected phone, it did not work as well.
  2. Wake Locks - Wake locks can be used to keep the display from dimming or the CPU from going to sleep. I was loathe to use them but it is just to inconvenient as a user to have to keep turning on the phone to see how much time is left or to look at my task list. Some notes:
    1. I chose to let the phone dim slightly instead of stay completely bright.
    2. Wake lock is per activity, so I had to remember to use it on both my home screen and my "running" screen.
    3. remember to release the wake lock when the activity is paused. I made sure that once the user left my activity, that the wake lock stopped working and the phone would dim/sleep while in other tasks.
  3. Intents - when a task is finished, i add a notification to the top of the screen. I thought it would be cool to let them see that a task had finished (even though it should be obvious) and then start the app from there. Good news is that it worked, the bad news is that it always started a new instance of my application. I didn't want this, i wanted it to join the running app. This is something I just have to get used to with Android: it controls when an app starts and I just can't expect that the app will still be running. The best I could do was change the activity launch mode to "singleTop" so that Android would try to use the current instance if it is at the top of the task list. I am sure I don't yet understand this correctly but it helped.

Wednesday, August 3, 2011

Can't find my published app on Android Market

I published my first app under the name "My Pomodoro Time Management". I wanted the name to be just "My Pomodoro" (which is the app name) but I was concerned people would not know what Pomodoro is.

Anyhow, so I wait a while and then I go to Android Market and search for "Pomodoro". My app doesn't show up. But I get 21 other hits including an app named MyPomodoro. I then search for "My Pomodoro" and I find nothing. I search for "Time Management" and I find my app!!!

Yuch. I was really disheartened that I finally had my app out there but no one would be able to find it! Why can't it find "My Pomodoro"?? Anyhow, I changed the app name to "Pomodoro" and waited a while. Later that day, a search for "My Pomodoro" found my app under the new name!! So I waited another day and now I can search for "Pomodoro" and there is my app.

So one thing I learned is that I have to be a little patient. Wait at least a day. But I still dont think the search was working that well. It should have found my app right away when I searched for "Pomodoro" since it could find it under "Time Management". Lesson is, don't take the search for granted!