Sunday 15 November 2015

Change layout dynamically in Android applications. A case study.

Android applications are usually built to function in a variety of devices that have different screen dimensions and also they must be functional in any orientation of the screen and in any change of the orientation. Moreover the efficient use of space especially in devices with small screens is very important and gives great value to the application, so the GUI of an activity must be designed with special care to screen sizes and orientation changes.
The indicative way to design the GUI of an activity is to prepare a layout file for it. This is a more straight forward procedure than defining the graphical elements of your app programmatically and is easier to handle and test. All the default layout files are stored in /res/layout directory of the projects file structure. In case you want to prepare a layout file for an activity with landscape orientation, you have to create directory /res/layout-land and you place the layout file in there. When the size of the screen matters and you want different UI for different screen sizes then you must create a layout for each case. For instance, a layout designed for screens with smaller size larger than 600 dp must be placed in a directory named layout-sw600dp and if you want to combine screen sizes and orientation you must place the appropriate layout file in the directory named layout-600dp-land.
All the layouts that refer to the same activity must have the same name regardless of the directory they are stored. When an app is executed, Android will search for the most appropriate layout considering the screen size and orientation. In case there is not a special layout defined, Android will use the layout stored in the default directory.
Problems start when some of the elements of the screen change dynamically while the app is running and a layout has already been loaded. In most cases the change in the orientation of the screen causes troubles in the dynamically changed elements. When orientation changes, Android usually reloads the activity losing all the changes the user has caused. In order to avoid that you must edit the manifest file and in the activities that are declare some how like this

        <activity
            android:name=".app.MyActivity"
            android:label="@string/title_activity_myactivity" >
        </activity>


add one line of code changing them like this

        <activity
            android:name=".app.MyActivity"
            android:configChanges="keyboardHidden|screenSize|orientation"
            android:label="@string/title_activity_myactivity" >
        </activity>


This tells Android that you know what to do with your activity so it shouldn’t change anything on its own. Let’s see now what you should do when it is you that you want to cause layout changes when orientation changes. In order to present this more efficiently I will use the example of my app called Map This Way.
In an activity of this app called activity_line the user may record the GPS tracks along a route. In the layout there is some text that is updated with the current GPS coordinates and a map view that shows user’s current location. In big screens when the screen is in landscape orientation the map is better to be on the right of the screen and the text on the left and when the screen orientation is vertical the map view is better displayed under the text. In small screens the map view should always be under the text. In order to do this I prepared layout files for all cases. Then I had to arrange how the app will behave in screen orientation changes.


Orientation changes are detected and manipulated by the onConfigurationChanged method which must be overridden.

@Override
public void onConfigurationChanged(Configuration newConfig){
     super.onConfigurationChanged(newConfig);
     setContentView(R.layout.activity_line);
}


The above code reloads the UI of the activity using the most appropriate layout file for the new orientation of the screen. The advantage of using this technique instead of letting Android do the changes is that in this method you may add code to define how the activity elements will be displayed in the new screen. The disadvantage is that the elements are initialized and reloaded so you will have to add code so as to initialize them properly and update the values of the layout elements with the changes that have occurred so far. Also you have to restart procedures that may have stopped after orientation changed like advertisement display etc.
In the case of my app the map view has to be updated so the users will see their current location and if the mapping procedure has started the colors and functionality of layout elements has to be adjusted accordingly.
In general, a good way to deal with such situations is to initialize the layout elements in the onConfigurationChanged method as you did in the onCreate method. So for update of the map view of my app I use the method showmap(x, y) which updates the location of the map and it is executed in both methods.
Another problem occurs when you have to pass some value from a variable that affects layout elements to the newly initialized elements after orientation changes. For this, the use of some global variable is very helpful. In my app when the mapping procedure has started, a red button appears which may be pressed by the user and cause the stopping of the mapping procedure. The way I worked around this issue is the declaration of a global boolean variable called mapping which takes the value true when mapping has started and false otherwise also the declaration of a global Button instance. Then I used both in onConfigurationChanged method.
All the above were formed as follows.

@Override
public void onConfigurationChanged(Configuration newConfig){
    super.onConfigurationChanged(newConfig);
    setContentView(R.layout.activity_line);
   showmap(x, y);
   if (mapping){
        button1=(Button) findViewById(R.id.button1);
        button1.setBackgroundColor(0xffff0000);
   }
}

Then activity_line is functional and ready to use.