The popularity of Android has created a humongous demand for applications. As developers, it’s our responsibility to ensure that users don’t have a bad experience while using our apps. Here, at Innofied, there are a few Android development tips that we follow to ensure that our consumers/ clients get the optimum experience from the products. Lets take a look:
Android’s official website explicitly states “Android Studio is now the official IDE for Android”. If you’re still on Eclipse and that’s not enough reason to switch, here are few exclusive features of Android Studio that might change your mind:
Furthermore, Google has ended development of the ADT plugin for Eclipse.
Adding text as String resources is always useful in the long-run especially when support for new languages need to be added.
The <include /> tag makes it possible to have a single layout, re-used across multiple activities and fragments.
For example, for a uniquely styled button that must be shown in many activities of your application, a separate layout can be created. That layout can then be included in each activity’s layout.
<Button android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/unique_button" android:textStyle="bold" android:text="Unique button" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="See button below" /> <include layout="@layout/unique_button.xml" /> </LinearLayout>
Another handy tag is the <merge /> tag. It acts as a pseudo-parent and helps get rid of an unneeded root ViewGroup.
For example, if your re-usable layout contains two Buttons placed vertically, you can put them inside a LinearLayout with a vertical orientation. But this LinearLayout becomes redundant if the layout is included (using <include />) into another LinearLayout. In this case, our re-usable layout can have <merge /> as the root ViewGroup instead of LinearLayout.
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Submit" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Reset" /> </merge>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Enter text" /> <include layout="@layout/buttons.xml" /> </LinearLayout>
Many devices (and launcher apps) use launcher icons from the xxxhdpi drawable folder. Moreover, the LG G3 and Nexus 6 use all resources from xxxhdpi folders. There will definitely be many xxxhdpi devices in the future. So, you should start developing a habit of including them.
When building separate apks for different densities, drawable folders for other densities get stripped. This will make the icons appear blurry in devices that use launcher icons of higher density.
Since, mipmap folders do not get stripped, it’s always best to use them for including the launcher icons.
Gradle allows configuring custom build types. By default, two build types, debug and release are provided.
We can provide separate files (source, resources & assets) for each build type by creating their corresponding folders, adjacent to the main folder, in the project structure. These folders follow the same structure as main & should contain only the overriding files.
An example of its usefulness is when you need to have a configuration file for Google Analytics. Separate XML files containing the Tracking ID can be created for both build types. During compilation, the file corresponding to the build will automatically be pulled.
Something to note here is that resource files/values from the main folder are always used. But a resource file/value in main will be overridden if it also exists in the build folder.
The image shows how to place the configuration file, ga_tracker.xml, for Google Analytics for debugging and release build types.
Basic shapes and gradients can easily be drawn using the <shape /> tag without the use of images. The resulting shapes that are drawn are always sharp and do not need to be created for multiple densities.
A basic circle can be created in the following way and saved as circle.xml in the drawables folder
<shape android:shape="oval" > <solid android:color="#ff01aef0" /> </shape>
The <selector /> tag can be used to add different visual states (like pressed, disabled, checked) to Views.
A simple selector, to add a pressed state background to a button, can be created in the following way and saved in the drawables folder
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/button_pressed" android:state_pressed="true"/> <item android:drawable="@drawable/button_normal"/> </selector>
Having a deep hierarchy of Views makes the UI slow, not to mention a harder to manage layout.
Deep hierarchies can mostly be avoided by using the correct ViewGroup.
For example, a view hierarchy like this:
can be created in either of these ways:
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/magnifying_glass" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="top text" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="bottom text" /> </LinearLayout> </LinearLayout>
OR
<RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/magnifying_glass" /> <TextView android:id="@+id/top_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toRightOf="@id/image" android:text="top text" /> <TextView android:id="@+id/bottom_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/top_text" android:layout_toRightOf="@id/image" android:text="bottom text" /> </RelativeLayout>
The second way should be preferred since it has a single level hierarchy.
The Google Play Services library is a package that contains all the native Google libraries for Android. Google also provides these libraries individually so that we can avoid adding the whole Google Play Services package. They can be added in build.gradle.
A list of the available libraries can be found here.
When building a big application, our networking code can get huge because of boilerplate code. Not only does that make it difficult to maintain but also makes debugging harder. Libraries like Volley and Retrofit reduce a lot of boilerplate code and we have fewer things to worry about.
Serialization of an object that implements the Parcelable interface is much faster than using Java’s default serialization.
A class the implements the Serializable interface is marked as serializable and Java serializes it using reflection (which makes it slow).
When using the Parcelable interface, the whole object doesn’t get serialized automatically. Rather, we can selectively add data from the object to a Parcel using which the object is later deserialized.
A caveat while using an AsyncTask is that if the Activity gets destroyed before the AsyncTask has completed, it will still keep running and deliver the result in it’s onPostExecute() method, which could cause unexpected behavior. A typical example of this situation is when a device is rotated while an AsyncTask is loading content.
Loaders were introduced in Honeycomb but can also be used in pre-Honeycomb versions using the support library.
Loaders are managed by a LoaderManager which is tied to the lifecycle of its Activity or Fragment. Each Activity or Fragment contains an instance of LoaderManager. If the Activity/Fragment is destroyed, the LoaderManager destroys the Loaders and frees up resources. In case of a configuration change, it retains its Loaders.
We can get a LoaderManger instance and initialize a Loader in the following way:
getLoaderManager().initLoader(LOADER_ID, null, this);
A simple AsyncTaskLoader can be created in the following way:
class CustomLoader extends AsyncTaskLoader<String> { public CustomLoader(Context context) { super(context); } public String loadInBackground() { String result = null; // Load result string return result; } }
You might also want to override onStartLoading(), onForceLoad(), onReset(), onCancelled(), onStopLoading(), onAbandon(), cancelLoadInBackground(), onCancelLoad() according to your needs.
When calling the initLoader() method of a LoaderManager, we must pass an implementation of LoaderManager.LoaderCallbacks as the third parameter. It’s a callback for the events occurring in a Loader.
class MyActivity extends Activity implements LoaderManager.LoaderCallbacks<String> { private static final int LOADER_ID = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // set layout and stuff getLoaderManager().initLoader(LOADER_ID, null, this); } @Override public Loader<String> onCreateLoader(int id, Bundle args) { // Return a loader according to the Loader id return new CustomLoader(context); } @Override public void onLoadFinished(Loader<String> loader, String data) { // Process result here } @Override public void onLoaderReset(Loader<String> loader) { } }
File operations should always be performed on a worker thread typically using an AsyncTask/Loader. They take time and if done on the UI thread can make the interface feel sluggish. And in case it blocks the UI thread for 5 seconds, an ANR will be triggered.
These are some strong tips that one should always remember while developing an Android application. There are numerous other optimizations that can be done and much more that might be specific to your application. Following good practices not only makes it easier to manage and maintain your code but also reduces the number of potential bugs in your application.
If you have any questions don’t forget to drop them in the comments below.