Monday, 9 March 2015

Hitchhiker's Guide to Modern Android Development: The Pitfalls and Cryptic Errors

No platform or language is perfect, each has its own problems, pitfalls and cryptic errors of its own. Thanks to Java heritage, Android usually offers smooth, easily learning curve with a huge database of blogs, forums and stackoverflow answers.
After running and mentoring the Study Jam series for two weeks, I decided to compile a set of common pitfalls developers came across which tend to be cryptic or not google friendly.


Error: Gradle DSL method not found: 'runProguard()'
Possible causes:<ul><li>The project 'XX' may be using a version of Gradle that does not contain the method.
<a href="open.wrapper.file">Open Gradle wrapper file</a></li><li>The build file may be missing a Gradle plugin.
<a href="apply.gradle.plugin">Apply Gradle plugin</a></li>

This is one of the most cryptic error you may come across on Android. If you have been using Android Studio from Beta, you will/did probably face this error after upgrading to gradle version 0.14.0 (Android Studio 0.9). Simply the Android Tools team decided to rename runProguard to minifyEnabled. Similarly zipAlign has been enabled to zipAlignEnabled. You may find the full list of changes here.


Error:The project is using an unsupported version of the Android Gradle plug-in (X.X.X). The recommended version is X.X.X.
<a href="fixGradleElements">Fix plugin version and re-import project</a>

Similar to previous issue, this problem occurs each time you get an update to Android Studio and Gradle. Unlike the previous one, this can easily be solved by clicking the Fix plugin version link provided in the Gradle Messages window.


android.os.NetworkOnMainThreadException

You may come across this error if it is your first network call ever or you are updating an old app to post Honeycomb. Beginning from 4.0 (Ice Cream Sandwich) Android OS does not allow performing network operations on main (UI) thread which should have been this way since day 0. The main thread's main responsibility is running the UI smoothly. Networking, file i/o or any other lengthy operation will block the UI updates resulting in non smooth UI/UX. Networking might be the worst of all since the speed of the connection can rely on many different factors.
The solution is simple. Anything which is not part of UI should not be done in the main thread. Android offers an easy way to use threads which is called AsyncTask. Simply use an AsyncTask to perform lengthy operations.


I use an AsyncTask but still getting android.os.NetworkOnMainThreadException

To start an Asynctask, you need to call the .execute method on the Asynctask object. Calling the .doInBackground method will bypass the creation of a new thread and run the network operation on the main thread.


java.lang.SecurityException: Permission Denial: ... requires Android.permission.X

Android is based on linux and each app runs on its own sandbox with very limited permissions. If you need access a common system resource such as camera, bluetooth, internet or file system, you need to ask users permission. The permission are added to AndroidManifest.xml between the manifest and application tags. You can refer this for a complete set of permissions which can be added with uses-permission tag.


I added the permission java.lang.SecurityException: Permission Denial: ... requires Android.permission.X

Having a valid AndroidManifest.xml does not necessarily mean it is just working. Adding the uses-permission tags in the application tag, does not break the xml structure but doesn't reserve the permissions you asked either. List your uses-permission tags out of the application tag.


Permission Denial: … requires android.permission.WRITE_EXTERNAL_STORAGE

If you are sure you reviewed the previous two items but still getting the error, this should be because of the android:maxSdkVersion="18" property inside the uses-permission tag. To allow your app to use the write permission above version 18 you need to remove maxSdkProperty.
<uses-permission android:name=“android.permission.WRITE_EXTERNAL_STORAGE"/>


My Fragment's overflow menu displays a menu item (settings) not in menu.xml

Fragments can add their own menu items by overriding onCreateOptionsMenu(). However this does not mean cleaning up the menu items added by the activity. Since a fragment is always hosted by activity and uses the activity's context, it would inherit such stuff from the hosting activity. The item which you believe (settings is given as the default menu item) is not relevant belongs to the activity. Navigate the menu.xml which belongs to the activity and remove the unwanted item(s).


The method replace or commit in the type FragmentTransaction is not applicable for the arguments 

You are passing a fragment object to a method which is expecting a fragment object but still complaining? Well, not all fragments are the same. Java's design allow classes with same names as long as they are in different packages. Android SDK team decided to introduce the fragment support package with the same class name (Fragment) but in a different package (android.support.v4.app.Fragment vs android.app.Fragment). Navigate to imports and remove the wrong import and add the appropriate one.


onOptionsItemsSelected(), onCreateOptionMenu(), afterTextChange(Editable s), onPostExecute() or any other method which should have been called is not called?!?

The @Override annotation must have been the most underrated annotation of all time. It exists for a very specific reason. Unlike C++ every method is virtual in Java. Thus, unless it is final any method can overriden. The @Override simply checks if the implementation is really overriding a method from the parent class. All the methods above has a typo which any level of developer may do. Removing the override annotation also removes the ability of the compiler to detect any typos in the so-called overriden methods. This typos may occur in the method or even in the parameter list. Simply if a listener or a method which should have been called will silently failed to be executed. You can easily detect such errors by placing the @Override to every overriding method.