Reputation: 39
I get data from server and I am creating the object of class QuizFormat
for each time the loop executes. I have a global ArrayList
declared in which I am trying to add these objects. I am doing this task in a separate method getTest()
which has inner class and it is extending the AsyncTask
class, as the background task of fetching the data is carried out in it. The problem is, the objects are not being added to the list. I tried creating a separate method to add objects by passing them as parameters, but not a luck. How can i fix this? Here is the code I am using.
QuizFormat
, a basic class with constructor, getters and setters:
public class QuizFormat {
int id;
String question;
String[] options;
String answer;
public QuizFormat(int id, String question, String[] options, String answer) {
this.id = id;
this.question = question;
this.options = options;
this.answer = answer;
}
//getters and setters below
...
...
}
Following is the TestActivity
class, in which i am doing all the above described task:
public class TestActivity extends AppCompatActivity {
ArrayList<QuizFormat> quizFormatList; //Global ArrayList
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
quizFormatList = new ArrayList<QuizFormat>(); // Initializing ArrayList
getTest(); //call to the method to fetch and add elements
//checking final size
Log.i("size","ArrayList: "+ quizFormatList.size());
try {
for (QuizFormat qf: quizFormatList) {
Log.i("que",qf.getQuestion());
}
} catch (NullPointerException e){
e.printStackTrace();
}
}
public void getTest(){
class FetchData extends AsyncTask<Void,Void,String> {
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
for (int i=0; i<testArray.length(); i++){
//Parsing JSON data here, skipping that part
String[] options = new String[4];
options[0] = optionsObj.getString("opt1");
options[1] = optionsObj.getString("opt2");
options[2] = optionsObj.getString("opt3");
options[3] = optionsObj.getString("opt4");
int que_id = testObj.getInt("que_id");
String question = testObj.getString("question");
String answer = testObj.getString("answer");
//Adding to ArrayList
quizFormatList.add(new QuizFormat(que_id, question, options, answer));
}
//checking ArrayList size
Log.i("quizFormatList", "size: "+quizFormatList.size());
}
}
//Executing class
FetchData fd = new FetchData();
fd.execute();
}
}
Following is the logcat output. This is the size in counted from inner class 'FetchData'
I/quizFormatList: size: 5
and the following one is the size counted in onCreate()
method after calling getTest()
, as shown in the code above.
I/size: ArrayList: 0
How can i get that data in the list, and how to access it? Help needed.
Upvotes: 2
Views: 141
Reputation: 17085
This is a classic problem of async vs sync behavior. What's happening here is that you are calling the method getTest
which is asynchronous and accessing right after that quizFormatList
. The problem is that you access quizFormatList
before getTest
has a chance to populate it. Unfortunately, this cannot be done like that and also, sometimes it might work, and sometimes it might not.
There are several solutions for this, let me try and provide one that doesn't require too many changes to what you have now. First, we want the async task to actually notify the main activity when it's done with its work. We will do this with a callback:
interface OnAsyncWorkDoneCallback {
void asyncWorkDone(List<QuizFormat> quizFormatList);
}
class FetchData extends AsyncTask<Void,Void,String> {
private final List<QuizFormat> quizFormatList = new ArrayList<>();
private final OnAsyncWorkDoneCallback callback;
public FetchData(OnAsyncWorkDoneCallback callback) {
this.callback = callback;
}
@Override
protected void onPostExecute(String s) {
// Do exactly what you were doing before and populate the field quizFormat
callback.asyncWorkDone(quizFormatList);
}
}
So what we have is an async task that requires now a callback to be created and once it's done it'll call this callback with the correct results for quizFormatList
.
Here's how you use it from the activity:
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new FetchData((results) -> {
// do whatever you need to do with results
}).execute();
}
}
The code above will hopefully work and serve your needs, but I have to say that using async tasks on android is considered dangerous and highly discouraged. This topic is another whole answer by itself and I didn't want to address it right now, since your problem is not necessarily specific to async tasks, but to race conditions in an asynchronous environment. However, you can see that if the main activity gets destroyed (the phone rotates, i.e.) the callback passed to the currently running async task will belong to a destroyed activity and depending on what you do with the results it might end up trying to access things it can no longer access. This is just one of the issues with this approach. You can read online about all of it, there's more than enough information on it, but here's a really good post by Dan Lew.
Upvotes: 2