0

I am desperatly trying to fix a bug that:

  • always happens in my emulator for Android versions 2.2, 2.3
  • never happens in emulator android versions 4.*
  • never happens in a real device (android 4.*)

It is the following IndexOutOfBoundsException exception:

java.lang.RuntimeException: Unable to start activity
ComponentInfo{<myapppackage>}: java.lang.IndexOutOfBoundsException:
Invalid index 39, size is 0

In my app I am fecthing data from a json file that I am displaying as text. I've isoleted where the bug is coming from, it is when I call this method:

public String getItemValue(int id, String s) {      

    List<JsonItems> list = new ArrayList<JsonItems>();

    try {
        // CONVERT RESPONSE STRING TO JSON ARRAY
        JSONArray ja = new JSONArray(s);

        // ITERATE THROUGH AND RETRIEVE 
        int n = ja.length();            

        for (int i = 0; i < n; i++) {
            // GET INDIVIDUAL JSON OBJECT FROM JSON ARRAY
            JSONObject jo = ja.getJSONObject(i);

            // RETRIEVE EACH JSON OBJECT'S FIELDS
            JsonItems ji = new JsonItems();
            ji.id = jo.getInt("id");
            ji.text= jo.getString("text");
            list.add(ji);

        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return list.get(id).text; 
}

My class JsonItems is very basic:

public class JsonItems{
int id;
String text;
}

Sample from my json file:

[
{"id":0,"text":"some text 0"},
{"id":1,"text":"some text 1"},
{"id":2,"text":"some text 2"}
]

Here is how I process content of my json file into a String

public static String fromJsonFileToString(String fileName, Context c) {
    //JSONArray jArray = null;
    String text = "";
    try {
        InputStream is = c.getAssets().open(fileName);
        int size = is.available();
        byte[] buffer = new byte[size];
        is.read(buffer);
        is.close();
        text = new String(buffer);

    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    return text;

}

Once again I repeat: the IndexOutOfBoundsException NEVER happens on a device with Android 4.* , it only happens when I test the app on emulators with Android 2.*

Any idea where it is coming from?

Thanks

Don
  • 977
  • 3
  • 10
  • 28
  • have you replace this for (int i = 0; i < n; i++) into for (int i = 0; i <= n; i++) i think it will be work – Android_coder Mar 11 '13 at 13:02
  • You are not validating that your `id` argument is actually less than the size of the computed list. Bad. Thats the direct cause of the exception. The indirect cause has something to do with the way your JSON Array is being constructed. – Perception Mar 11 '13 at 13:04
  • Besides the obvious fact that your code is not robust in the sense that it handles problems gracefully nowhere (besides the explicitly treated list length of course), the question is, _are you sure that the jo objects look always the same_. In other words, how do you fill the JSON array. By the way, it would be nice if you could point out which code line the exception refers to. – class stacker Mar 11 '13 at 13:04
  • @Kintaro can you post a sample json that causes the problem ? – Moh Sakkijha Mar 11 '13 at 13:08
  • thanks all for your comments, I have edited my question with a sample of my json file. The content of my Json file is static, i doubt there is a bug in its content – Don Mar 11 '13 at 13:16
  • @ClassStacker: I have edited my question, I have added my method to process my json data into a String. It is very possible my code is not robust enough, could you please point me towards the good way to make it better? – Don Mar 11 '13 at 13:22
  • @Perception: how would I validate that? – Don Mar 11 '13 at 13:22
  • @Kintaro - If you continue using a list then you can do something along the lines of `return id < list.size() ? list.get(id).text : null;`, but I am thinking you are trying to return the value that has the ***same*** id from your JSON, in which case you should probably be storing results in a Map, not a List. – Perception Mar 11 '13 at 13:26
  • It's a bad idea to use `available()`. It could be the cause of your problem. It's not necessary and it could very well yield 0 the first time you call it, you never know. You should read in a loop until an EOF condition is encountered. – class stacker Mar 11 '13 at 13:26
  • @ClassStacker: reading in a loop until EOF is encountered sounds good, how can I detect the EOF? Some code would be very helpful if you can write any? – Don Mar 11 '13 at 13:36
  • @Perception: yes I want to return the value that has the same id in my json. I am really not familiar with Map, would you have an example of code that would replace how I am doing? That would be very helpful. – Don Mar 11 '13 at 13:39
  • Come on @Kintaro that's a standard Java task and probably even mentioned on the Java tutorial. – class stacker Mar 11 '13 at 13:42
  • Have you tried debugging it ? Halt execution at `for` loop and have a look at what values the variable `ja` holds. – S.D. Mar 11 '13 at 13:58

1 Answers1

2

First problem:

You are not reading your input stream correctly. Calling available literally does just that - it returns you the amount of data that is available to be read, when the call is made. This number may, or may not represent the entire content of the file.

Reading material for you:

Note that there are helper libraries like Apache Commons IO that make it possible to read file contents in a single line of code (IOUtils.toString(inputStream)). Android doesn't support Java 7 yet but a noteworthy alternative is available in that release, with the Files.readAllLines method. In any case, you can make the below shown changes to your file reading code and it should work better:

public static String fromFileToString(String fileName, Context context) {
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                context.getAssets().open(fileName)));

        String line = null;
        StringBuilder builder = new StringBuilder(1024);
        while ((line = reader.readLine()) != null) {
            builder.append(line);
        }

        return builder.toString();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

Second problem:

You do not do any bound check, to make sure that the argument you pass into your 'search' method:

public String getItemValue(int id, String s)

Does not exceed the length of the list of items you eventually calculate:

return list.get(id).text;
//              ^
// -------------
// 'id' could be larger than the list size!

In any case, your current design doesn't at all match what you are really trying to do, aka, to determine the element in the JSON array that has an 'id' field matching what you supply to the method. You need to process the JSON data as a map, in order to be able to do this.

public String getItemValue(int id, String json) {
    if(json == null || json.trim().equals("")) return null;

    Map<Integer, JsonItems> map = new HashMap<Integer, JsonItems>(4);

    try {
        JSONArray ja = new JSONArray(json);

        int n = ja.length();

        for (int i = 0; i < n; i++) {
            JSONObject jo = ja.getJSONObject(i);

            JsonItems ji = new JsonItems();
            ji.id = jo.getInt("id");
            ji.text = jo.getString("text");
            map.put(Integer.valueOf(ji.id, ji);
        }
    } catch(NumberFormatException nfe) {
      throw new IllegalArgumentException("Invalid JSON format");  
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    
    JsonItems item = map.get(id);
    return item != null ? item.text : null;
}

Some quick notes:

  • JsonItems should be called JsonItem, to conform to good Java naming standards
  • You should really parse and store your JSON just once, to improve performance
  • You are really only using a minimal subset of your JSON data, you could actually determine the matching node within your for loop and directly return its value, without having to use an intermedia Java bean object
Community
  • 1
  • 1
Perception
  • 79,279
  • 19
  • 185
  • 195
  • ...ooops, are you getting paid for this or what? ;) Question is, should I upvote because of this perfectly fitting collection of material, or should I downvote because you pamper the OP? ;) – class stacker Mar 11 '13 at 15:43
  • Don't worry, I left some homework in there for him. He actually has quite a bit of work left to do on this. – Perception Mar 11 '13 at 15:48
  • That is really helpful, thanks a lot. As I have integrated your solution into my code, I am facing the following log error: "Value of type java.lang.String cannot be converted to JSONArray" Do you know where it could come from? I think my json content is fine: [ {"id":0,"text":"some text 0"}, {"id":1,"text":"some text 1"}, {"id":2,"text":"some text 2"} ] – Don Mar 11 '13 at 16:08
  • @Perception: Just FYI, I found where the bug came from: for some obscure reason, on Android 2.* I need to remove the first char like in the following http://stackoverflow.com/questions/11743422/string-cannot-be-converted-to-jsonarray On Android 4.0, this does not result in a bug... Weird But thanks for your time and your suggestions to improve my code – Don Mar 11 '13 at 18:10
  • @Kintaro - no problem, good luck with the rest of the project! – Perception Mar 11 '13 at 20:36