Bruno Freeman Bruno Freeman - 1 year ago 94
Android Question

Android - Call to a method with AsyncTask disrupts other methods

I have a menu with two options. One of the options refreshes the content of the app with web content. I want to dim the ListView that's on the screen and then display a progress bar until the content refreshes. I have methods

startLoadingAnimation()
and
endLoadingAnimation()
that accomplish this. But when I call
refreshArticles()
in between them, they stop working. I have print statements in them that show they execute when they should, but when

case R.id.refresh:
startLoadingAnimation();
refreshArticles();
endLoadingAnimation();


is executed, only the print statements come through, not the UI changes.

Full activity code:

package com.bruno.newsreader;

import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.NumberPicker;
import android.widget.ProgressBar;

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

import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

public class ListActivity extends AppCompatActivity {

private ListView listView;
private int numArticles;
private SharedPreferences sharedPreferences;
private ArrayList<String> articleTitles;
private ArrayList<String> articleUrls;
private SQLiteDatabase articleDB;
private ProgressBar progressBar;

@Override
public boolean onCreateOptionsMenu(Menu menu) {

MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.options_menu, menu);

return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);

switch (item.getItemId()) {
case R.id.refresh:
startLoadingAnimation();
refreshArticles();
endLoadingAnimation();
return true;
case R.id.numArticles:
final NumberPicker numberPicker = new NumberPicker(getApplicationContext());
numberPicker.setMaxValue(50);
numberPicker.setMinValue(5);
numberPicker.setValue(numArticles);
new AlertDialog.Builder(this)
.setTitle("Set Number of Articles")
.setMessage("This will determine how many articles are stored on your device:")
.setView(numberPicker)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {

@Override
public void onClick(DialogInterface dialog, int which) {
numArticles = numberPicker.getValue();
sharedPreferences.edit().putInt("numArticles", numArticles).apply();
}
})
.setNegativeButton("Cancel", null)
.show();
default:
return false;

}
}

public class TopArticleDownloadTask extends AsyncTask<URL, Void, JSONArray> {

@Override
protected JSONArray doInBackground(URL... urls) {
String jsonString = "";
URL url = urls[0];
HttpURLConnection urlConnection = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
InputStream in = urlConnection.getInputStream();
InputStreamReader reader = new InputStreamReader(in);
int data = reader.read();
while(data != -1) {
char current = (char) data;
jsonString += current;
data = reader.read();
}
return new JSONArray(jsonString);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

public class IndividualArticleDownloadTask extends AsyncTask<URL, Void, JSONObject> {

@Override
protected JSONObject doInBackground(URL... urls) {
String jsonString = "";
URL url = urls[0];
HttpURLConnection urlConnection = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
InputStream in = urlConnection.getInputStream();
InputStreamReader reader = new InputStreamReader(in);
int data = reader.read();
while(data != -1) {
char current = (char) data;
jsonString += current;
data = reader.read();
}
return new JSONObject(jsonString);
} catch (Exception e) {

}
return null;
}
}

private void refreshArticles() {

String topArticlesUrlString = "https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty";
URL topArticlesUrl = null;
try {
topArticlesUrl = new URL(topArticlesUrlString);
} catch (MalformedURLException e) {
e.printStackTrace();
}
TopArticleDownloadTask topArticleDownloadTask = new TopArticleDownloadTask();
JSONArray topArticlesJSON = null;
try {
topArticlesJSON = topArticleDownloadTask.execute(topArticlesUrl).get();
} catch (Exception e) {
e.printStackTrace();
}
int[] articleIds = new int[numArticles];
for (int i = 0; i < articleIds.length; i++) {
try {
articleIds[i] = topArticlesJSON.getInt(i );
} catch (JSONException e) {
e.printStackTrace();
}
}

String articleUrlStart = " https://hacker-news.firebaseio.com/v0/item/";
String articleUrlEnd = ".json?print=pretty";
articleDB.execSQL("DELETE FROM articles");
for (int id : articleIds) {
String articleUrlString = articleUrlStart + id + articleUrlEnd;
URL articleUrl = null;
try {
articleUrl = new URL(articleUrlString);
} catch (MalformedURLException e) {
e.printStackTrace();
}
JSONObject articleJSON = null;
try {
IndividualArticleDownloadTask individualArticleDownloadTask = new IndividualArticleDownloadTask();
articleJSON = individualArticleDownloadTask.execute(articleUrl).get();
} catch (Exception e) {
e.printStackTrace();
}

try {
String title = articleJSON.getString("title");
title = title.replaceAll("'", "''");
String url = articleJSON.getString("url");
articleDB.execSQL("INSERT INTO articles (title, url) VALUES ('" + title + "', '" + url + "')");
} catch (Exception e) {
e.printStackTrace();
}
}

syncArrayListsWithDB();
}

private void syncArrayListsWithDB() {
articleTitles.clear();
articleUrls.clear();

Cursor cursor = articleDB.rawQuery("SELECT * FROM articles", null);

if (cursor.moveToFirst()) {
while(!cursor.isAfterLast()) {
String title = cursor.getString(0);
String url = cursor.getString(1);
articleTitles.add(title);
articleUrls.add(url);
cursor.moveToNext();
}
}

ArrayAdapter<String> arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, articleTitles);
listView.setAdapter(arrayAdapter);
}

private void startLoadingAnimation() {
System.out.println("start start load");
listView.setAlpha(.25f);
progressBar.setVisibility(View.VISIBLE);
System.out.println("end start load");
}

private void endLoadingAnimation() {
System.out.println("start end load");
listView.setAlpha(1);
progressBar.setVisibility(View.INVISIBLE);
System.out.println("end end load");
}

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

articleTitles = new ArrayList<>();
articleUrls = new ArrayList<>();
listView = (ListView) findViewById(R.id.listView);
progressBar = (ProgressBar) findViewById(R.id.progressBar);

sharedPreferences = this.getSharedPreferences("com.bruno.newsreader", Context.MODE_PRIVATE);
numArticles = sharedPreferences.getInt("numArticles", 10);

articleDB = this.openOrCreateDatabase("Data", MODE_PRIVATE, null);

articleDB.execSQL("CREATE TABLE IF NOT EXISTS articles (title VARCHAR, url VARCHAR)");

Cursor c = articleDB.rawQuery("SELECT count(*) FROM articles", null);
c.moveToFirst();
int count = c.getInt(0);
if (count == 0) {
refreshArticles();
} else {
syncArrayListsWithDB();
}

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(getApplicationContext(), ReaderActivity.class);
intent.putExtra("url", articleUrls.get(position));
startActivity(intent);
}
});
}
}

Answer Source

It's hard to tell exactly what refreshArticles() is actually doing, but I suspect that the answer is "lots of work", and that it's not doing it asynchronously (that is, it's just running along doing its work instead of spawning a new thread to do it and then calling back to you when it is done).

onOptionsItemSelected() is executed on the main thread (also called the UI thread). The UI can't be updated while anything is being done on the main thread. So if you're "starting" a loading animation, doing a lot of work, and then "stopping" that animation (all on the main thread), you won't ever actually see the animation run. By the time you're done running your code on the main thread, you've stopped your loading animation.

The solution is to move the work that refreshArticles() does to a background thread. There are lots of different ways to accomplish this; reading about Handler.post(Runnable) and AsyncTask should get you started.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download