I’m working on an Android app and I’ve got an Activity on which I am displaying two TableLayout’s on top of each other. The first TableLayout holds a scoreboard for a race (type 1) and the second the scoreboard for another race (type 2).
I inflate both of these TableLayout’s from the same XML layout file. I populate them with data from a doInBackground() (AsyncTask). However, the population part is pretty heavy, so I have put this into a custom synchronized method (inside the AsyncTask class) and pass the inflated TableLayout objects as an argument to this method.
I call said method from the doInBackground() method by using an ExecutorService object. This ExecutorService object is created with Executors.newSingleThreadExecutor(). I put the first method call in one Runnable and the second method call in another Runnable and fire off the ExecutorService.execute() on these runnables.
In the method I am accessing UI elements by calling findViewById() on the passed in TableLayout object parameter and calling methods like TextView.setText() on these views. This works fine most of the time, but I am starting to see a some exceptions like
ERROR/AndroidRuntime(409): FATAL EXCEPTION: pool-9-thread-1
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
and
ERROR/AndroidRuntime(378): FATAL EXCEPTION: pool-2-thread-1
java.lang.NullPointerException
which lets me to belive that I must be doing this incredibly backwards. I’ll include a simplified code snippet below to help describe what I am doing currently:
class AddRacesTask extends AsyncTask<Void, View, Void> {
synchronized void populateTable(ArrayList<Race> data, TableLayout table) {
TextView headerText = table.findViewById(R.id...);
headerText.setText("Header");
for(Race race : data) {
TableRow row = getLayoutInflater().inflate(R.layout....);
TextView pos = row.findViewById(R.id....);
pos.setText(race.getPos());
TextView person = row.findViewById(R.id....);
person.setText(race.getPerson());
// etc.
// etc..
// etc...
table.addView(row);
}
publishProgress(table);
}
@Override
private Void doInBackground(Void... voids) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
@Override
public void run() {
TableLayout raceType1Layout = getLayoutInflater().inflate(R.layout...);
populateTable(data, raceType1Layout);
}
});
executor.execute(new Runnable() {
@Override
public void run() {
TableLayout raceType2Layout = getLayoutInflater().inflate(R.layout...);
populateTable(data, raceType1Layout);
}
});
}
@Override
protected void onProgressUpdate(View... values) {
if(values[0] instanceof TableLayout) {
container.addView(values[0]);
}
}
}
I welcome any feedback at all. Also if you need more to go on, I’d be happy to post my actual source code but I figured a simplified and clean version would be easier.
You need implement a custom Adapter that populate your custom view with Race object. You receive your data in background with custom thread or AsyncTask and pass to the UI thread via Handler or onPostExecute method respectively. Then, you can call Adapter.clear method and after