2

I wanted to make a workout app that counts the working seconds and the rest seconds based on user input (user inputs only the work seconds). I tried to create a loop (in this case, for testing, running only 2 times where I call serie() ). This function takes an int as a param. and displays a countdown from that variable to 0. In my loop I call this 2 times ( once for the working seconds and once for rest seconds). In the loop the countdown takes place only for the rest seconds (maybe because it is already initialized, and doesn't depend on user input ??) and after it hits 0 it doesn't repeat. In the first screen the user types in a number of secs. and presses the submit button. After that the second activity will run where this code is. There isn't a problem with the passing of variables between activities.

main activity code :

package com.example.cronometru;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

public static final String sec_pass = "com.example.cronometru.sec_pass";

int work_seconds;
EditText secunde_input;

Button start_w;

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

    secunde_input = (EditText) findViewById(R.id.numar_secunde_work);
    start_w = (Button) findViewById(R.id.btn_start);
    start_w.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            work_seconds = Integer.valueOf(secunde_input.getText().toString());

            openWorkout();

        }
    });


}

public void openWorkout(){
    Intent intent = new Intent(this, Activity2.class);
    intent.putExtra(sec_pass, work_seconds);
    startActivity(intent);
}

private void showToast (String txt){
    Toast.makeText(MainActivity.this, txt, Toast.LENGTH_SHORT).show();
}

}

the second activity code :

  package com.example.cronometru;

  import androidx.appcompat.app.AppCompatActivity;

  import android.content.Intent;
  import android.os.Bundle;
  import android.os.CountDownTimer;
  import android.view.View;
  import android.widget.TextView;

  import java.sql.Time;
  import java.util.Locale;
  import java.util.concurrent.TimeUnit;
  
  public class Activity2 extends AppCompatActivity {

TextView timer;
boolean timer_free = true;
int numar_pauza = 5;
int workout_cycles = 0;
int numar_work;
long interval = 1000;

private int work = 0;
private  int rest = 1;

public void serie (int numar_secude, int mode){
    if(mode == work && timer_free){
        timer_free = false;
        new CountDownTimer(numar_secude * 1000 , interval) {
            @Override
            public void onTick(long l) {
                String sDuration = String.format(Locale.ENGLISH, "%02d : %02d", TimeUnit.MILLISECONDS.toMinutes(l),TimeUnit.MILLISECONDS.toSeconds(l) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(l)));
                timer.setText(sDuration);
            }

            @Override
            public void onFinish() {
                serie(numar_pauza, rest);
            }
        }.start();
    }
    else if (mode == rest){
        new CountDownTimer(numar_secude * 1000 , interval  ) {
            @Override
            public void onTick(long w) {
                String sDuration = String.format(Locale.ENGLISH, "%02d : 
           %02d", TimeUnit.MILLISECONDS.toMinutes(w),TimeUnit.MILLISECONDS.toSeconds(w) - 
           TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(w)));
                timer.setText(sDuration);
            }

            @Override
            public void onFinish() {

                timer_free = true;
                workout_cycles--;
                if(workout_cycles > 0 ){
                    serie(numar_work, work);
                }
            }
        }.start();
    }

}

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



    Intent intent = getIntent();
    numar_work = intent.getIntExtra(MainActivity.sec_pass, 0);

    timer = findViewById(R.id.textView);
    int i = 0;
    workout_cycles = 2;
    serie(numar_work, work);

}

}

Where is the problem ? Any ideas on doing things differently?

Alec
  • 73
  • 2
  • 7
  • Hi do you want to run the work and rest timers one after the other? According to this code wouldn't both timers run simultaneously and update the same textView? – Abhi_J Mar 12 '21 at 15:38
  • what is the role of the for loop and why you started two CountDownTimers at the same time to write a different result to the to same textView ? – Shay Kin Mar 12 '21 at 15:44
  • hi! I just want the working seconds, and then the resting seconds. These process should repeat a specific number of times – Alec Mar 12 '21 at 16:00
  • What will happen if the user rotates the screen, or changes the system theme and your Activity is destroyed and re-created? – Michael Krause Mar 12 '21 at 16:03
  • didn't think of that yet – Alec Mar 12 '21 at 16:12

2 Answers2

1

Hi to start the rest counter right after the work counter, start the rest counter inside onfinish() of work counter.

The CountDownTimer doesn't block execution of the for loop so in effect you are creating six (2x3) CountDownTimer objects almost immediately and they are all updating the same output TextView timer.

Try something like this:

int numar_pauza = 30;
int numar_work;
int workoutCycles = 0;
private final long CD_INTERVAL = 500;

private final int WORK = 0;
private final int REST = 1;

boolean timer_free = true;


public void serie (int numar_secude, int mode){
    if(mode == WORK && timer_free){
      timer_free = false;
      new CountDownTimer(numar_secude * 1000 , CD_INTERVAL) {
        @Override
        public void onTick(long l) {
            timer.setText("" + String.valueOf(Math.round(l* 0.001f)));
        }

        @Override
        public void onFinish() {
            serie(numar_pauza, REST);
        }
      }.start();
    }
    else if(mode == REST){
      new CountDownTimer(numar_secude * 1000 , CD_INTERVAL) {
        @Override
        public void onTick(long l) {
            timer.setText("" + String.valueOf(Math.round(l* 0.001f)));
        }

        @Override
        public void onFinish() {
           timer_free = true;
           workoutCyclesRemaining--;
           if(workoutCyclesRemaining > 0){
             serie(numar_work, WORK);
           }
        }
      }.start();
}

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

    Intent intent = getIntent();
    numar_work = intent.getIntExtra(MainActivity.sec_pass, 0);

    timer = findViewById(R.id.textView);

    workoutCycles = 2;
    serie(numar_work, WORK);
}

Here, we make the serie function to work in two modes:

  1. to start workout counter
  2. to start rest counter

we pass the mode as an argument during function call. Also we need to make sure the next counter is started only after one workout-rest cycle is completed and no other timer is running. So lets keep a global boolean called timer_free.

Now for testing you want execute the workout-rest cycle twice, so lets use a gobal variable workoutCycles.

  1. First the serie functions called in WORK mode. This sets the boolean timer_free to false so no other workout-timer is started till this cycle finishes. It then starts the timer to count down for numar_work seconds.
  2. When the countdown stops the onFinish() function is called, inside which we call the serie again but in REST mode.
  3. This starts a new timer with to count down for numar_pauza seconds.
  4. When this timer finishes and calls its onFinish() function, we set the timer_free variable to true. Also here we check if workoutCycles is 0.
  5. If its not we need to perform another workout-cycle so we call serie function in WORK mode. The process repeats

Read here about why we use Math.round in onTick

Hope this helps.

Abhi_J
  • 2,061
  • 1
  • 4
  • 16
  • Hi! thank you for your fixes! When i run my app in the emulator it crashes after I type in a number and press submit – Alec Mar 13 '21 at 08:44
  • It first goes "not responding" – Alec Mar 13 '21 at 08:53
  • @Alec, Hi that was my bad, I put an infinite loop in the UI thread that would freeze an Android app. I've edited the answer. This answer is for testing and understanding how `CountDownTimer` works. I hope you modify it to suit your need. – Abhi_J Mar 13 '21 at 09:46
  • nice ! the cycle works now, but there is a bug that happens when the rest period timer starts. The countdown will be displayed 5 -> 3 -> 2 -> 1 -> 0. Idk why this is happening – Alec Mar 13 '21 at 10:16
  • is the value of `numar_pauza` still 30 ? – Abhi_J Mar 13 '21 at 10:38
  • oh yea sorry!! I forgot to mention I changed it from 30 to 5, cause when it was 30 it was doing the same thing (jumping 1-2 numbers). – Alec Mar 13 '21 at 10:45
  • 1
    Oh ok, the jump in numbers is because the ticks don't happen exactly at 1000 milli-seconds. I've lowered the tick interval, added code to round `l` and provided a link to an answer that explains this behaviour. – Abhi_J Mar 13 '21 at 11:11
  • If I try to format the text and show minutes and seconds (I changed the code in the question so have a look) how do I apply that round thing to the formatted countdown? – Alec Mar 13 '21 at 14:31
  • @Alec Please don't change your question to the answer, you can ask this as a new question with the output that your are getting. – Abhi_J Mar 13 '21 at 15:03
0
  1. You don't call it only twice, your loop for(int i = 0; i <= 2; i++) goes from 0 to 2 -> 3 times. I also don't understand why you use this loop - it will set the same text multiple (currently 3) times on the same textView.
  2. You assign both the calculated values to the same textView. If you replace the timer.setText("" + l / 1000); with a log statement you will see, that all the timer instances are working correctly.

I would add a second parameter to your serie method to indicate which textView you want to set the text on.

  • Oh! I thought when I call a function it executes it and then jumps to the next line where the countdown starts again. I only want one textView that goes from lets say 30 to 0 (working) and then from 15 to 0 (resting). This process should be executed a specific numebr of times. Maybe I should put 2 textView one on top of eachother, one for work and one for rest then when timer ends I the visibility GONE? – Alec Mar 12 '21 at 16:04
  • @Alec Yes, the code is executed sequentially, but it is finished with your `start();` call. The `tick` method will execute later, this means the functionality is added to the call stack only when the time interval is reached, otherwise the countdown would block any further execution. What you need is to start the second timer when the first is finished. You can use the `onFinish` method in your CountDownTimer implementation – Jennifer Kiesel Mar 12 '21 at 16:29