Search code examples
javaandroidhandlerrunnablejava-threads

Scheduling Multiple Runnables in Sequence


I am working on an Android app in Java. It is a game that is basically a trivia game focused on seniors. The GameActivity runs 10 rounds per game. I am currently doing this with a handler and runnable but the problem is getting the post game logic to work after it completes. I tried creating a second runnable but they just run simultaneously. Looking for some advice on how to resolve this issue. I need to be able to write the game to our MongoDB(through realm) after the game completes. Currently it is only done when exiting the activity.

I would like the post game logic found in postGameRunnable to run after my mainGameRunnable completes. I'm sure my structure is a mess. I have been working on this specific problem for 20 hours this weekend and it is driving me insane. Thanks in advance for all the help. I have tried ExecutorServices as well but ended up with the same problem.

`

public class GameActivity extends AppCompatActivity {

int questionCount = 0;
int playerScore = 0;
private final boolean gameFinished = false;
long _ID = UUID.randomUUID().getMostSignificantBits();

//declaring all of the layout objects
Button answerOneBtn, answerTwoBtn, answerThreeBtn, answerFourBtn;
TextView questionTextView, playerScoreText;
// Player playerTwo;

//declaring current game, handler for rounds, and player one and two


Handler gameHandler = new Handler();
Handler postGameHandler = new Handler();
Handler mainGameHandler = new Handler();



Realm realm;
Game currentGame;
LoadQuestions loadQuestions;
private Users current;
private loginPreferences session;
private String username;

Runnable gameRunnable = new Runnable() {
    @Override
    public void run() {

        playGame();
        answerOneBtn.setClickable(true);
        answerTwoBtn.setClickable(true);
        answerThreeBtn.setClickable(true);
        answerFourBtn.setClickable(true);


    }
};

Runnable postGameRunnable = new Runnable() {
    @Override
    public void run() {

        System.out.println("Gets here");

    }

};

Runnable mainGameRunnable = new Runnable() {
    @Override
    public void run() {

        for (int i = 0; i <= 9; i++) {
            gameHandler.postDelayed(gameRunnable, 5000 * i);
        }

    }
};


@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_game);

    //load realm
    loadRealm();

    //INSTANTIATE OBJECTS FOR USE
    gameHandler = new Handler();
    loadQuestions = new LoadQuestions();
    loadQuestions.questionList.AnswersJumbled();

    //INSTANTIATE BUTTONS AND TEXT VIEWS
    answerOneBtn = findViewById(R.id.AnswerOneButton);
    answerTwoBtn = findViewById(R.id.AnswerTwoButton);
    answerThreeBtn = findViewById(R.id.AnswerThreeButton);
    answerFourBtn = findViewById(R.id.AnswerFourButton);
    questionTextView = findViewById(R.id.questionText);
    playerScoreText = findViewById(R.id.playerScore);


    answerOneBtn.setOnClickListener(v -> {

        loadQuestions.playerOneSelection = answerOneBtn.getText().toString();
        playerScore += loadQuestions.checkPlayerAnswer(loadQuestions.playerOneSelection);

        answerOneBtn.setClickable(false);
        answerTwoBtn.setClickable(false);
        answerThreeBtn.setClickable(false);
        answerFourBtn.setClickable(false);


    });


    answerTwoBtn.setOnClickListener(v -> {

        loadQuestions.playerOneSelection = answerTwoBtn.getText().toString();

        playerScore += loadQuestions.checkPlayerAnswer(loadQuestions.playerOneSelection);

        answerOneBtn.setClickable(false);
        answerTwoBtn.setClickable(false);
        answerThreeBtn.setClickable(false);
        answerFourBtn.setClickable(false);


    });


    answerThreeBtn.setOnClickListener(v -> {

        loadQuestions.playerOneSelection = answerThreeBtn.getText().toString();
        playerScore += loadQuestions.checkPlayerAnswer(loadQuestions.playerOneSelection);
        answerOneBtn.setClickable(false);
        answerTwoBtn.setClickable(false);
        answerThreeBtn.setClickable(false);
        answerFourBtn.setClickable(false);


    });


    answerFourBtn.setOnClickListener(v -> {

        loadQuestions.playerOneSelection = answerFourBtn.getText().toString();
        playerScore += loadQuestions.checkPlayerAnswer(loadQuestions.playerOneSelection);

        answerOneBtn.setClickable(false);
        answerTwoBtn.setClickable(false);
        answerThreeBtn.setClickable(false);
        answerFourBtn.setClickable(false);

    });


}

@Override
protected void onStart() {
    super.onStart();

    mainGameHandler.postDelayed(mainGameRunnable, 0);
    gameHandler.postDelayed(postGameRunnable, 0);

}

private void loadRealm() {
    //open a realm and find logged in user
    session = new loginPreferences(getApplicationContext());
    username = session.getusername();
    if (realm == null) {
        realm = Realm.getDefaultInstance();
    }
    current = realm.where(Users.class).equalTo("_id", username).findFirst();

    //check for game or create game
    if (realm.where(Game.class).equalTo("playerCount", 1).findFirst() != null) {
        currentGame = realm.where(Game.class).equalTo("playerCount", 1).findFirst();
        currentGame.setPlayerCount(2);
    }
    else{
        currentGame = new Game(username, _ID, 1);
    }


}

@Override
protected void onPause() {
    super.onPause();
    currentGame.setGameCompleted(true);
    realm.executeTransaction(transactionRealm -> transactionRealm.insert(currentGame));

    if (realm != null) {
        realm.close();
    }

    gameHandler.removeCallbacks(gameRunnable);
    postGameHandler.removeCallbacks(postGameRunnable);


}

public void playGame() {

    answerOneBtn = findViewById(R.id.AnswerOneButton);
    answerTwoBtn = findViewById(R.id.AnswerTwoButton);
    answerThreeBtn = findViewById(R.id.AnswerThreeButton);
    answerFourBtn = findViewById(R.id.AnswerFourButton);
    questionTextView = findViewById(R.id.questionText);
    playerScoreText.setText(username + " " + playerScore);
    currentGame.setPlayerOneScore(playerScore);

    loadQuestions.loadQuestion(questionCount);
    questionTextView.setText(loadQuestions.currentQuestion);
    answerOneBtn.setText(loadQuestions.firstAnswer);
    answerTwoBtn.setText(loadQuestions.secondAnswer);
    answerThreeBtn.setText(loadQuestions.thirdAnswer);
    answerFourBtn.setText(loadQuestions.fourthAnswer);

    questionCount++;


}

}`


Solution

  • You can simply post the two Runnables to the same Handler:

    mainGameHandler.post(mainGameRunnable);
    mainGameHandler.post(postGameRunnable);
    

    Each handler has its own thread, that is why the runnables execute simultaneously.

    Additionally, mainGameRunnable does its own post operations which are asynchronous (meaning they finish instantly, not after the specified delay) so the mainGameRunnable will finish long before the gameRunnables will start execution. If you want to run postGameRunnable after all gameRunnables then you need to schedule it after the last gameRunnable has finished:

    Runnable mainGameRunnable = new Runnable() {
        @Override
        public void run() {
    
            for (int i = 0; i <= 9; i++) {
                gameHandler.postDelayed(gameRunnable, 5000 * i);
            }
            //schedule postGameRunnable here assuming its start delay is known
            gameHandler.postDelayed(postGameRunnable, 50000); // 
        }
    };
    

    However, given that probably you need to wait for the player to interact with the game during each gameRunnable, its duration is unknown so you probably can't know in advance when to run postGameRunnable. In this case you would need a different approach (not using scheduling).