3

I am creating a trivia app. I want the right/wrong animation to occur immediately after the user clicks an answer, and then after 3 seconds, the question to switch automatically(fading into the next question). I tried doing this using Thread.sleep(3000), but the whole program freezes. This is my code so far:

binding.buttonTrue.setOnClickListener(view -> {
            checkAnswer(true);
            updateQuestion();
            //these two calls invoke right/wrong animation

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            changeTheQuestion();
            //this invokes fade animation

        });

How can I add a period of time between these two animations? Thanks!

Full code: package com.bawp.trivia;

import android.graphics.Color;
import android.media.AudioAttributes;
import android.media.SoundPool;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;

import com.bawp.trivia.data.Repository;
import com.bawp.trivia.databinding.ActivityMainBinding;
import com.bawp.trivia.model.Question;
import com.google.android.material.snackbar.Snackbar;

import java.util.ArrayList;
import java.util.List;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

public class MainActivity extends AppCompatActivity {

    List<Question> questionList;
    Handler mHandler;
    private ActivityMainBinding binding;
    private int currentQuestionIndex = 0;
    SoundPool soundPool;
    private int sound1, sound2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        questionList = new Repository().getQuestions(questionArrayList -> {
                    binding.questionTextview.setText(questionArrayList.get(currentQuestionIndex)
                            .getAnswer());

                    updateCounter(questionArrayList);
                }

        );

        mHandler = new Handler();


        binding.buttonNext.setOnClickListener(view -> {

            currentQuestionIndex = (currentQuestionIndex + 1) % questionList.size();
            updateQuestion();

        });


        binding.buttonTrue.setOnClickListener(view -> {

          checkAnswer(true);
          updateQuestion();

          new Handler().postDelayed(new Runnable() {
              @Override
              public void run() {
                  changeTheQuestion();
              }
          }, 3000);

        });




        binding.buttonFalse.setOnClickListener(view -> {

            checkAnswer(true);
            updateQuestion();

            new Handler().postDelayed(new Runnable() {
                public void run() {
                    changeTheQuestion();
                }
            }, 3000);



        });



        AudioAttributes audioAttributes = new AudioAttributes.Builder()
                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                .build();

        soundPool = new SoundPool.Builder()
                .setMaxStreams(4)
                .setAudioAttributes(audioAttributes)
                .build();

        sound1 = soundPool.load(this, R.raw.correct, 1);
        sound2 =  soundPool.load(this, R.raw.wrong, 1);


    }

    private void checkAnswer(boolean userChoseCorrect) {
        boolean answer = questionList.get(currentQuestionIndex).isAnswerTrue();
        int snackMessageId = 0;
        if (userChoseCorrect == answer) {
            snackMessageId = R.string.correct_answer;
            fadeAnimation();
            soundPool.play(sound1, 1, 1, 0, 0, 1);
        } else {
            snackMessageId = R.string.incorrect;
            shakeAnimation();
            soundPool.play(sound2, 1, 1, 0, 0, 1);
        }
        Snackbar.make(binding.cardView, snackMessageId, Snackbar.LENGTH_SHORT)
                .show();

    }

    private void updateCounter(ArrayList<Question> questionArrayList) {
        binding.textViewOutOf.setText(String.format(getString(R.string.text_formatted),
                currentQuestionIndex, questionArrayList.size()));
    }

    private void fadeAnimation() {
        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        alphaAnimation.setDuration(300);
        alphaAnimation.setRepeatCount(1);
        alphaAnimation.setRepeatMode(Animation.REVERSE);


        binding.cardView.setAnimation(alphaAnimation);

        alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                binding.questionTextview.setTextColor(Color.GREEN);
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                binding.questionTextview.setTextColor(Color.WHITE);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });





    }
    private void changeTheQuestion() {
        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        alphaAnimation.setDuration(300);
        alphaAnimation.setRepeatCount(1);
        alphaAnimation.setRepeatMode(Animation.REVERSE);




        binding.cardView.setAnimation(alphaAnimation);

        alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                binding.questionTextview.setTextColor(Color.BLUE);
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                currentQuestionIndex = (currentQuestionIndex + 1) % questionList.size();
                updateQuestion();
                binding.questionTextview.setTextColor(Color.WHITE);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });





    }

    private void updateQuestion() {
        String question = questionList.get(currentQuestionIndex).getAnswer();
        binding.questionTextview.setText(question);
        updateCounter((ArrayList<Question>) questionList);
    }

    private void shakeAnimation() {
        Animation shake = AnimationUtils.loadAnimation(MainActivity.this,
                R.anim.shake_animation);
        binding.cardView.setAnimation(shake);


        shake.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                binding.questionTextview.setTextColor(Color.RED);

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                binding.questionTextview.setTextColor(Color.WHITE);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });






    }

    


}

Repository.java:

package com.bawp.trivia.data;

import android.util.Log;

import com.android.volley.Request;
import com.android.volley.toolbox.JsonArrayRequest;
import com.bawp.trivia.controller.AppController;
import com.bawp.trivia.model.Question;

import org.json.JSONException;

import java.util.ArrayList;
import java.util.List;

public class Repository {
    ArrayList<Question> questionArrayList = new ArrayList<>();

    String url = "https://raw.githubusercontent.com/curiousily/simple-quiz/master/script/statements-data.json";

    public List<Question> getQuestions( final AnswerListAsyncResponse callBack) {

        JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(Request.Method.GET,
                url, null, response -> {
            for (int i = 0; i < response.length(); i++) {

                try {
                    Question question = new Question(response.getJSONArray(i).get(0).toString(),
                            response.getJSONArray(i).getBoolean(1));

                    //Add questions to arraylist/list
                    questionArrayList.add(question);

                    //Log.d("Hello", "getQuestions: " + questionArrayList);


                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
            if (null != callBack) callBack.processFinished(questionArrayList);



        }, error -> {

        });

        AppController.getInstance().addToRequestQueue(jsonArrayRequest);

        return questionArrayList;
    }

}
CodingChap
  • 1,088
  • 2
  • 10
  • 23
  • Removed `android-studio` tag. This tag is used for problems/questions about the Android Studio product. Your question is a generic Android question and has nothing to do with Android Studio. – David Wasser May 02 '21 at 20:20
  • Ok, thanks. Any idea how to solve this? Thanks. – CodingChap May 02 '21 at 20:23

1 Answers1

4

You cannot call Thread.sleep() on the main (UI) thread. This will freeze your app and cause your app to crash with ANR (Application Not Responsive) error.

You can just post a Runnable that will run a chunk of code after a certain period of time. Something like this:

new Handler().postDelayed(new Runnable() {
    public void run() {
        changeTheQuestion();
    }
}, 3000);
David Wasser
  • 93,459
  • 16
  • 209
  • 274
  • Hi, I actually tried this, but it starts glitching and blinking like 3 times before moving on to the next question. Sometimes it works on questions, sometimes you have to click the answer several times in order to move on. Any idea what may be causing this? Thanks. – CodingChap May 02 '21 at 20:29
  • 1
    No, but it sounds like you have other problems. Doing this shouldn't cause that problem. I would have to see more code. Maybe show the code for your `changeQuestion()` – David Wasser May 02 '21 at 20:36
  • Included the full code in the OP. Thanks. – CodingChap May 02 '21 at 20:38
  • I'm assuming that the problem is in your animation. What happens if you don't call `changeQuestion()` at all? Do you still have this blinking/glitching? – David Wasser May 02 '21 at 20:41
  • Also, since you already have declared a `Handler`, you can just use that: `mHandler.postDelayed(...)` instead of `new Handler().postDelayed(...)` – David Wasser May 02 '21 at 20:41
  • Hmm...I think you may be right. I took out changeTheQuestion(), and just increased the index + called updateQuestion() and it worked(but without the animation). How can I get it to fade in to the next question? Thanks. – CodingChap May 02 '21 at 20:45
  • 1
    Hi, solved the problem. For some reason, calling updateQuestion() inside changeTheQuestion() screwed things up. Instead, I increased the index, called changeTheQuestion() and then updateQuestion() and things are working fine. Thanks! – CodingChap May 02 '21 at 20:47
  • Do you know if there's a specific reason behind why calling updateQuestion() inside changeTheQuestion() doesn't work? Thanks! – CodingChap May 02 '21 at 20:49
  • Not sure exactly. I don't know what you mean by "screwed things up". Anyway, in `updateQuestion()` you are accessing a database. This is being called on the main (UI) thread, so it could cause some freezing of the display. You should make sure that you don't access the database or network from the main (UI) thread, always do this in background threads. – David Wasser May 02 '21 at 22:17
  • Ah, I see. Thank you so much. – CodingChap May 02 '21 at 22:39
  • Hi, I'm a little bit confused. Since we're calling changeTheQuestion() from a background thread, how is it that we're able to update components from the UI thread(i.e. perform the animation on card view)? Or is it being run on the main thread due to the handler(https://stackoverflow.com/questions/5185015/updating-android-ui-using-threads)? Thanks! – CodingChap May 03 '21 at 03:39
  • `Handler` is created on the main (UI) thread, so the `Runnable` that you post to it also runs on the main (UI) thread. It isn't a background thread. – David Wasser May 03 '21 at 07:37
  • "This is being called on the main (UI) thread, so it could cause some freezing of the display. You should make sure that you don't access the database or network from the main (UI) thread, always do this in background threads." What is the background thread in this case? Thanks! – CodingChap May 03 '21 at 15:20
  • If they're both being called on the Main UI thread, what is the difference between calling updateQuestion() inside changeTheQuestion() (onAnimationEnd() ) and calling updateQuestion() inside the runnable of the Handler? Thanks! – CodingChap May 03 '21 at 15:28
  • Probably nothing. Except something in one of those methods is accessing a database, which you shouldn't do on the main (UI) thread anyway. It is difficult to diagnose this remotely. It could also be that making the `onAnimationEnd()` method take a long time is causing some problems. – David Wasser May 03 '21 at 16:17
  • Isn't onAnimationEnd when the animation is finished though? So there shouldn't be any problems with calling methods in it? Not sure – CodingChap May 03 '21 at 16:48
  • Btw I added the Repository.java file where I access all the question data from the internet. – CodingChap May 03 '21 at 17:13
  • Thanks. I misunderstood. It seems that you access the Internet just once to load all the data, so you aren't accessing a database on the main (UI) thread. I don't see anything obvious that would cause the behaviour you described. Sorry. – David Wasser May 03 '21 at 18:26
  • It's ok, I'll ask in a separate post to see if I can get any feedback. Thanks for all the help. – CodingChap May 03 '21 at 20:03
  • Hi, I'm having some problems with this. It seems to make the onClick() method wait for 3 seconds instead of just making changeTheQuestion() inside the Handler wait three seconds. Thus, checkAnswer() and updateQuestion() aren't able to execute to play the first animation. Any idea how to solve this? Thanks. – CodingChap May 04 '21 at 23:36