Just an FYI, this is my first time working with JSON, Volley, or ASyncTask.
For a coding challenge I have to download a JSON array from a url into an android application and display the list of items after doing some editing and filtering. I am connecting to the JSON file via Volley in Java. In my "OnResponse" method within my JSONArrayRequest I am taking the JSON items, converting them into java items, and then adding them to a Room SQLite database. This heavy task caused an ANR so I moved the conversion and transfer into the database to an ASyncTask. It is still causing an ANR. How is this possible if all heavy methods are occurring Asynchronously? Relevant code and explanations of code below:
This is my OnCreate method for my controller where I call the AsyncTask:
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects()
.penaltyLog()
.build());
repository = new Repository(getApplication());
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_list_screen);
itemRV = findViewById(R.id.itemListRecyclerView);
progressBar = findViewById(R.id.progressBar1);
RequestQueue queue1 = Volley.newRequestQueue(ItemListScreen.this);
queue = queue1;
final ItemAdapter itemAdapter = new ItemAdapter(this);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
RecyclerView recyclerView = findViewById(R.id.itemListRecyclerView);
recyclerView.setAdapter(itemAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
if(repository.getAllItems().isEmpty()) {
//moves the download of the json file offof the UI thread
startAsyncTask();
}
//fills GUI table with database data
items = repository.getAllItems();
itemAdapter.setItems(items);
}
PHOTO VERSION:
Below is the method that performs the JSONRequest and fills a Room SQLite database with the newly created items. This is called within the ASyncTask.
private void getData () throws InterruptedException {
//fills database with JSON objects
JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(Request.Method.GET, url, null, new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
for (int i = 0; i < response.length(); i++) {
try {
JSONObject responseObj = response.getJSONObject(i);
int id = responseObj.getInt("id");
int listId = responseObj.getInt("listId");
String name = responseObj.getString("name");
Item item = new Item(id, listId, name);
repository.insert(item);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(ItemListScreen.this, "Failed to get the data", Toast.LENGTH_SHORT).show();
}
});
//Thread.sleep(1000);
queue.add(jsonArrayRequest);
}
PHOTO VERSION:
This is my AsyncTask which is called in the OnCreate method of my controller. The PreExecute should turn on the progress bar, then the DoInBackground should make the data transfer, then the Post should occur after the DoInBackground is done and turn off the progress bar. The code should then go back to the OnCreate method and finish out the code below the ASyncTask call:
public void startAsyncTask(){
ItemsAsyncTask itemsAsyncTask = new ItemsAsyncTask(this);
//not sure what this integerparameter does but a youtube video said this was very important to have.
itemsAsyncTask.execute(10);
}
private static class ItemsAsyncTask extends AsyncTask<Integer, Integer, String>{
private WeakReference<ItemListScreen> reference;
ItemsAsyncTask(ItemListScreen activity){
reference = new WeakReference<ItemListScreen>(activity);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
ItemListScreen activity = reference.get();
if (activity == null || activity.isFinishing()){
return;
}
//This should begin the progress bar on the screen, which should continue throughout the doInBackground method.
activity.itemRV.setVisibility(View.INVISIBLE);
activity.progressBar.setVisibility(View.VISIBLE);
}
@Override
protected String doInBackground(Integer... integers) {
//this calls the getData method that performs the data transfer away from the UI thread.
try {
ItemListScreen activity = reference.get();
if (activity == null || activity.isFinishing()){
return "return";
}
activity.getData();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "return"; }
@Override
protected void onPostExecute(String s) {
//After the doInBackground method is complete, this should remove the progress bar
// and then continue on to the rest of the OnCreate method where the GUI table will be filled witht he database objects
super.onPostExecute(s);
ItemListScreen activity = reference.get();
if (activity == null || activity.isFinishing()){
return;
}
activity.itemRV.setVisibility(View.VISIBLE);
activity.progressBar.setVisibility(View.INVISIBLE);
}
}
PHOTO VERSION:
So, in summary, My volley request is causing an ANR even though it is within an ASyncTask. The download of the JSON into the database still occurs while the ANR is happening (I am viewing the database populate in Android Studio) and the android device is showing a pop up message saying "application is not responding", but the Pre and Post methods instantly blink on the screen when the oncreate first occurs and then are instantly gone again. So it's like the Post method is being called before the DoInBackground is complete or something. Does anyone know why this things are occurring?
Also, after the application does this big download the first time the application is opened, upon further openings the database is already full so the ANR doesnt occur. It only happens the first time the app is used and it has to perform the challenge requirement of downloading the JSON from url.
I tried ASync but the ANR still occurs
EDIT: I updated my code as a user suggested and now have one small error left to solve. Even though I am calling a weak reference of activity for the response, it is still not recognizing response. Any Idea why this is the case? Here is the code:
@Override
protected String doInBackground(Integer... integers) {
//this calls the getData method that performs the data transfer away from the UI thread.
try {
ItemListScreen activity = reference.get();
if (activity == null || activity.isFinishing()){
return "return";
}
for (int i = 0; i < activity.response.length(); i++) {
try {
JSONObject responseObj = activity.response.getJSONObject(i);
int id = responseObj.getInt("id");
int listId = responseObj.getInt("listId");
String name = responseObj.getString("name");
Item item = new Item(id, listId, name);
activity.repository.insert(item);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "return"; }
My volley request is causing an ANR even though it is within an ASyncTask.
Your request is in the AsyncTask but the response is in the UI thread. Volley runs requests on a worker thread and then delivered the response in the main thread, so your AsyncTask is pointless.
You need to run the code you have now in the response (inserting into the database) in the background.
So.
getData
should just launch the Volley request.