mdoery ~ software development

adventures in coding

Observer / Observable pattern in Java

| Comments

Summary: A very simple example of the Observer / Observable pattern in Java

I’ve created a very simple example of the Observer pattern using Java’s built-in Observable class and Observable interface.

In my example, a class called ObservableExample extends Observable. It has a method called setCookie which is used to set a String with the name of a cookie such as “Chocolate chip”. When this method is called, its Observer is intended to do something – in this example, it prints a message about what beverage accompanies each type of cookie.

There are three key pieces to this pattern:

  1. The class that implements Observer must override update. The update method is called when the Observable calls its notifyObservers method.
  2. You “register” an Observer with the class that extends Observable by calling addObserver on it. You can add as many Observers as needed.
  3. The class that extends Observable must call the superclass’s Observable.setChanged method prior to calling notifyObservers, otherwise no notification will be sent to the Observer.

Here’s a skeleton of the code, showing all the important parts without implementation details:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ObserverExample implements Observer {
  public void update(Observable obs, Object arg) {
      // Do something depending on arg and/or obs
  }
}

class ObservableExample extends Observable {
  public void someImplementationSpecificMethod(...) {
      setChanged(); // method of Observable superclass.
      notifyObservers(...); // causes update method to be called on any Observers
  }
}

...
// In other code, e.g. main method:
ObservableExample observable = new ObservableExample();
ObserverExample observer = new ObserverExample();
observable.addObserver(observer);
// later...
observable.someImplementationSpecificMethod(...);

This code sample is available as a project in github. That project contains an additional code example which abstains from using Java’s Observable class and Observer interface. This demonstrates that you can do what you want without those tools. However, you have to introduce additional code to manage the notification into your own classes. This makes the code more cluttered, and less clear. Our little demo project is all about serving the right hot beverage with a given cookie. Using the Observer pattern abstracts away functionality which is not crucial to this purpose!

swipe over radio buttons in android

| Comments

Summary: Demonstrating how to register a swipe over a row of radio buttons in Android

In this Android coding example, a user is presented with a row of radio buttons. Clicking on a radio button causes the background of the application to change to a different color.

That’s the easy part! Here’s the hard part: there’s a secret swipe, from the left edge of the screen all the way to the right edge, which changes the background color to black. Not only do you need to swipe all the way from left to right, you also have to swipe over the radio buttons.

Android is designed to make it easy for radio buttons to register and respond to clicks. This example is complicated because detecting a swipe over the screen, if done incorrectly, may interfere with that mechanism. Also, we don’t want the left-to-right swipe to do anything if it falls outside the radio buttons. So special code needs to be added to check for this when swiping.

The code sample which solves this problem is available as a project in github.

These are the key parts to the solution:

For responding to individual clicks, the radio buttons are given an onClickListener when they are created.

Responding to a swipe is the tricky part. The activity’s onTouchEvent is implemented to check when a swipe starts (with MotionEvent.ACTION_DOWN) and ends (with MotionEvent.ACTION_UP). Then, as the user’s finger moves across the screen, the code in onTouchEvent checks to see if its position stays over the radio buttons.

The radio buttons are laid out in a LinearLayout and given a specific id, surround_layout. If we know the upper and lower vertical boundaries to this layout, then we can check to see whether the user’s “touch” strays outside this boundary while it moves across the screen – when onTouchEvent receives a MotionEvent.ACTION_MOVE event.

Here’s the “design” view of that layout in Android Studio. The boundaries of the layout show up as a white frame.

radio button layout

That’s great, but how do we get the boundaries of the layout when our application is running? The answer is that our Activity implements ViewTreeObserver.OnGlobalLayoutListener. This is done so that the boundaries can be computed after the layout is inflated. If you try to get these values before the view is inflated, you’ll find that the boundaries are not correct.

This code example is useful because it forces you to deal with the way layouts are created and inflated. This is something that is usually safely ignored when programming for Android. However, understanding these details can save you a lot of time when they become important.

centered radio buttons in android

| Comments

Summary: Demonstrating how to line up radio buttons that have different heights in Android

Recently, a client requested that an Android app have some custom radio buttons added. Android radio buttons are similar to those you find on the web. They allow you to select one and only choice. Graphically, they are quite simple by default. Usually each button looks identical. Sometimes, people like to get fancy and include text or make some of the buttons larger or smaller. It is certainly possible to do these things in Android, but your first attempt may look rather ugly.

I created a simple project and added it to my github account to demonstrate the problem. Here is a button that’s supposed to appear at the left of the row of buttons. Notice it has some text below it:

left-most button includes text

Next, here’s a button that will appear in the middle of the row. It does not have any text in it, so its height is less than the other button:

middle button does not include text

When you first add these images as custom radio buttons to your Android app, you get something that’s a little bit ugly. This screenshot shows the problem: technically, your buttons are lined up in a row. However, visually, it looks like they are misaligned, because of the text in the outer buttons:

custom radio buttons misaligned

In the screenshot above, the different parts of the layout are highlighted in white to show you their boundaries. Clearly, the outermost buttons are taller than the inner ones.

There are different ways to deal with this, but the simplest one is to rework your buttons so that they all have the same height. That’s what I did. Here are the two new buttons. They look identical, but now they have the same height. The second button is now taller, but it’s just padded with transparent fill.

left-most button includes text

middle button does not include text and is now taller

The new buttons were created by adding each of the four flower images to a single canvas with a transparent background in Gimp (a graphics tool), making sure they were lined up along their center points. Then, a rectangle was sliced around the entire set of buttons, cropping as tightly as possible to avoid excessive space being taken up by any individual button. Then, each individual image was sliced out from the rectangle, keeping their heights all the same.

Once I had the new images, I replaced the old ones in my Android app. Here’s the result:

custom radio buttons are centered and aligned

This is a good solution because you aren’t trying to force Android to realign the buttons programmatically. That can get tricky and isn’t really what Android is designed to do. When you add buttons in Android, it is recommended that you set their heights and widths in “dp” in the layout xml file. When writing the code to programmatically change image heights and widths, you’re dealing with pixels. It’s better to just avoid trying to make changes at the pixel level.

You can check out the entire project at github if you want to experiment with it.

[Edit October 2, 2018: It turns out that you can also solve this problem using Android’s ConstraintLayout. This adds a little bit of complexity to the layout, but it has the advantage that the button graphics do not have to be reworked. The github project includes code which demonstrates this option, as well.]

Thanks to inky2010 for making the flower images available in the public domain!

an android hiccup

| Comments

Summary: if you switch to https watch out for this Android bug

One of the Android projects that I’m working on uses the Apache HTTP library, which is located in a library – android.jar – that comes with Android. The project uses the library to connect to an external server using an HTTP GET command. I’ve experienced a couple of problems with this project due to this connection requirement.

(Note: this project is not mine, but belongs to one of my customers, so I’m not the original developer who wrote this code! I’m just dealing with the fallout.)

The first problem occurred when the IP address for the server was changed by the hosting service. I do not know why someone would hard-code an IP address when making a connection, but that was done in this app. The problem was obvious, and the fix was simple enough: change the line of code to point to the domain name (like, “http://example.com”) rather than the IP address. Problem fixed!

The second problem occurred more recently, when the hosting server was switched to use HTTPS. The URL had to be changed – again! – to read something like “https://example.com”. This happened months ago, and no issues were noticed… until a couple days ago, when I was adding a new feature to the app, and it suddenly stopped working!

I checked the logs and noticed an Exception with a lengthy stack trace. Here is the stack trace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
08-23 08:11:23.863 W/System.err(25119): javax.net.ssl.SSLException: hostname in certificate didn't match: <example.com> != <*.hostmonster.com> OR <*.hostmonster.com> OR <hostmonster.com>
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:185)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java:54)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:114)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:95)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:381)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:165)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:360)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
...
08-23 08:11:23.863 W/System.err(25119):   at android.os.Handler.handleCallback(Handler.java:615)
08-23 08:11:23.863 W/System.err(25119):   at android.os.Handler.dispatchMessage(Handler.java:92)
08-23 08:11:23.863 W/System.err(25119):   at android.os.Looper.loop(Looper.java:137)
...

The first line is key: “javax.net.ssl.SSLException: hostname in certificate didn’t match: <example.com> != <.hostmonster.com> OR <.hostmonster.com> OR <hostmonster.com>”. My customer is using hostmonster to host their external server. What’s going on?

When I saw this message, I thought the server might be down. The logs contained the URL being requested, so I accessed that URL in a browser, and found it was working just fine.

A simple search for javax.net.ssl.SSLException did not reveal a simple explanation for this error. After substantial hunting, I found a StackOverflow question which provided a clue – it seemed to be related to SNI (server name indication) support. That page pointed to another question at StackOverflow – Why does android get the wrong ssl certificate? (two domains, one server) which made things more clear.

The answer is that the client (the app, in this case) needs to support SNI, and if it does not, the server may send the wrong SSL certificate. That definitely seemed to be happening here. As recommended in the answer to that SO article, I rewrote the app code to use java.net.HttpURLConnection, and the app was fixed immediately. You might think it would be required to use HttpsURLConnection, but it worked perfectly fine with HttpURLConnection.

I did not research the problem much further. I noticed another open-ended question at SO asking what’s the deal with SNI support in Android.

Most of these questions and responses are fairly old. I found a discussion at Google’s issue tracker with the following remarks (from 2010 and 2011!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[bd...@google.com says:]
The main problem with supporting SNI in the browser is that many sites choke on TLS extensions, especially lesser known ones. Chrome and other browsers try to retry the connection without TLS (and without compression, which technically is SSL and not TLS) 

We have moved forward with this in 2.3 (Gingerbread) and javax.net.ssl.HttpsURLConnection does attempt to handshake with SNI (and automatically falls back to SSL w/o compression if there are problems). Unfortunately the browser uses Apache HTTP Client and there was not a simple local fix to make it retry failed connections, so it is overly conservative and does not include SNI information.

[vi...@gmail.com says:]
Non "corporate gibberish" summary : 
 * Gingerbread (2.3) Will never get this update officially. 
 * Honeycomb (3.0) contains the fix but isn't available on phones. 
 * Ice Cream Sandwich (4.0) will provide this feature to phones but isn't already available (soon). 

Also : 
 * The fix from Honeycomb could have been backported by the manufacturers that got it's source code but none that I know off did it. 
 * As Honeycomb is a closed source release none of the alternative distribution could merge the patch without re-developing it. 
 * As Ice Cream Sandwitch will include this patch it will be in all distributions and could even be backported to Gingerbread. 

Sadly coupled with the fact that google don't control at all the updates of previous phones it lock SNI in an "Unusable for most of Android users" state for 2+ Years at least for environements where you don't control users hardware. 

At least now for companies you could plan an upgrade (software or hardware depending on the phone models your employees got) as soon as ICS is released

I still don’t know why this problem appeared so suddenly. It could be due to some change at hostmonster (seems most likey), or it could be due to a change in the OS on my test device.

For the record, here’s the fixed code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
URL testUrl = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection)testUrl.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
int code = urlConnection.getResponseCode();
InputStream in = urlConnection.getInputStream();
BufferedReader bin = new BufferedReader(new InputStreamReader(in));
String inputLine;
StringBuffer sBuffer = new StringBuffer();
while ((inputLine = bin.readLine()) != null) {
  sBuffer.append(inputLine);
}
in.close();
String result = sBuffer.toString();
// do something with the result...

in praise of testing

| Comments

Summary: testing is good… no, great

I’ve been working on a website based on node.js for about 1.5 years now. I didn’t create the site, and when I got it, I saw that 1) it had a very extensive code base and 2) a small number of tests had been written relative to the size of the code base.

It’s understandable; I can see the temptation to avoid coding tests when you’re not really sure if the code will just end up being thrown away. The time spent writing tests could (perhaps) be more productively spent adding new, vital features.

In any case, I decided I needed more code coverage, because I hate it when I fix a bug or add some feature, only to find that it has been broken in some obscure way months later. Having a large suite of tests to run won’t always protect against this kind of thing, but it does help some.

Most recently, I’ve been writing a new, fairly complicated feature. It’s nearing completion. I’ve been writing unit-like tests all along, but I decided in the end that it would be worth writing some integration-like tests that run against a working dev server. The second integration test that I wrote kept failing – immediately. It looked okay. I kept debugging it until I was nearly sure that it should pass. So I went back and looked at my server code. Voilà! My server code was at fault. Nerds! To be honest, I rarely find that tests so quickly uncover a bug. But it happens often enough, and it gives me a warm fuzzy feeling about testing!

coursera algorithms part i

| Comments

Summary: The coursera course Algorithms Part I is worth it, with some caveats

My educational background is in physics and math, not computer science. I’ve taken exactly one computer course (Pascal) as an undegraduate. Aside from that, the programming knowledge that I’ve got was all self-taught: from the Basic programs that I wrote as a teenager on my father’s Commodore 64, to the Fortran code that I wrote to study physical systems in grad school, it was all stuff I had learned on my own or from building on the code of others.

When my career took a left turn, and I started developing for web applications, my background served me well. However, I had the feeling that I was missing some things that I would have learned if I’d gone into a computer science degree program. This is hammered in when you go for job interviews where they ask you abstruse computer science questions.

Even so, it’s been hard to make myself study something like algorithms – there’s just so much time in life, and there always seem to be more urgent things to do… But then I noticed the Coursera course in algorithms, and finally decided to take a swing at it. This is Algorithms, Part I. The promotional material reads: “This course covers the essential information that every serious programmer needs to know about algorithms and data structures.” To top it off, it’s a Princeton University course – at no charge! What more could you ask for? And they tell you that the course requires “6-12 hours of work / week” for 6 weeks. I can certainly afford that! It seems time well spent.

I didn’t have much time during the month of June, but I figured I could catch up. It has not been easy! Each week covers 2 topics. Week 1 covers the “Union-Find” algorithm, and also “Analysis of Algorithms”. I just finished the first programming assignment for “Union-Find”. It didn’t take long for me to get running code which did what the spec wanted – perhaps 3 or 4 hours. But that’s not enough. When you submit your code, it’s run through an automated grading system. Little bugs which seemingly shouldn’t count for much can result in nearly complete failure.

In my case, I uploaded the assignment 5 different times, each time resulting in an improvement until it passed with 100% the very last time. The first couple times I had to debug a few items because I had misunderstood the spec for a couple of API methods. In one particularly nasty bug, I had somehow managed to use the wrong version of their algorithm class (QuickFindUF instead of WeightedQuickUnionUF) in my code – oops! That turned out to be very difficult and time-consuming to discover. The automated grading system gives you hints about what is going wrong, but in some cases it just appears to be annoyingly cryptic.

Fortunately, there’s a tipsheet that goes with each assignment. And there are also discussion forums for when you get really desperate. With all this help, it still took me at least 10 hours to finish just this initial programming assignment, and I haven’t even touched the next one yet. I hope things go faster from here on out, but I’m skeptical about that.

code coverage for node and vows

| Comments

Summary: you can get code coverage stats for your node.js code

(FYI my dev box uses Ubuntu Linux 13.10.)

I’ve been developing new features in my node.js application, and concurrently writing vows.js tests for the new code. Yesterday, I decided that it was about time that I check to see just how much code my tests are covering.

What to do? When developing Java software with the eclipse IDE, it’s relatively simple to get code coverage information for your JUnit tests. With node, things are more complicated.

I looked at code coverage in npm and saw a number of potentially useful libraries, but didn’t want to research each one to see if they worked with vows. Then I noticed that the vows page says “Code coverage reporting is available if instrumented code is detected. Currently only instrumentation via node-jscoverage is supported.” Well, that makes it a no-brainer. They give you instructions for downloading and installing node-jscoverage, which is not in npm!

While I was hunting around, I had found Alex Seville’s post about some problems he had encountered using node-jscoverage. That made me concerned that the instructions on the vows page were incomplete. So I followed his directions. In his step 2, I ran into a little permission trouble; I had to run each command as sudoer:

1
2
3
sudo ./configure
sudo make
sudo make install

From then on, almost everything worked. Here’s my node.js app code:

1
2
3
/srv/app/node-app
/srv/app/node-tests
/srv/app/node_modules

And here’s how I produced the instrumented code to test:

1
jscoverage /srv/app/node-app /home/mdoery/instrumented/node-app

Notice I put my instrumented code into a directory which is completely independent from my app directory structure. You don’t want instrumented code running in your app! It’s only used for generating coverage reports.

I followed Alex’s link to Jeff Kunkle’s post about switching between testing instrumented and non-instrumented code. I grabbed Jeff’s sample code to do this, and it worked like a charm. Then I ran a test –

1
2
3
4
5
6
7
8
9
10
11
12
/srv/app/node-tests$ ../node_modules/vows/bin/vows test-something.js
ERROR   => Error: Cannot find module 'moment'
  at Function.Module._resolveFilename (module.js:338:15)
    at Function.Module._load (module.js:280:25)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at exports.undefined.window.C (/home/mdoery/instrumented/node-app/C.js:208:22)
    at Object.<anonymous> (/home/mdoery/instrumented/node-app/C.js:607:3)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)

Oops! The instrumented code couldn’t find my app’s libraries (such as moment) in /srv/app/node_modules. So I created a soft link to these in my instrumented directory:

1
ln -s /srv/app/node_modules /home/mdoery/instrumented/node_modules

After doing that, my instrumented code appeared to run fine; I got the usual happy output from vows:

1
2
3
/srv/app/node-tests$ ../node_modules/vows/bin/vows test-something.js
·············
  ✓ OK » 13 honored (0.004s)

But how could I be sure that the instrumented code was being exercised? This time, I reran my test with the --cover-html option:

1
../node_modules/vows/bin/vows test-something.js --cover-html

Sure enough, I saw a file called coverage.html had been generated in my current directory. I opened it in my browser window to view an html display of what code is covered, and what still needs to be tested. Nice!

android example using GridLayout

| Comments

Summary: I demonstrate building a simple app which uses android.support.v7.widget.GridLayout

I’m trying out a GridLayout for one of my projects. I built a small app to test it. The app looks like this on my tablet:

Here’s how to do it.

First, create an Android project in Eclipse (File > New > Project… > Android Application Project). In setting up my project I used a Minimum Required SDK of 2.2 (Froyo) and Target SDK 4.2 (Jelly Bean), and otherwise kept all the default settings proposed by Eclipse.

Next, import five image files into the res/drawable folder. These will be displayed in the grid. You can grab these images from this page (right-click on the image and “Save as…”). They are all in the public domain.

Open activity_main.xml in “Graphical Layout” view, click on “Layouts” and drag a “GridLayout” onto the view. A dialog opens which says “Warning android.widget.GridLayout requires API level 14 or higher, or a compatibility library for older versions. Do you want to install the compatibility library?” – do that. Set “Use Default Margins” to true, and set the “Column Count” to 2 (these two items are found under the Properties panel):

When I did this last step, something weird happened. I saw a couple of errors in the xml:

Unexpected namespace prefix “app” found for tag android.support.v7.widget.GridLayout. The activity_main.xml had the property app:columnCount=“2” – I hadn’t added this manually. I hunted around the internet and found a bug report about it, including a workaround – adding the attribute tools:ignore=“MissingPrefix” to the RelativeLayout tag gets rid of the error.

Finally, set the Android id for the GridLayout to be android:id=“@+id/gridLayout1”

The rest is simple; add all the images programmatically to the GridLayout, and it’s done. This is all the code that’s in my MainActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.mdoery.tut.gridlayout;

import android.os.Bundle;
import android.app.Activity;
import android.support.v7.widget.GridLayout;
import android.widget.ImageView;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      GridLayout gl = (GridLayout)findViewById(R.id.gridLayout1);
      setupGridLayout(gl);
  }

  private void setupGridLayout(GridLayout gl) {
      gl.addView(getImageView(R.drawable.apple));
      gl.addView(getImageView(R.drawable.ladybeetle));
      gl.addView(getImageView(R.drawable.moon));
      gl.addView(getImageView(R.drawable.sheep));
      gl.addView(getImageView(R.drawable.strawberry));
      
  }
  private ImageView getImageView(int resource) {
      ImageView iv = new ImageView(this);
      iv.setBackgroundResource(resource);
      return iv;
  }

}

where did my GridLayout go?

| Comments

Summary: The class android.support.v7.widget.GridLayout vanished from my Eclipse project! Here’s how I retrieved it.

I was working on an Android project in Eclipse a while back, when I got pulled off of it to do some contract work. When I finally returned to the project today, I was surprised to see it had this compiler error: “The import android.support.v7.widget cannot be resolved”.

Periodically I’m dumbfounded by things which happen in Eclipse, but I’ve begun to take surpises in a stride. I searched around the internet for posts which reported this problem and eventually found that the solution is to import a “support” library which is found in the Android SDK directory. For me, that jar file is located here: C:\Program Files\Android\android-sdk\extras\android\support\v7\gridlayout\libs\android-support-v7-gridlayout.jar

This is a quick rundown of the simple procedure I followed to solve the problem:

  1. Open Eclipse’s Navigator view (from the menu up top, select Window > Show view > Navigator)
  2. Right-click on the project’s libs directory
  3. From the menu which opens, choose “Import…” and then choose “File System”.
  4. In the “File System” dialog which opens, navigate to wherever your android-support-v7-gridlayout.jar has been stored. Like I said above, for me it was under C:\Program Files\Android\android-sdk\extras\android\support\v7\gridlayout\libs\android-support-v7-gridlayout.jar where presumably the Android SDK Manager placed it. Select the jar file, and click the Finish button.
  5. Tada! The compiler error goes away.

I don’t understand how this project broke while I wasn’t working on it. I can only imagine that the imported library was somehow deleted, but I don’t recall doing that, so it’s another one of those fun Android mysteries!

P.S. This evening, I discovered that I had added the jar file to a separate, Android library Eclipse project and had set that project as an Android reference for my current project (under Properties > Android). The library project had been closed some time between now and then. So that explains why the file was “missing;” it was never there to begin with!

working with json in android apps

| Comments

Summary: Extracting data from a JSON formatted file is pretty simple in Android.

I’m planning to store data in the JSON format for the app that I’m currently building. So here’s a quick post which demonstrates the procedure for reading JSON into data which can be used in your app.

  1. Create an Android application project called MainJson in Eclipse.
  2. Set the Minimum Required SDK property to API 8: Android 2.2 (Froyo), and the Target SDK to API 17: Android 4.2 (Jelly Bean). I tested this tutorial with these parameters.
  3. Set the package name to be com.mdoery.tut.json.
  4. Start the project with a blank Activity.
  5. Create a sub-directory called json under the assets folder; we’ll store our JSON file here.

Now that the project is ready, create a file with JSON-formatted data as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{"sculptors": [
    {"sculptor": {
        "firstname": "Isamu",
        "lastname": "Noguchi",
        "sculptures": [
            {"sculpture": {"name": "Red Cube", "date": "1968"}},
            {"sculpture": {"name": "Black Sun", "date": "1969"}},
            {"sculpture": {"name": "Sky Gate", "date": "1977"}}
        ]
    }},
    {"sculptor": {
      "firstname": "David",
      "lastname": "Smith",
      "sculptures": [
        {"sculpture": {"name": "Medals for Dishonor", "date": "1937-40"}},
        {"sculpture": {"name": "Agricola I", "date": "1952"}},
        {"sculpture": {"name": "CUBI VI", "date": "1963"}}
      ]
    }},
    {"sculptor": {
      "firstname": "Rachel",
      "lastname": "Whiteread",
      "sculptures": [ {"sculpture": {"name": "House", "date": "1993"}} ]
    }}
  ]
}

Call the file sculptors.json, and add it to the assets/json folder.

Finally, open the MainActivity java class, which was created by default, and edit it to contain the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.mdoery.tut.json;

import java.io.IOException;
import java.io.InputStream;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.text.Html;
import android.text.Spanned;
import android.widget.TextView;

public class MainActivity extends Activity {

  private static String sep = System.getProperty("line.separator");
  private static String br = "<br>";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView tv = (TextView)this.findViewById(R.id.textView1);
    try {
      // Read contents of file sculptors.json 
      String json = getText("json/sculptors.json",
        getApplicationContext()) + sep;
      // Display it in TextView
      displaySculptorNames(tv, json);
    } catch (Exception e) {
      // If there is an exception, show it in the TextView
      tv.setText("Exception: " + sep + e + sep);
    }
  }

  private String getText(String filename, Context ctx)
    throws IOException {
    InputStream is = ctx.getAssets().open(filename);
    // We guarantee that the available method returns 
    // the total size of the asset not more than 2G.
    int size = is.available();
    // Read the entire asset into a local byte buffer.
    byte[] buffer = new byte[size];
    is.read(buffer);
    is.close();
    String text = new String(buffer);
    return text;
  }

  /**
   * Extracts information from JSON text, and displays it in TextView 
   * @param tv TextView
   * @param json String of json from file sculptors.json
   * @throws JSONException
   */
  private void displaySculptorNames(TextView tv, String json)
    throws JSONException {
    String text = "";
    JSONTokener tokener = new JSONTokener(json);
    JSONObject object = (JSONObject) tokener.nextValue();
    JSONArray sculptors = object.getJSONArray("sculptors");
    for (int ii = 0; ii < sculptors.length(); ii++) {
      text += (ii + 1) + ") ";
      JSONObject obj = sculptors.getJSONObject(ii);
      JSONObject sculptor = obj.getJSONObject("sculptor");
      String firstname = sculptor.getString("firstname");
      String lastname = sculptor.getString("lastname");
      JSONObject sculpture = sculptor.getJSONArray("sculptures")
        .getJSONObject(0).getJSONObject("sculpture");
      String name = "<i>" + sculpture.get("name") + "</i>";
      text += firstname + " " + lastname + " sculpted " + name;
      String date = sculpture.getString("date");
      try {
        Integer.parseInt(date);
        text += " in " + date;
      } catch (NumberFormatException e) { // catch date range
        text += " during " + date;
      }
      text += br;
    }
    Spanned span = Html.fromHtml(text);
    tv.setText(span);
  }
}

If you run this in the emulator, you should see a screen with text created from the information extracted from the JSON file, like this:

As an aside, notice that some of the text is formatted in italics. That was done using Html.fromHtml in the code. I found out how to do that at Stealthcopter.com.

BTW, here’s a handy tip if you want to try this project with your own JSON: validate JSON before trying to read it in your app. It may save you some trouble! I used jsonlint to do so.