0

I have an ArrayList with custom json objects fetched from the web with Volley. I would like to be able to save and restore these objects on a screen rotate. I would also like to save and restore my current scrolled position on screen rotate.

I have a sketchy idea that this can be done with onSaveInstanceState and onRestoreInstanceState?

Activity Code

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";



    //Creating a list of posts
    private List<PostItems> mPostItemsList;

    //Creating Views
    private RecyclerView recyclerView;
    private RecyclerView.Adapter adapter;
    private RecyclerView.LayoutManager layoutManager;
    private ProgressDialog mProgressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "Device rotated and onCreate called");

        //Initializing Views
        recyclerView = (RecyclerView) findViewById(R.id.post_recycler);
        layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);


        //Initializing the postlist
        mPostItemsList = new ArrayList<>();
        adapter = new PostAdapter(mPostItemsList, this);

        recyclerView.setAdapter(adapter);

        if (NetworkCheck.isAvailableAndConnected(this)) {
            //Caling method to get data
            getData();
        } else {
            final Context mContext;
            mContext = this;
            final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
            alertDialogBuilder.setTitle(R.string.alert_titl);
            alertDialogBuilder.setMessage(R.string.alert_mess);
            alertDialogBuilder.setPositiveButton(R.string.alert_posi, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    if (!NetworkCheck.isAvailableAndConnected(mContext)) {
                        alertDialogBuilder.show();
                    } else {
                        getData();
                    }


                }
            });
            alertDialogBuilder.setNegativeButton(R.string.alert_nega, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    finish();

                }
            });
            alertDialogBuilder.show();

        }

    }

    //This method will get data from the web api
    private void getData(){


        Log.d(TAG, "getData called");
        //Showing progress dialog
        mProgressDialog = new ProgressDialog(MainActivity.this);
        mProgressDialog.setCancelable(false);
        mProgressDialog.setMessage(this.getResources().getString(R.string.load_post));
        mProgressDialog.show();

        //Creating a json request
        JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(ConfigPost.GET_URL,
                new Response.Listener<JSONArray>() {
                    @Override
                    public void onResponse(JSONArray response) {
                        Log.d(TAG, "onResponse called");
                        //Dismissing the progress dialog
                        if (mProgressDialog != null) {
                            mProgressDialog.hide();
                        }
                        /*progressDialog.dismiss();*/


                        //calling method to parse json array
                        parseData(response);

                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
                });

        //Creating request queue
        RequestQueue requestQueue = Volley.newRequestQueue(this);

        //Adding request to the queue
        requestQueue.add(jsonArrayRequest);
    }

    //This method will parse json data
    private void parseData(JSONArray array){
        Log.d(TAG, "Parsing array");

        for(int i = 0; i<array.length(); i++) {
            PostItems postItem = new PostItems();
            JSONObject jsonObject = null;
            try {
                jsonObject = array.getJSONObject(i);
                postItem.setPost_title(jsonObject.getString(ConfigPost.TAG_POST_TITLE));
                postItem.setPost_body(jsonObject.getString(ConfigPost.TAG_POST_BODY));

 } catch (JSONException w) {
                w.printStackTrace();
            }
            mPostItemsList.add(postItem);
        }

    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy called");
        if (mProgressDialog != null){
            mProgressDialog.dismiss();
            Log.d(TAG, "mProgress dialog dismissed");

        }
    }

Thanks in advance.

Note a duplicate of How to save custom ArrayList on Android screen rotate?. While the arraylist in that question is declared in the Activity, mine is fetched with volley from the web. I don't know how to implement it for my arraylist, else this question wouldn't be asked

Community
  • 1
  • 1
Faraday
  • 327
  • 1
  • 6
  • 19
  • I have seen that question before I asked this. While the arraylist in that question is declared in the Activity, mine is fetched with volley from the web. I don't know how to implement it for my arraylist, else this question wouldn't be asked. – Faraday Apr 03 '16 at 21:26
  • 1
    No, It's the same question. It doesn't matter where you get the data. I think what you're trying to ask is "what happens when request is not completed and user rotate screens?" In that case request is stuck with previous activity context, and it will deliver it to the destroyed activity, and you'll get an exception. Don't leak the activity context. – user3623735 Apr 03 '16 at 21:31
  • Yes, it's the same question but how should I implement it for data fetched from the web. – Faraday Apr 03 '16 at 21:41

2 Answers2

1

This is, in fact, a duplicate of the post you mentioned. Yes, the list was declared in the activity's onCreate() in that post, whereas you are doing it asynchronously. However, the idea is the same.

Once you have data to send, at any point of your application, it can be saved and restored.

The key, in your case, is to not call getData() every time the device is rotated. If you already have data loaded in mPostItemsList, then save and restore it via onSaveInstanceState(), and in onCreate() you get the data from the saved state. If that data does not exist, then you call getData().

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";
    private final String KEY_POST_ITEMS = "#postitems";

    //Creating a list of posts
    private List<PostItems> mPostItemsList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initializeViews();

        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_POST_ITEMS)){
            mPostItemsList = savedInstanceState.getParcelableArrayList(KEY_POST_ITEMS);
        } else {
            //Initializing the postlist
            mPostItemsList = new ArrayList<>();

            if (NetworkCheck.isAvailableAndConnected(this)) {
                //Caling method to get data
                getData();
            } else {
                showNoNetworkDialog();
            }
        }

        mAdapter = new PostAdapter(mPostItemsList, this);
        recyclerView.setAdapter(adapter);

    }

    private void parseData(JSONArray array){
        mPostItemsList.clear();

        for(int i = 0; i<array.length(); i++) {
            PostItems postItem = new PostItems();
            JSONObject jsonObject = null;
            try {
                jsonObject = array.getJSONObject(i);
                postItem.setPost_title(jsonObject.getString(ConfigPost.TAG_POST_TITLE));
                postItem.setPost_body(jsonObject.getString(ConfigPost.TAG_POST_BODY));
            } catch (JSONException w) {
                w.printStackTrace();
            }

            mPostItemsList.add(postItem);
        }

        mAdapter.notifyDataSetchanged();

    }

Edit: I didn't see the requirement to save scroll position. Look at Emin Ayar's answer for that. Also, a similar answer for it is also here: How to save recyclerview scroll position.

Community
  • 1
  • 1
James Davis
  • 474
  • 4
  • 10
  • From line `mPostItems = savedInstanceState.getParcelableArrayList(KEY_POST_ITEMS);` I don't have a variable `mPostItems`.Or am I getting it wrong? – Faraday Apr 03 '16 at 22:11
  • Sorry, this was a typo on my part. This is supposed to be `mPostItemsList`, not mPostItems – James Davis Apr 04 '16 at 23:45
  • Thanks James for the answer. The idea that it's `mPostItemsList` occurred to me and I actually tried it but Android Studio complained; I have forgotten the actual complain. I'm not with my PC now, could have checked. Just wanted to let you know. – Faraday Apr 05 '16 at 08:30
  • @Faraday did you ever figure this out? – James Davis Apr 20 '16 at 20:35
0

You can use onConfigurationChanged at your activity to detect rotation changes. Also you should track lastVisibleItemPosition with layoutManager.findLastVisibleItemPosition() and when rotation changed you should scroll to this position. You will need to use recyclerView.setOnScrollListener() to listen scrolls to keep your lastVisibleItemPosition updated

public class MainActivity extends AppCompatActivity {

private final String TAG = "MainActivity";

//Creating and initializing list of posts
private List<PostItems> mPostItemsList = new ArrayList<>();;

//Creating Views
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
private RecyclerView.LayoutManager layoutManager;
private ProgressDialog mProgressDialog;
private int lastVisibleItemPos = -1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.d(TAG, "Device rotated and onCreate called");

    if (NetworkCheck.isAvailableAndConnected(this)) {
        //Caling method to get data and check if postList have value set before or not
        // because this part will be called on every rotation change, we are controlling this
        if (mPostItemsList.size() <= 0) {
            //Initializing Views
            recyclerView = (RecyclerView) findViewById(R.id.post_recycler);
            layoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(layoutManager);

            adapter = new PostAdapter(mPostItemsList, this);

            recyclerView.setAdapter(adapter);
            getData();
        }

    } else {
        final Context mContext;
        mContext = this;
        final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
        alertDialogBuilder.setTitle(R.string.alert_titl);
        alertDialogBuilder.setMessage(R.string.alert_mess);
        alertDialogBuilder.setPositiveButton(R.string.alert_posi, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (!NetworkCheck.isAvailableAndConnected(mContext)) {
                    alertDialogBuilder.show();
                } else {
                    getData();
                }


            }
        });
        alertDialogBuilder.setNegativeButton(R.string.alert_nega, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                finish();

            }
        });
        alertDialogBuilder.show();

    }

}

//This method will get data from the web api
private void getData(){


    Log.d(TAG, "getData called");
    //Showing progress dialog
    mProgressDialog = new ProgressDialog(MainActivity.this);
    mProgressDialog.setCancelable(false);
    mProgressDialog.setMessage(this.getResources().getString(R.string.load_post));
    mProgressDialog.show();

    //Creating a json request
    JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(ConfigPost.GET_URL,
            new Response.Listener<JSONArray>() {
                @Override
                public void onResponse(JSONArray response) {
                    Log.d(TAG, "onResponse called");
                    //Dismissing the progress dialog
                    if (mProgressDialog != null) {
                        mProgressDialog.hide();
                    }
                    /*progressDialog.dismiss();*/


                    //calling method to parse json array
                    parseData(response);

                }
            },
            new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {

                }
            });

    //Creating request queue
    RequestQueue requestQueue = Volley.newRequestQueue(this);

    //Adding request to the queue
    requestQueue.add(jsonArrayRequest);
}

//This method will parse json data
private void parseData(JSONArray array){
    Log.d(TAG, "Parsing array");

    for(int i = 0; i<array.length(); i++) {
        PostItems postItem = new PostItems();
        JSONObject jsonObject = null;
        try {
            jsonObject = array.getJSONObject(i);
            postItem.setPost_title(jsonObject.getString(ConfigPost.TAG_POST_TITLE));
            postItem.setPost_body(jsonObject.getString(ConfigPost.TAG_POST_BODY));

        } catch (JSONException w) {
            w.printStackTrace();
        }
        mPostItemsList.add(postItem);
    }

}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    // set your adapter here with your data
    adapter = new PostAdapter(mPostItemsList, this);
    recyclerView.setAdapter(adapter);
}

@Override
public void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy called");
    if (mProgressDialog != null){
        mProgressDialog.dismiss();
        Log.d(TAG, "mProgress dialog dismissed");

    }
}
Emin Ayar
  • 1,104
  • 9
  • 13
  • These `adapter = new PostAdapter(mPostItemsList, this); recyclerView.setAdapter(adapter);` was done twice in your answer, is that appropariate. And `getData` is still being called after rotation. – Faraday Apr 03 '16 at 22:57
  • You are definitely right. We do not need to call on 'configurationchanged 'after we save items. But still if you want to keep it to make some orientation specific works it will be no problem. But as a result you will not need to define 'adapter' twice in app and do not need to call 'getData' after rotation. – Emin Ayar Apr 03 '16 at 22:59
  • Ok but `getData` is still being called after rotation – Faraday Apr 03 '16 at 23:06
  • Are you sure you removed initializing arrayList at onCreate method. Because everytime you initialize and assign it to mPostList, the size of arraylist will be equal to 0 and app will call 'getData()' function again and again on every rotation – Emin Ayar Apr 03 '16 at 23:09
  • According to James, "The key, in your case, is to not call getData() every time the device is rotated. If you already have data loaded in mPostItemsList, then save and restore it via onSaveInstanceState(), and in onCreate() you get the data from the saved state. If that data does not exist, then you call getData()." But in his answer, from line `mPostItems = savedInstanceState.getParcelableArrayList(KEY_POST_ITEMS);` because `mPostItems` is not a declared variable, Android Studio is highlighting it in red. – Faraday Apr 03 '16 at 23:16
  • You just need to remove onConfigurationChanged part on my codes to make it work as your needs. By this way it will getDatas only once and then everytime rotation change it will getDatas from your mPostItemsList variable – Emin Ayar Apr 03 '16 at 23:27
  • I have removed it and the "loading posts" dialog still shows – Faraday Apr 03 '16 at 23:35
  • Lastly, please can you try to adding `android:configChanges="orientation|screenSize"` between your activity tags on your `manifest.xml` file – Emin Ayar Apr 04 '16 at 06:17