Sunday, December 30, 2012

Android: Database corruption issue

I recently ran into an issue where all the rows of all the tables in a user's database were deleted. For this app, there is some security code that would do this, but it would also delete temp files and backup data files. No exceptions were thrown and no app crashes were reported.Those files were still there which indicated that the service for wiping the db didn't run, but I wasn't able to duplicate it and there were no other instances of this happening.

The other day I was running some backend tests and after the test run, went to restore the previous version of the DB to rerun the test and got this error : android.database.sqlite.SQLiteDatabaseCorruptException



12-29 10:22:39.190: D/(14156): Here
12-29 10:22:39.200: I/SqliteDatabaseCpp(14156): sqlite returned: error code = 11, msg = database corruption at line 46978 of [8609a15dfa], db=/data/data/com.myapp.android/databases/db_file
12-29 10:22:39.200: I/SqliteDatabaseCpp(14156): sqlite returned: error code = 11, msg = database corruption at line 46978 of [8609a15dfa], db=/data/data/com.myapp.android/databases/db_file

/data/data/com.myapp.android/databases/db_file
12-29 10:22:39.200: E/DefaultDatabaseErrorHandler(14156): Corruption reported by sqlite on database: /data/data/com.myapp.android/databases/db_file
12-29 10:22:39.200: E/DefaultDatabaseErrorHandler(14156): deleting the database file: /data/data/com.myapp.android/databases/db_file

12-29 10:22:39.220: E/SQLiteDatabase(14156): android.database.sqlite.SQLiteDatabaseCorruptException: error code 11: database disk image is malformed
12-29 10:22:39.220: E/SQLiteDatabase(14156): at android.database.sqlite.SQLiteStatement.native_executeInsert(Native Method)
12-29 10:22:39.220: E/SQLiteDatabase(14156): at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:112)
12-29 10:22:39.220: E/SQLiteDatabase(14156): at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1737)
12-29 10:22:39.220: E/SQLiteDatabase(14156): at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1610)
12-29 10:22:39.220: E/SQLiteDatabase(14156): at com.myapp.android.database.DBHelper.createRecord(DBHelper.java:80)
12-29 10:22:39.220: E/SQLiteDatabase(14156): at com.myapp.android.contentproviders.SynchLogProvider.insert(SynchLogProvider.java:117)
12-29 10:22:39.220: E/SQLiteDatabase(14156): at android.content.ContentProvider$Transport.insert(ContentProvider.java:203)
12-29 10:22:39.220: E/SQLiteDatabase(14156): at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:153)
12-29 10:22:39.220: E/SQLiteDatabase(14156): at android.os.Binder.execTransact(Binder.java:339)
12-29 10:22:39.220: E/SQLiteDatabase(14156): at dalvik.system.NativeStart.run(Native Method)
12-29 10:22:39.220: E/DatabaseUtils(14156): Writing exception to parcel

The database was completely wiped. Turns out that if a Sqlite database is corrupted, Android automatically deletes the database file and recreates it. This is a known Android bug/feature.

I didn't get this error until I tried to restore the db backup file and Android even thought that a desktop backup of the file was corrupted (although I was able to open it up in a third party tool on the desktop.) Likely the cause was the backup (simply a file copy) occurred while a service was updating the db.  We use the backup all the time and this was the first time that it had occurred. (The user issue was caused by something else corrupting the db file not the backup)

http://code.google.com/p/android/issues/detail?id=10127
http://stackoverflow.com/questions/2960015/android-database-disk-image-is-malformed
http://www.sqlite.org/lockingv3.html#how_to_corrupt

Solution appears to be to have a backup and restore routine so that data can be recovered if necessary.

Tuesday, December 4, 2012

Android Content Provider Limiting rows returned

To limit the number of rows returned by a content provider, try this code
Product obj = new Product();

        Cursor cursor = getContentResolver().query(
                ProviderHelper.determineURI(obj), null, null, null, " _ID DESC LIMIT 5000");

This will return the last 5000 rows in descending order.Limit needs to be last and don't forget the column name you want to descend.

Saturday, December 1, 2012

Android Cursor code example

Processing cursors is a very common task that developers do when creating android apps. Here's some sample loops for processing cursors. Pay attention to log files to make sure that there are no null pointer exceptions when developing your cursor processing.


Cursor cur = dB.rawQuery("SELECT firstname, salary FROM demos " +
           "where salary>100,000 LIMIT 10", null);

if (cur != null ) {
    if  (cur.moveToFirst()) {
        do {
            String firstName = cur.getString(cur.getColumnIndex("firstname"));
            int salary = cur.getInt(cur.getColumnIndex("salary"));
            results.add("" + firstName + ",salary: " + salary);
        } while (cur.moveToNext());
    }
}
cur.close();

Thursday, November 1, 2012

Organizing Resource Files

For very complex projects, I like to try to group the layout files together so that activity files, fragment 
files, adapter files and include files are all together. Developers working on an activity or fragment can immediately find the file that they need to change.  I do this by placing a key at the front of a file. Doesn't matter what the key is just as long as it makes sense. I typically make the activity files "views" and prepend a vi_ to their file names.Why? Eclipse will display the files alphabetically and since they files are not modified frequently, I want them at the bottom of the list and out of the way. Same goes for adapter template files.  

Here's what I use:

1, Container/Activity Files
Name container files the same as activity where possible. In this case I’ve named them vi_{ACTIVITYNAME}_container_portrait.xml and vi_{ACTIVITYNAME}_container.xml (for horizontal mode) I named them vi_* so that they end up towards the bottom of the directory since we won’t be touching them much and they’re more out of the way then.

2.       Fragments
In the example above, I named the fragment xmls, {activityname}.xml but we might want to call them fr_{ACTIVITYNAME}_top.xml, fr_{ACTIVITYNAME}_bottom.xml etc

3.       Include files for things like common headers I call inc_{FILEPURPOSE}.xml for example inc_topnav.xml

4.       Adapter list item files (I don’t see any in the screen shot) I would call li_{purpose}.xml  Purpose is generally related to the adapter name. For example, an adapter that shows a list of products would be called ProductsAdapter.java  with the list item being li_productsadapter.xml

Wednesday, August 15, 2012

Android Market Share now a dominating 68%

IDC reports that Android market share of the smart phone market has increased to 68%. Nearly 105 million Android phones shipped in last quarter, twice as many as a year ago. Apple also grew their shipments to 26 million.  Take a stab at Apple's market share.Where do you think it is? Way down at 17% of the global market.  Open platform is certainly the way to go.  Apple should have learned that way back in 1980s when it lost it's enormous advantage in personal computers to IBM compatible.

Sunday, August 12, 2012

Android: Business models Some Analysis of Downloads

A potential client contacted me the other day to talk about an idea that they have. It's a great idea and wanted to bounce around some business model ideas.  The question that they had was the following:

So, we are trying to figure out for Super App Idea, the kind of download traffic we might get if we were free vs $1 vs $2?  Do you have a feel for that?  I am sure it might be a little different on iPhone as opposed to Android as well.  Do you have any sense of conversion rates  as well?  

I did a quick look for a few apps that had a free version and a paid version. I decided to use a pill tracking system since that was an app I downloaded to test the other day.  The first app was a free pill box app (https://play.google.com/store/apps/details?id=com.mobilepills.pillbox&hl=en) that received between 10,000 and 50,000 downloads.  The first comment was put in on September 2010 so it's been around about 2 years. We'll assume that the first comment was put in right around launch date. So conservatively it is generating about 5000 downloads a year.  Note these downloads don't include downloads from third party distributors like Amazon. At a high end, they've received 25,000 downloads per year

The second one I looked at was a pay to use app that has similar functionality.   This app (https://play.google.com/store/apps/details?id=com.sartuga.android.pillboxalert) received between 1000 and 5000 downloads since their first comment on November 8, 2009. They are selling their app at $1.99. So taking the conservative 1000 downloads, that means that they've received about 300 downloads a year or revenue of about $600 per year. At a high end, they've received 1600 downloads per year or revenue of about $3200 per year.

Based on this example, free apps will get about ten times the downloads as paid apps.

I'm going to create a few more blog entries in the near future taking a look at a couple of ways to potentially increase the downloads if you have a paid application.

Monday, August 6, 2012

Determining where your Android APK is installed

A little bit of code can tell you where your apk files are being installed. Typically it will be /data/app/package.name.apk



07-25 09:15:59.065: D/Main(3000): Source directory at: /data/app/com.appulearn.supersecretapp.android-1.apk

This bit of code will tell you....


  PackageManager  pmMgr = getPackageManager();
        List pkgList = pmMgr.getInstalledPackages(PackageManager.GET_ACTIVITIES);
        List appinfo_list = pmMgr.getInstalledApplications(0);
        for (int x=0; x < pkgList.size(); x++){    
          Log.d(TAG, "Source directory at: " +appinfo_list.get(x).sourceDir);
        }

Friday, August 3, 2012

Designing Android Databases

Most of my app use a sqlite database and I use data scripts to create, alter and bootstrap tables. ADB comes with the ability to look at the database on a USB connected device, but I often test the scripts in an opensource tool called SqliteBrowser.  Unfortunately it's a little bit unstable, and seems like development on it has stopped but it's great for testing sql statements that you want to run within an Android app/

http://sqlitebrowser.sourceforge.net/

Tuesday, July 31, 2012

Android: Disabling or Blocking the back button

One of the tablet apps I've been working on is a massive data collection app. It is set up to run in vertical mode and it's easy for the user to accidentally push the back button and exit a data collection form potentially losing their work.  Luckily it is also easy to disable the back button by preventing the click action or putting a dialog up when it is touched to confirm the exit of the screen.

Here you go. Nothing to it.

@Override
public void onBackPressed() {
}

Thursday, July 26, 2012

Android logcat disappears with multiple devices attached

I was recently working on an Android project testing on multiple devices attached via USB and rotating the testing between them. After a while, all trace stopped being displayed and wouldn't come back. I disconnected devices, reset the debugging perspective, restarted Eclipse, restarted the laptop but nothing worked.  I typically have more than one Android device plugged in and in the past two years have never had any problems.  The solution was to go into DBMS perspective and select the specific device in the upper left hand panel. For whatever reason, it wasn't automatically switching based on what was running.

Monday, July 23, 2012

Data type, Android and Sqllite



When working with Android content providers and cursors, pay close attention to the datatypes you are using to pull the data from the database. If you set the data type of the sqlite field to NUMERIC and are using it to store LONG values, be sure to pull it from the database as a long and not a int. Pulling it as an Integer will lead to truncation of the long value. So for example,
Since CreateDate is a long, I need to pull it like this
data.setCreateDate(c.getLong(c.getColumnIndexOrThrow(DatabaseConstants.F_CREATEDATE)));  

and not like this

data.setCreateDate(c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.F_CREATEDATE)));  

Here's the complete cursor processing.


  while (!c.isAfterLast()) { 

      MyData data = new MyData(); 

      data.setFirstName(c.getString(c.getColumnIndexOrThrow(DatabaseConstants.F_FIRSTNAME))); 

      data.setLastName(c.getString(c.getColumnIndexOrThrow(DatabaseConstants.F_LASTNAME))); 

      data.set_id(c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.F__ID))); 

      data.setCreateDate(c.getLong(c.getColumnIndexOrThrow(DatabaseConstants.F_CREATEDATE))); 

      data.setIsdeleted(c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.F_ISDELETED))); 

      data.setLastUpdate(c.getLong(c.getColumnIndexOrThrow(DatabaseConstants.F_LASTUPDATE))); 

      list.add(data); 

      c.moveToNext(); 

 }  


Thursday, July 19, 2012

More Android code standards

When mapping edit text fields, select boxes and other elements that have GUI interaction with the user  within Android Activity Java code, the first thing you need to know is the resource id where the data will be coming from. Flipping back and forth between the xml resources and the Activities is a pain. Be sure to put a comment in your code next to the variable that indicates the xml resource id like below. This easy piece of documentation and code commenting is invaluable to the developers who will be maintaining your code


 private EditTextView mPersonId;   //   R.id.PersonIdETV
 private Spinner mPersonJob;     //   R.id.PersonJobSPN

Friday, July 13, 2012

Randomly generating a date before or after today



I was recently building a calendar type app for a client and needed to test out the placement of items on the calendar. Unfortunately their test data returned all items with the exact same date. So I wrote a little bit of code that would randomly replace the date in the data with AM PM (the calendar data didn't have a time just whether or not it was in the morning or afternoon and randomly assign a date within a week both before and after today.



 private String generateRandomAmOrPm() {  
         final Random myRandom = new Random();  
         boolean amOrPm=        myRandom.nextBoolean();  
         if (amOrPm) {  
              return "AM";  
         } else {  
              return "PM";  
         }  
      }  
      private int generateRandomDate() {  
            final Random myRandom = new Random();  
              boolean plusOrMinus=        myRandom.nextBoolean();  
              int dateToMove = myRandom.nextInt(6); // plus or minus 6 days  
              if (plusOrMinus) {  
                   return 0-dateToMove;  
              } else {  
                   return dateToMove;  
              }  
      }  

Tuesday, July 10, 2012

Android HTTP Clients

Making a backend http call to retrieve or send data is a very common Android task.  There are a couple of different ways to do it from Apache HTTP Client to HttpURLConnection. The blog article below summarizes the advantages and disadvantages of both and is a must read.

http://android-developers.blogspot.com/2011/09/androids-http-clients.html

Tuesday, July 3, 2012

Launching settings from a button

I was recently working on an Android tablet project that needs to detect connectivity to a network. If the user wasn't connected to the Internet, I would throw up a dialog box telling them to connect. I decided to take this one step further and take them to the setting screen that would allow them to connect to wifi.

So to do this, I needed to send an intent that would launch settings. Easy enough.The Android snippet below does that.


Intent intent = new Intent(Intent.ACTION_MAIN);
 intent.setClassName("com.android.settings", "com.android.settings.Settings");
startActivity(intent);


Now that's ok, but it takes the user to settings main screen. Which is nice but a better way to do that is to send them directly to the wireless settings screen.  This can be done using the code below.


Intent intent =new Intent(Settings.ACTION_WIRELESS_SETTINGS);
startActivity(intent);



Sunday, July 1, 2012

Naming Conventions for Views

I recently worked a project as a lead developer working with several other developers on a very complex tablet app that was collecting medical history information. So lots of complex forms, widgets, and layouts.  One of the things that they worked on is setting up the xml layout files which is the first step in the Android development process. The screens looked great but we didn't have consistency on the naming of the individual layouts. For most simple apps, this isn't a big deal. But some of these forms had a dozen checkboxes, plus a dozen radiogroups plus a dozen spinners plus the text views, linear layouts and edit texts.

When I went to do the mapping to our json, I couldn't look at an R.id.field and know exactly what it it was for. For example, we had R.id.rash_field1 but that didn't tell me that it was an edit box that goes with SKIN-->Rash-->Color-->OtherValue

I would have name the resource R.id.skin_rash_color_othervalueET

This tells me a lot about what is going to be entered into the field and what the field purpose is as well as where in the data model I'm likely to be binding it to.

I try to use the following abbreviations for naming fields. This isn't an exhaustive list but will get you thinking on how you want to name the fields.

EditText: R.id.fieldnameET
TextView: R.id.fieldnameTV
Spinner: R.id.fieldnameSpn
Linearlayout: R.id.fieldnameLL
Checkbox: R.id.fieldnameCB
RadioGroup: R.id.fieldnameRG
RadioButton: R.id.fieldnameRB

Friday, June 29, 2012

Automated Testing on Android

For my most recent project, a very large tablet app, I've been experimenting with the Android Testing Framework.  My piece of the app is the backend integration consisting of a number of different API calls that send and receive JSON, handle login and more. The app will utilize content providers to enable other apps to access the database.  I'm specifically using the test framework to exercise these content providers as well as the services that integrate with the backend.

While there is some overhead with learning the test framework. it's very useful. There's a few gotchas that slow down setting up your tests. A good one is why doesn't my test run?  Test methods need to start with the word test. So testMyInsertCode() will run while doMyInsertCodeTest() won't.  Content provider testing has some specific requirements to set up the mock statements as well.  Be sure when testing queries that you don't forget to put the =? on the end of the where clauses....

Monday, June 25, 2012

Android Service not found error

I was recently helping a developer debug some services when he ran into an error saying that the service was not found. You get this error if your manifest doesn't have the service registered. Be sure to check for typos in the service tag.


06-19 13:26:32.543: W/ActivityManager(306): Unable to start service Intent { act=com.yourapp.special.android.services.BigService }: not found


< service
            android:name=" com.yourapp.special.android.services.BigService "
            android:exported="true" >
           
               
           
       

Friday, June 22, 2012

Content Provider and Count queries


I'm creating a number of custom content providers for a client and on one of the screens we want to show the count of some of the tables.
  Now in sql world that would be easy, simply run a query that is

  select count(_id) from cartable

  But in content provider world, it's a bit harder. After stumbling around a bit playing with provider.call methods,
  Now just like you can tell the content provider to give you an instance of a car by id like this:

  content://find.me.a.car/car/123


  You can define a new URI format and use that to have the content provider's query method do something other than return a cursor that contains a
  list or a single row.  You can have it return a cursor that is a count.

  Here's our test method. Since it's a test, we're inserting a record first then getting the count of the table.  The URI would look something like

  content://find.me.a.car/car/COUNT


 
  public void testCountCall(){ 

         Log.d(TAG, "--------------testInsert()-------------"); 

     ContentProvider provider = getProvider(); 

     Log.d(TAG, "Running insert of new Car record"); 

     Car obj = createObject(); 

         mUriRInsert=provider.insert(ProviderHelper.determineURI(obj), obj.createContentValues()); 

        Log.d(TAG, "Content inserted at: " + mUriRInsert); 

        Log.d(TAG, "------- testinsert will complete successfully if the returned uri is not null"); 

     Uri uriCount = Uri.withAppendedPath(ProviderConstants.CAR_CONTENT_URI, "COUNT"); 

     Cursor c = provider.query( uriCount, null, null, null, null); 

     c.moveToFirst(); 

     int count = c.getInt(0); 

      Log.d(TAG, "new Count:" + count); 

      assertEquals(1, count);      

   }  

 
    --- NEXT STEP: Define a pattern for the COUNT in your provider
 
    The first two are standard, the third pattern will match the count.
 
 
 
  static 

        { 

             sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 

             sUriMatcher.addURI(ProviderConstants.CAR_AUTHORITY, ProviderConstants.CAR_PATH, 

                 ProviderConstants.CP_TYPE_LIST); 

             sUriMatcher.addURI(ProviderConstants.CAR_AUTHORITY, ProviderConstants.CAR_PATH+"/#", 

                 ProviderConstants.CP_TYPE_ITEM); 

             sUriMatcher.addURI(ProviderConstants.CAR_AUTHORITY, ProviderConstants.CAR_PATH+"/COUNT", 

                 ProviderConstants.CP_TYPE_COUNT); 

      }  


----------- Third Step: Modify the query() method of the provider so it knows what to do with a URI that matches the COUNT


 @Override 

      public Cursor query(Uri uri, String[] projection, String selection, 

          String[] selectionArgs, String sortOrder) 

      { 

           Log.d(TAG, "uri:" +uri); 

           Log.d(TAG, "where:" +selection); 

           SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); 

           switch (sUriMatcher.match(uri)) 

           { 

           case ProviderConstants.CP_TYPE_LIST: 

                builder.setTables(TABLENAME); 

                builder.setProjectionMap(sDataProjectionMap); 

                break; 

           case ProviderConstants.CP_TYPE_ITEM: 

                builder.setTables(TABLENAME); 

                builder.setProjectionMap(sDataProjectionMap); 

                builder.appendWhere(DatabaseConstants.F__ID + " = " 

                    + uri.getPathSegments().get(1)); 

                break; 

           case ProviderConstants.CP_TYPE_COUNT: 

                builder.setTables(TABLENAME); 

                HashMap<String, String> countMap = new HashMap<String, String>(); 

                countMap.put("count", "count(*)"); 

                builder.setProjectionMap(countMap); 

                break; 

           default: 

                throw new IllegalArgumentException("Unknown URI: " + uri); 

           } 

           Cursor queryCursor = mDBHelper.runQueryBuilder(builder, projection, 

               selection, selectionArgs, null, null, null); 

           queryCursor.setNotificationUri(getContext().getContentResolver(), uri); 

           return queryCursor; 

      }  



Pretty straightforward and very handy.

Sunday, June 17, 2012

Android- Link to Google Store for Ranking


User reviews of Android Apps and rankings are the key metric by which Android apps are measured. I've added buttons to many of my apps to make it easy for users to create a review for my app from the app.  All you need is the url to the app and the code below.

// the package name as registered in the manifest.
public static final String URL_LINK_DEST = "http://market.android.com/search?q=pname:com.yourapp.appname.android";


 Button btnRate= (Button) findViewById(R.id.btnRateThisApp);
       btnRate.setVisibility(View.VISIBLE);
       btnRate.setClickable(false);
       btnRate.setOnClickListener(new View.OnClickListener(){

public void onClick(View v) {
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(GlobalConstants.URL_LINK_DEST));
    startActivity(i);  
}

});

Wednesday, June 13, 2012

Sending an SMS


Sending an SMS from your app is actually quite simple.  Note that once you add sms capabilities to your app, devices (like tablets) that don't have SMS can't install the app.



 String message= "Watson come here"; 

 String watsonsCell= "1234567890"; 

 SmsManager sms = SmsManager.getDefault();  

  if (message!=null) { 

   sms.sendTextMessage(watsonsCell, null, message, null, null); 

 }  



You need to add a permission to the AndroidManifest.



 <uses-permission android:name="android.permission.SEND_SMS"/>  



If you want to process an sms, you need the following permission, but it isn't necessary for sending.



 <uses-permission android:name="android.permission.RECEIVE_SMS"/>  


Sunday, June 10, 2012

Android Bug Report Database

A very useful resource is the official Android bug report database located at http://source.android.com/source/report-bugs.  This page allows developers to report bugs and search for known issues.  This is particularly useful for debugging problems reported for specific devices or for different API levels.  For example, I've used this to determine problems with JSON processing failure on a particular HTC device or problems debugging camera integration on Samsung tables and  differences between GMAIL support on api levels.  So check it out and book mark the page. Sooner or later it will come in handy!

Saturday, June 9, 2012

Starting Services on Boot


Most of the projects I work on have lots of backend integrations,some of which update data on a regular basis.  So I set up these integrations within a service that gets started when the device is turned on.  Pretty easy to do. I set up an array in the arrays.xml that has the full path to the service I want to start (packagename+servicename") so for example com.me.myproject.android.services.mygreatservice

The boot service gets the Broadcast message that Android sends out on startup, iterates through array of services and starts them one by one.

You'll need to add the permission below and register the receiver class in the manifest.



  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 <receiver android:name=".services.BootReceiver" >
       <intent-filter >
         <action android:name="android.intent.action.BOOT_COMPLETED" />
       </intent-filter>
     </receiver>      



 public class BootReceiver extends BroadcastReceiver { 
      private static final String TAG ="BootReceiver";
      /**
       * Service intent
       */
      public static Intent mIntent;
      @Override
      public void onReceive(Context context, Intent intent) {
           String[] servs= context.getResources().getStringArray(R.array.services);
            for (int i=0; i<servs.length; i++) {
                 Log.d(TAG, "Starting: "+servs[i]);
                     Intent mIntent = new Intent();                                
                     mIntent.setAction(servs[i]);
                     context.startService(mIntent);
            }
      }
 }  

Wednesday, June 6, 2012

Illegal Access Error and Referenced Projects

I've been writing a number of test classes for a very complex Android app. Recently I ran into a problem with a third party jar file misbehaving and throwing a java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation.  This is due to a class path problem. The solution is to reference like you normally would the jar in the source app and BE SURE TO EXPORT IT.(last tab in Eclipse properties-->Classpath


Then in the test app you MUST REFERENCE the project. Thanks to this blog for the excellent details on what to do!

http://blog.js-development.com/2010/06/android-instrumentation-test.html

Sunday, June 3, 2012

Deleting files from the SDCard

As part of a recent project, I enabled users to export data from the database to the file system by clicking a button.  Users then wanted an easy way to delete the files that they created.   This little bit of code below does just that. It looks in the standard Android download directory (where I'm saving the files) for all the files that end in testfile.txt (I'm naming files {_id}_{date}_testfile.txt) and then deletes them one by one..



 Button clearJsonFiles = (Button) findViewById(R.id.clearJsonFiles); 
           clearJsonFiles.setOnClickListener(new OnClickListener()
           {
                public void onClick(View v)
                {
                     File path = Environment
                         .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
                     File[] files = path.listFiles();
                     for (int i = 0; i < files.length; i++)
                     {
                          try {
                               Log.d(TAG, "Found File:" + files[i].getCanonicalFile());
                               if (files[i].getCanonicalPath().contains("testfile.txt")) {
                                    Log.d(TAG, "Deleting File:" + files[i].getCanonicalFile());
                                    //deleteExternalStoragePublicFile(files[i].getCanonicalPath());
                                    files[i].delete();
                               }
                          } catch (IOException e) {
                               // TODO Auto-generated catch block
                               e.printStackTrace();
                          }
                     }
                }
           });  

Tuesday, May 29, 2012

Android Coding Standards

One of the challenges facing any large development effort is ensuring that all developers follow the same coding standards. Maintaining coding standards, which is "a set or rules or guidelines for formatting code", has numerous benefits including helping lower the cost of code maintenance, enabling new developers to learn the code base faster and overall lower cost of ownership. This link talks about the benefits of coding standards in a very concise and easy to understand way.

http://www.valid-computing.com/benefits-of-coding-standards.html

The Android development team has its own code standards that they use for the Android source. It's published here.  http://source.android.com/source/code-style.html  I try to follow these standards the best that I can, although individual projects may have their own set of standards that should be followed.  The important thing is to have standards and to make sure that everyone uses them.

Thursday, May 24, 2012

Getting the Android id from a URI - Content Provider

I've been working quite a bit with Android content providers on a recent project and one thing that keeps coming up is how to determine the android id (_id) of a record that has just been created via a content provider. The insert statement returns the uri pointing to the row that has been created and this URI ends with the key to the object. In other words the URI can tell you the id, all you have to do is parse it.

 An easy way of doing that parsing is shown below.


Uri uri=getContentResolver().insert(ProviderHelper.determineURI(myCarObj), myCarObj.createContentValues());
Log
String carId = uri.getPathSegments().get(1);

Log.d(TAG, "Content inserted at: " + uri);
Log.d(TAG, "Car_id: " + carId);




05-24 13:49:41.290: D/CarProviderTest(11228): Content inserted at: content://com.supercar.edc.android.contentproviders.Car/car/123


05-24 13:49:41.290: D/CarProviderTest(11228): Car_id: 123)



123 is the Android Id (_id) for the record I just created.






Friday, May 18, 2012

Setting up a dialog template


A real common task is to pop up a confirm/cancel dialog on button clicks.  I've moved this code into a method that I can easily copy and paste into Android Activities and call the dialog with one line of code like the line below. This sample code shows a one button dialog, it's easy to do a two button dialog.  It's easy to set up your custom dialog xml layout which is really similar to any other layout.


showOneButtonDialog(getResources().getString(R.string.errorDialogTitle), message);



 private void showOneButtonDialog(String titleStr, String message) { 

                AlertDialog.Builder builder = new AlertDialog.Builder(WeeksOfPractice.this); 

                LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 

                View layout = inflater.inflate(R.layout.custom_dialog, 

                          (ViewGroup) findViewById(R.id.layout_root)); 

                TextView title = (TextView) layout.findViewById(R.id.headerTitle); 

                title.setText(titleStr); 

                TextView text = (TextView) layout.findViewById(R.id.text); 

                text.setText(message); 

                ImageView image = (ImageView) layout.findViewById(R.id.image); 

                image.setImageResource(R.drawable.ic_launcher); 

                builder.setView(layout); 

                AlertDialog errorDialog = builder.create(); 

                errorDialog.setButton(getResources().getString(R.string.dialogContinue), 

                          new DialogInterface.OnClickListener() { 

                               public void onClick(DialogInterface dialog, int id) { 

                                    dialog.cancel(); 

                               } 

                          }); 

                errorDialog.show(); 

           }  


Monday, May 14, 2012

Displaying PDF files

I recently had a request by a client to be able to display links that would go to hosted PDF files (files that are out in the cloud somewhere and not resident to the device).  Now there is no guarantee that a particular device has a PDF viewer, say from Adobe installed. And using that viewer would kick the person out of the app anyway when the intent fires.  One easy solution for this is to pass the PDF link to Google documents which would automatically convert the PDF to html and display it.

if (url !=null && url.endsWith(".pdf")) {
Uri uri = Uri.parse("http://docs.google.com/viewer?url=" + url);
Intent intent = new Intent(Intent.ACTION_VIEW);
 intent.setDataAndType(uri, "text/html");
startActivity(intent);
}

Sunday, May 13, 2012

XML based Drawables


Android gives you the ability to define shape drawables using xml.Basically these are simple shapes that are great for using as backgrounds or buttons.  The sample below will create a white oval that I use for backgrounds of linear layouts, say for a product description with an image and price. More information is at the link. To add a drawable, pick one of res/drawable directories and save the xml file right to it. For example, I saved a file called shape_white_four_corners.xml to my drawables-hdpi directory.  It'll appear as part of your resource and you can access it just like any other drawable.

http://developer.android.com/guide/topics/resources/drawable-resource.html#Shape


 <?xml version="1.0" encoding="utf-8"?> 

 <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"  

 android:background="@color/white"> 

   <gradient android:startColor="@color/white" android:endColor="@color/white" 

       android:angle="270"/> 

   <stroke android:width="0.5dp" android:color="@color/white"/>     

   <padding android:left="7dp" android:top="7dp" 

       android:right="7dp" android:bottom="7dp" /> 

   <corners android:bottomRightRadius="8dp"  

           android:bottomLeftRadius="8dp" 

           android:topLeftRadius="8dp"  

           android:topRightRadius="8dp"/> 

 </shape>  

Wednesday, May 9, 2012

Gmail Attachment Issues

Android will allow a user to put more than one app on it that does something, for example send email So you have your email account app out of box, your gmail account and let's say a third party super duper email app that you can't live without installed on your phone. When you click the email link, it will launch a picker that lists these three apps. You select the one that you want to process the message and it does it. All fine and dandy. Once that picker comes up, you're out of your app and in the picker app. In other words, you don't have much control over error handling. Here's the problem: Gmail can't process attachments. It throws an exception that can't be caught and I can't control what gets processed by the app. For example, having gmail handle a link to an web site instead of putting the attachment right into the message. The only workaround that I have is a link in the message (the images I'm trying to send are on a web server anyway) to the image which should make it work on every app. Android bug report http://code.google.com/p/android/issues/detail?id=27269&q=gmail%20attachments&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars Google http://groups.google.com/a/googleproductforums.com/forum/#!category-topic/gmail/android/PsZ68HiLc_o http://thenextweb.com/mobile/2010/11/10/google-acknowledges-android-gmail-attachment-issues-invites-users-to-help-debug/

Tuesday, May 8, 2012

Debugging HTTP Connections

Most of my Android apps have multiple and sometimes a dozen or so of backend Get, POST and other http api calls. Part of debugging them is figuring out http header request and response data. This code snippet below is a useful utility method that allows you to log out http headers:


public static void logHttpHeaders(Header[] headers)
{
Log.d(TAG,"Logging http headers: " + headers.length );

for (int i = 0; i < headers.length; i++)
    {
    Header h = headers[i];
    Log.d(TAG,"Header: " + h.getName() + " " + h.getValue());
    }

}

Friday, May 4, 2012

Creating an iPhone like PickerWheel

One of the most useful interfaces that iPhone has is the picker wheel which I like to call roulette wheel.  Android date pickers and out of box select boxes are very html-like and not very slick looking. Designing and implementing one that is like the iPhone picker wheel is actually pretty complex/ About two years ago, I put one together for a customer but I was never quite happy with it and it never really felt exactly right.    Luckily the Android open source community has lots of really smart folk, doing some great work and if you're looking for a nice interface to extend in your project, this open source wheel project is what you're looking for.


Android Wheel Control. https://code.google.com/p/android-wheel/

I've used it on a couple of projects and it works great.  It's relatively easy to customize and has some great sample code that pretty much will do what you need to do out of box.  It's one of my favorite Android projects and worth a look to see if it jazzes up your app.

Monday, April 30, 2012

Prevent autolaunch of soft keyboard


Android will want to give focus to an edittext when an activity loads which will automatically popup the soft keyboard. You may not want the keyboard to display until the user taps an edittext field. To prevent Android from launching the keyboard, simply request focus to some other (non-editable) field like a textview like in the code below.

TextView textViewTV = (TextView) findViewById(R.id.mytextview);
        textViewTV.setFocusable(true);
        textViewTV.setFocusableInTouchMode(true);
        textViewTV.requestFocus();

Friday, April 27, 2012

Limiting Debug Trace to your App

I tend to debug on a device rather than an emulater and depending on which device I'm using., there might be tons of logging which basically makes trace statements unusable. Luckily in Eclipse, it's easy to create a log filter to get rid of the messages I don't want to see.
In debug mode, you'll see the logcat trace down at the bottom and probably a saved filter list to the left of the logging.  Click the green cross icon and a dialog will popup with a few edit fields. The two that I use the most are Filter name (which gives your saved filter a name :-) ) and by Application Name which will limit trace to whatever application you want. The application name is basically the package name in your android manifest


manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.appulearn.coolapp.android"

So I would paste in com.appulearn.coolapp.android

You can also filter out by TAG, and depending on what I have running (like for example a bunch of services) I might create a tag filter which would match the tag name you use in your log.d(TAG, "whoops! error"); statement.

Tuesday, April 24, 2012

Message and Handlers


Message and Handlers

Handlers are a useful too for doing a task within a class in response to a change in a variable state or to an action. A common use for a handler is to update the UI once a thread is done processing (if you choose to do it this way versus asynctask.)  I often use a handler when I want an activity to do something in response to the completion of a service or to button clicks or to form validation and other things that might generate UI changes. For example, let's say that the app starts up and kicks off a service. When the service completes, it fires an Intent that the activity is listening for which triggers the handler to redirect off a splash screen either to a login screen or a main screen based on what the service returns.

Now how does the activity know what to do? By opening up the intent and getting the bundle. The example below uses a handler to receive a broadcast from a service category download. If the service has processed successfully, the icon will update itself to a success icon, otherwise the error message from the service will be displayed.   The service generates an intent with a bundle, and broadcasts it.  The activity listens for that broadcast and processes it (code below is basically logging the intent and builds a message out of it to pass to the handler.The trigger to update the UI is a message that I create through a utility method.


 private BroadcastReceiver mReceiver = new BroadcastReceiver() 

 { 

      @Override 

      public void onReceive(Context context, Intent intent) 

      { 

           Bundle data= intent.getExtras(); 

           mHaveCategoryError = data.getBoolean(GlobalConstants.EXTRA_ERROR_STATUS, false); 

           mDoRedirect = data.getBoolean(GlobalConstants.EXTRA_DO_REDIRECT, true); 

           String error= data.getString(GlobalConstants.EXTRA_ERROR_MESSAGE); 

           HashMap<String, String> dataList = new HashMap<String, String>(); 

                dataList.put(GlobalConstants.EXTRA_ERROR_MESSAGE, error); 

           Message message = Utils.createMessage(1, dataList); 

           handler.dispatchMessage(message);           

      } 

 }; 

 private Handler handler = new Handler() { 

      @Override 

      public void handleMessage(Message msg) { 

           Bundle b = msg.getData(); 

           String message = b.getString(GlobalConstants.EXTRA_ERROR_MESSAGE); 

           String messageType = b.getString(GlobalConstants.EXTRA_MESSAGE_TYPE); 

           if (messageType!=null && messageType.equals(GlobalConstants.SERVICE_CATEGORIES_DOWNLOAD)){ 

                ImageView iv1 = (ImageView) findViewById(R.id.categorySpinnerView); 

                TextView statusMessageE = (TextView) findViewById(R.id.categoryStatusMessage); 

                Drawable d= getResources().getDrawable(R.drawable.iconsuccess); 

                if (message!=null) { 

                     d= getResources().getDrawable(R.drawable.iconalert48); 

                       statusMessageE.setText(message); 

                } else { 

                     statusMessageE.setText(getResources().getString(R.string.categoriesDownloaded));      

                } 

                 iv1.clearAnimation(); 

                  iv1.setImageDrawable(d); 

           }  

      } 

 } 

 public static Message createMessage(int messageId, HashMap<String, String> dataList) 

   { 

           Message msg = new Message(); 

           msg.what=messageId; 

           Bundle data = new Bundle(); 

           if (dataList == null) 

           { 

                dataList= new HashMap<String, String>(); 

           } 

           Set<Entry<String, String>> entries = dataList.entrySet(); 

           Iterator<Entry<String, String>> it = entries.iterator(); 

           while (it.hasNext()) 

           { 

                Entry<String, String> entry = (Entry<String, String>) it.next(); 

                data.putString(entry.getKey(), entry.getValue()); 

           } 

           msg.setData(data); 

           return msg; 

   }  

Sunday, April 22, 2012

Showing Progress in a Long Running Process

One of the great things about Android is that there often is several different ways you can implement a specific programming task. Let's say that you want to have some sort of indication that a background process is occurring, for example downloading bootstrap data or posting data to an api or simply displaying a splash screen when the app launches for a few seconds.

Iphone apps use a little spinner icon to denote this.

You could do this as out of box progress bar and there is a DownloadManager api that looks pretty interesting and I'm going to be trying it on my next project. The disadvantage of the progress bar is that phone manufacturers can change the default image associated with it which makes your GUI look different to different users.  

Now another approach would be to use an imageview and Android's animation framework to spin an icon.  Code is below and it's simple. This allows you to customize the spinning icon to your heart's content. On a surf report app I wrote, I spun a surfboard. On a ski report app, I spun a snowflake. On another Find Your Friend app, I actually spun to two little man images one doing a cartwheel to the left of a "LOADING...." textview and one doing a cartwheel to the right of that textview.  You'll see that I'm controlling the visibility of the image view which you set and unset in your asynctask or thread handler.


 <ImageView  
         android:id="@+id/spinnerView"  
         android:layout_width="wrap_content"  
         android:layout_height="wrap_content"  
         android:layout_marginTop="12dp"  
         android:visibility="invisible"  
         android:layout_centerHorizontal="true"  
         android:layout_below="@id/logo"  
         android:src="@drawable/loadingimagewhite" />  
  protected void startSpinner()  
   {  
           ImageView iv = (ImageView) findViewById(R.id.spinnerView);  
           iv.setVisibility(View.VISIBLE);  
                Drawable d = getResources().getDrawable(R.drawable.loadingimagewhite);  
             iv.setImageDrawable(d);  
             float width = d.getMinimumWidth() / 2;  
             float height = d.getMinimumHeight() / 2;  
             RotateAnimation a = new RotateAnimation(0, 360, width, height);  
             a.setDuration(3000);  
             a.setRepeatCount(RotateAnimation.INFINITE);  
             a.setInterpolator(new LinearInterpolator());  
             a.setRepeatMode(RotateAnimation.RESTART);  
             iv.startAnimation(a);  
   }  
      protected void stopSpinner()  
   {  
           ImageView iv = (ImageView) findViewById(R.id.spinnerView);  
           iv.setVisibility(View.INVISIBLE);  
        iv.clearAnimation();  
   }