Walter has been developing applications since the 80's, and professionally since the late 90's. He has been an early adopter of Java technology in the dot com days, and has managed and led software development teams across the globe. Walter has posted 4 posts at DZone. View Full User Profile

Android App to Monitor Hudson - Part II Configurations

02.11.2010
| 6623 views |
  • submit to reddit

Last week, demonstrated building and Android application that queried Hudson remote api through REST calls, which returned back JSON objects; in Android App to Monitor Hudson Rest API. The application was very basic, in that all that could be done was launch it and there were no configurations, or customization to make the application more user friendly. This week the application will be enhanced to use menus and add additional screens or activities to allow a user to configure the application to point to a Hudson remote server of their choosing. The application will also save the configuration state so the user doesn't have to re-enter the information after the application has shutdown.

The first step in customizing the application will be to get rid of the default android icon, and place a customised icon. Android supports .jpg, .gif, .png, and .bmp image formats. To customize the default icon, simply open the res/drawable folder and put the image in this folder. The image should be a 48x48 size .PNG image.

 

Open the AndroidManifest.xml file and edit the android:icon attribute to be android:icon=”@drawable/youriconname”. Notice the .PNG extension is not included in the name. Resources are compiled into the final application and are identified by the folder structure of where the file is located in the res/ directory. The main one for this application are drawable, menus, layout, values (simple values).

Example Launch Icon.

 hudson launch icon

  There are three main types of menus. Options Menus, which are activated by hitting the menu key. Context menu, which are when a long press on the application is detected. This is similar to right click menu option on a computer. Then finally Submenu, which can be added to any menu item except another submenu; example is nesting menus. This is a simple application so it will only use an Options Menu. One option for settings, and another for refreshing the listing.

In the main listing activity class (SpyHudson.java), add the onCreateOptionsMenu(Menu menu) call back method. This method overrides the parent menu creation and is modified to add the two menu items we need. So to make unique identifiers for which menu item was clicked on we will declare two constants, for the menu item of setup and another for refresh.

private static final int ACTIVITY_SETUP = Menu.FIRST; 
private static final int ACTIVITY_REFRESH = Menu.FIRST + 1;

Modify and add the following to the onCreateOptionsMenu call back.

/* Creates the menu items */
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, ACTIVITY_SETUP, 0,"Setting");
menu.add(1,ACTIVITY_REFRESH,0,"Refresh");
return true;
}

The first parameter in the add menu simply assigns the item to a menu group, the second is the unique menu identifier, the zero is the order the menu items appear, and the string is what the display name of the menu item will be. An alternative to writing out the text in code is to reference it as a resource by adding it to the res/values/strings.xml and referencing as: getString(R.string.setting)

The modification then would be:

public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, ACTIVITY_SETUP, 0, getString(R.string.setting_menu));
menu.add(0,ACTIVITY_REFRESH,1, getString(R.string.refresh_menu));
return true;
}

Now, to handle menu actions, this is done by adding the method call back onOptionsItemSelected(Menu). If you've worked with swing this is an action event. The Menu object on the call has the item identifier, which we declared earlier and that can be used in the switch statement of this method. The “Settings” menu option selected will call changeSettings(). The “Refresh” menu option will call a method refreshView(). The interesting thing here is the onOptionsItemSelected expects a boolean return value. False allows normal processing to proceed and true indicates to handle process here. Like Swing is not thread safe, same holds true for the Android UI, and so you have to be a little more precise about where you want the UI to hold up.

  
       /* Handles item selections */
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case ACTIVITY_SETUP:
changeSetting();
return true;
case ACTIVITY_REFRESH:
refreshView();
return true;
}
return false;
}

  The resulting menu will look something like this:

menu example

The changeSettings() call will be addressed in a little bit.

SharedPreferences is an interface that allows an Android application to save a preference state into a hashmap which can be stored and retrieved as needed. Use getSharedPreferences(“preference_filename”,mode_id) to get a reference to this interface. The preference file name is any string, and the default mode id should be 0. A mode of 1 indicates the preference file then is readable by other android applications, and mode 2 indicates it's writable by other android applications. Most cases the mode should be 0 as the preferences should be private to the current Android application.

Access the value of a preferences is the same as java properties object. Simply call sharedpreference.getString(“keyname”, “default value”).

The result of the refresh view is the following two methods:

        private void refreshView() {
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
String remoteUrl = settings.getString("serverurl", "http://localhost:8080/hudson/api/json");
this.refreshView(remoteUrl);
}

private void refreshView(String url) {
ArrayList<JSONObject> jobs = spyRest.retrieveJSONArray(url);
setListAdapter(new ImageTextListAdapter(this,jobs));
}

Notice, that we simply moved the two lines at the end of onCreate(Bundle) where it makes the call to the remote URL, and moved it to this new method.

Saving to the preference file is simple as getting the SharedPreferences.Editor, putting the key name and the string value into it. Make a special note to call editor.commit() to save the data. This is done in the savePreferences method call.

private void savePreferences(String value) {
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString("serverurl", value);
// Don't forget to commit your edits!!!
editor.commit();
}

 Each screen within Android is an Activity. Therefore, we want to make another screen for configuring the server URL we would like to query on a Hudson CI server. First create a layout in res/layout, and it will have a text with an edit box, and a button. Here layout management skills become necessary; laying out components for an application that can be deployed on a multitude of screen sizes needs to be considered in various choices.

This is the following layout:

 settings screen

 The supporting xml layout for this form is as follows:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>

<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">

<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_title" />
<EditText android:text="@string/server_url_text" android:id="@+id/serverurl"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>

<Button android:text="@string/confirm_btn" android:id="@+id/confirmbtn"
android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
</LinearLayout>

The next step is to create the interacting activity class AppSettingActivity. Like all activity classes we will override and add to the onCreate(Bundle) callback. We need to get the edit text and overwrite the default text with the information in our preferences to do this use the findViewById() passing the reference ID into it. This will return a view object from the layout xml, that then can be cast into the EditText object. There the text for the edit box can be set. This is all done in the onCreate(Bundle) call in the activity:

     editTextServerUrl = (EditText) findViewById(R.id.serverurl);

SharedPreferences settings = getSharedPreferences(SpyHudson.PREFS_NAME, 0);
String remoteUrl = settings.getString("serverurl", "http://localhost:8080/hudson/api/json");
editTextServerUrl.setText(remoteUrl);

 Next, add a click listener to the button so that when it is clicked it stores the text into a bundle that can be passed back to the calling intent and close out this activity. Intent objects are bindings to activities or a go between the activity life-cycle. Intents will be talked about in a moment. For now, the additional information of the edit box will be put into string mapping bundle. This is to demonstrate how bundling between activities works. The other option is to simply use SharedPreferences and store the value of the edit text right into the preferences, as we used this to pull previously stored information from it.

        Button confirmButton = (Button) findViewById(R.id.confirmbtn);
confirmButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {
Bundle bundle = new Bundle();

bundle.putString("server", editTextServerUrl.getText().toString());

Intent mIntent = new Intent();
mIntent.putExtras(bundle);
setResult(RESULT_OK, mIntent);
finish();
}

});

  This almost completes the application. The final step is wiring the activity into the main application requires adding it to the application manifest. If you forget to add this to the AndroidManifest.xml you'll get an application that errors and asks to “force close” it when it tries to launch the activity. Remember an application can have one or many activities.

 (See how activity .SettingsActivity, which is the class name is added to application)

  <application android:label="@string/app_name" android:icon="@drawable/hudson">
<activity android:label="@string/app_name" android:name=".SpyHudson">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".AppSettingActivity"/>
</application>

Remember the changeSettings() method in the SpyHudson class. We will add the Intent of launching the AppSettingActivity and monitor it for returning activity. This is how this looks:

       private void changeSetting() {
Intent i = new Intent(this, AppSettingActivity.class);
startActivityForResult(i, ACTIVITY_SETUP);
}

The startActivityForResult call launches the activity AppSettingActivity, and when the activity finishes the results will then a onActivityResult() call will be placed. This method call has to be overridden of the parent class.

After the activity returns we want to get the inent's extra information because as recalled it had the “server” url information. This is inside the Bundle object. Then save that string in the preference by calling the savePreferene() method call created earlier, and refresh the listing view calling the refreshView() method. Here is what this looks like:


       @Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
Bundle extras = intent.getExtras();
switch (requestCode) {
case ACTIVITY_SETUP:
String serverUrl = extras.getString("server");
this.savePreferences(serverUrl);
refreshView(serverUrl);
break;
}
}

Run the application and it should have two menu options. The setting menu option bring the user to a screen to fill in the hudson url that they want to query and that information is stored in the application's local preferences.

This application is really simple, and touches upon some common core elements of an Android application. It doesn't have all the elements to trap errors of network timeouts and handle user input of bad malformed urls, since again it is a presentation of a possible app for the Android platform, which talks to Hudson's REST api.



 

 

 

AttachmentSize
SimpleHudsonImproved.zip28.69 KB
Published at DZone with permission of its author, Walter Bogaardt.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)