Android JsonReader is closed when attempting to refresh content

348 views Asked by At

Currently, I'm working on an app that shows contents of an JSON-based API in a listview. Fetching the data and using the adapter to fill the listview works fine the first time. But when I try to refresh the contents using Swipe to refresh, the first time I try to refresh I get an IllegalStateException Error:

W/System.err: java.lang.IllegalStateException: JsonReader is closed
W/System.err:     at android.util.JsonReader.peek(JsonReader.java:361)
W/System.err:     at android.util.JsonReader.expect(JsonReader.java:308)
W/System.err:     at android.util.JsonReader.beginArray(JsonReader.java:277)
W/System.err:     at com.cologne_international.cologneinternationalapp.notamActivity.readNotamArray(notamActivity.java:71)
W/System.err:     at com.cologne_international.cologneinternationalapp.notamActivity.readJsonStream(notamActivity.java:63)
W/System.err:     at com.cologne_international.cologneinternationalapp.notamActivity.updateNOTAMS(notamActivity.java:122)
W/System.err:     at com.cologne_international.cologneinternationalapp.notamActivity$1.onRefresh(notamActivity.java:48)
W/System.err:     at android.support.v4.widget.SwipeRefreshLayout$1.onAnimationEnd(SwipeRefreshLayout.java:187)
W/System.err:     at android.support.v4.widget.CircleImageView.onAnimationEnd(CircleImageView.java:106)
W/System.err:     at android.view.ViewGroup.finishAnimatingView(ViewGroup.java:6237)
W/System.err:     at android.view.View.draw(View.java:17129)
W/System.err:     at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
W/System.err:     at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
W/System.err:     at android.view.View.draw(View.java:17188)
W/System.err:     at android.view.View.updateDisplayListIfDirty(View.java:16167)
W/System.err:     at android.view.View.draw(View.java:16951)
W/System.err:     at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
W/System.err:     at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
W/System.err:     at android.view.View.updateDisplayListIfDirty(View.java:16162)
W/System.err:     at android.view.View.draw(View.java:16951)
W/System.err:     at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
W/System.err:     at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
W/System.err:     at android.view.View.updateDisplayListIfDirty(View.java:16162)
W/System.err:     at android.view.View.draw(View.java:16951)
W/System.err:     at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
W/System.err:     at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
W/System.err:     at android.view.View.updateDisplayListIfDirty(View.java:16162)
W/System.err:     at android.view.View.draw(View.java:16951)
W/System.err:     at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
W/System.err:     at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
W/System.err:     at android.view.View.updateDisplayListIfDirty(View.java:16162)
W/System.err:     at android.view.View.draw(View.java:16951)
W/System.err:     at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
W/System.err:     at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
W/System.err:     at android.view.View.updateDisplayListIfDirty(View.java:16162)
W/System.err:     at android.view.View.draw(View.java:16951)
W/System.err:     at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
W/System.err:     at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
W/System.err:     at android.view.View.draw(View.java:17188)
W/System.err:     at com.android.internal.policy.DecorView.draw(DecorView.java:753)
W/System.err:     at android.view.View.updateDisplayListIfDirty(View.java:16167)
W/System.err:     at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:648)
W/System.err:     at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:654)
W/System.err:     at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:762)
W/System.err:     at android.view.ViewRootImpl.draw(ViewRootImpl.java:2800)
W/System.err:     at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2608)
W/System.err:     at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2215)
W/System.err:     at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1254)
W/System.err:     at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6337)
W/System.err:     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:874)
W/System.err:     at android.view.Choreographer.doCallbacks(Choreographer.java:686)
W/System.err:     at android.view.Choreographer.doFrame(Choreographer.java:621)
W/System.err:     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:860)
W/System.err:     at android.os.Handler.handleCallback(Handler.java:751)
W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:95)
W/System.err:     at android.os.Looper.loop(Looper.java:154)
W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6119)
W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

However, when I try to reload for a second time, everything is working as it should.
notamActivity Class:

public class notamActivity extends AppCompatActivity {
    private static ListView lv;
    private static AdapterNotam adbNotam;
    private static SwipeRefreshLayout swipeContainer;
    private static JsonReader reader;
    private static Context context;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_notam);
        context = getApplicationContext();
        lv = (ListView) findViewById(R.id.list);
        try {
            adbNotam = new AdapterNotam(this, readJsonStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
        lv.setAdapter(adbNotam);
        swipeContainer = (SwipeRefreshLayout) findViewById(R.id.swiperefresh);
        swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                Log.i("Called onRefresh", "onRefresh called from SwipeRefreshLayout");
                notamActivity.updateNOTAMS();
            }
        });
    }

    public static List<Notam> readJsonStream() throws IOException {
        try {
            return readNotamArray(reader);
        } finally {
            reader.close();
        }
    }

    public static List<Notam> readNotamArray(JsonReader reader) throws IOException {
        List<Notam> notams = new ArrayList<Notam>();
        reader.beginArray();
        while (reader.hasNext()) {
            notams.add(readNotam(reader));
        }
        reader.endArray();
        return notams;
    }

    public static Notam readNotam(JsonReader reader) throws IOException {
        int id = 0;
        String datestart = null;
        String dateend = null;
        String text = null;
        reader.beginObject();
        while (reader.hasNext()) {
            String jname = reader.nextName();
            if (jname.equals("id")) {
                id = Integer.parseInt(reader.nextString());
            } else if (jname.equals("datestart")) {
                datestart = reader.nextString();
            } else if (jname.equals("dateend")) {
                dateend = reader.nextString();
            } else if (jname.equals("textde")) {
                text = reader.nextString();
            } else {
                reader.skipValue();
            }

        }
        reader.endObject();
        return new Notam(id, text, datestart, dateend);
    }
    public static void setReader(JsonReader pReader){ 
        reader = pReader; 
    }

    public static Context getContext(){
        return context;
    }
    public static void updateNOTAMS(){
        new downloadNOTAMTask().execute("http://cologne-international.com/notam/json.php");
        try {
            adbNotam = new AdapterNotam(getContext(), readJsonStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
        lv.setAdapter(adbNotam);
        swipeContainer.setRefreshing(false);
    }
}

downloadNOTAMTask Class:

class downloadNOTAMTask extends AsyncTask<String,Void,JsonReader>{

    protected JsonReader doInBackground(String... url){
        JsonReader reader = null;
        try {
            InputStream ls = new URL(url[0]).openStream();
            reader = new JsonReader(new InputStreamReader(ls, "UTF-8"));
        }catch(Exception e){
            e.printStackTrace();
        }
        return reader;
    }

    protected void onPostExecute(JsonReader result){
        notamActivity.setReader(result);
    }
}

Layout xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_event"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.cologne_international.cologneinternationalapp.eventActivity">



    <android.support.v4.widget.SwipeRefreshLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/swiperefresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bt_back">

        <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
         />

    </android.support.v4.widget.SwipeRefreshLayout>

    <Button
        android:text="@string/button_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:id="@+id/bt_back"
        android:layout_alignParentEnd="true"
        android:layout_alignParentStart="true"
        android:onClick="back"
        style="@android:style/Widget.Holo.Button" />
</RelativeLayout>
1

There are 1 answers

0
Dharmendra Pratap Singh On BEST ANSWER

There is a problem with the activity that you have published in the question:-

  • You shouldn't hold static reference to context or any view, it might end up with memory leak
  • When you are using AsynTask try to pass an interface to get the communication back like I did in the below activity.
  • Do the JSON parsing in the background as it might end up with the heavy processing and show you ANR.

However, when I try to reload for a second time, everything is working as it should.

It is happening because you are not doing things in synchronous manner. (ie. You are not waiting for AsynTask to get finished)

Below I have given you the reference to write more cleaner and less error prone code:-


notamActivity Class:

public class notamActivity extends AppCompatActivity {
    private ListView lv;
    private AdapterNotam adbNotam;
    private SwipeRefreshLayout swipeContainer;
    private Context context;
    private DownloadNOTAMTask downloadNOTAMTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_notam);
        context = getApplicationContext();
        lv = (ListView) findViewById(R.id.list);

        //to initially get the list of Notams
        updateNOTAMS();

        swipeContainer = (SwipeRefreshLayout) findViewById(R.id.swiperefresh);
        swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                Log.i("Called onRefresh", "onRefresh called from SwipeRefreshLayout");
                updateNOTAMS();
            }
        });
    }

    public void cancelTask(DownloadNOTAMTask downloadNOTAMTask) {
        if (downloadNOTAMTask != null && downloadNOTAMTask.getStatus() == AsyncTask.Status.RUNNING)
            downloadNOTAMTask.cancel(true);
    }

    //you should create a reference to cancel the task when the activity is closing
    public void updateNOTAMS() {
        cancelTask(downloadNOTAMTask);
        downloadNOTAMTask = new DownloadNOTAMTask(new GenericCallBack() {
            @Override
            public void success(List<Notam> notamList) {
                //you don't need to setup adapter again and again 
                //just use setDataSetChanged
                adbNotam = new AdapterNotam(notamActivity.this, notamList);
                lv.setAdapter(adbNotam);
                swipeContainer.setRefreshing(false);
            }

            @Override
            public void failure() {
                swipeContainer.setRefreshing(false);
            }
        }).execute("http://cologne-international.com/notam/json.php");

    }

    //interface help you to get callback from the downloadNOTAMTask
    interface GenericCallBack {
        void success(List<Notam> notamList);

        void failure();
    }
}

DownloadNOTAMTask Class:

public class DownloadNOTAMTask extends AsyncTask<String, Void, List<Notam>> {
    private final notamActivity.GenericCallBack callBack;

    public DownloadNOTAMTask(notamActivity.GenericCallBack genericInterface) {
        this.callBack = genericInterface;
    }

    protected List<Notam> doInBackground(String... url) {
        JsonReader reader = null;
        List<Notam> notamList = null;
        try {

            InputStream ls = new URL(url[0]).openStream();
            reader = new JsonReader(new InputStreamReader(ls, "UTF-8"));
            notamList = readNotamArray(reader);
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return notamList;
    }


    @Override
    protected void onPostExecute(List<Notam> notams) {
        if (callBack != null) {
            if (notams != null)
                callBack.success(notams);
            else
                callBack.failure();
        }
    }

    private List<Notam> readNotamArray(JsonReader reader) throws IOException {
        List<Notam> notams = new ArrayList<>();
        reader.beginArray();
        while (reader.hasNext()) {
            notams.add(readNotam(reader));
        }
        reader.endArray();
        return notams;
    }

    private Notam readNotam(JsonReader reader) throws IOException {
        int id = 0;
        String datestart = null;
        String dateend = null;
        String text = null;
        reader.beginObject();
        while (reader.hasNext()) {
            String jname = reader.nextName();
            switch (jname) {
                case "id":
                    id = Integer.parseInt(reader.nextString());
                    break;
                case "datestart":
                    datestart = reader.nextString();
                    break;
                case "dateend":
                    dateend = reader.nextString();
                    break;
                case "textde":
                    text = reader.nextString();
                    break;
                default:
                    reader.skipValue();
                    break;
            }

        }
        reader.endObject();
        return new Notam(id, text, datestart, dateend);
    }
}

Layout xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_event"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.cologne_international.cologneinternationalapp.eventActivity">



    <android.support.v4.widget.SwipeRefreshLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/swiperefresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bt_back">

        <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
         />

    </android.support.v4.widget.SwipeRefreshLayout>

    <Button
        android:text="@string/button_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:id="@+id/bt_back"
        android:layout_alignParentEnd="true"
        android:layout_alignParentStart="true"
        android:onClick="back"
        style="@android:style/Widget.Holo.Button" />
</RelativeLayout>