Learn how to create a beautiful material design timer app for Android.
In this course you will learn how to make a user interface. Later we’re going to code a timer which can run in the foreground. Then we are going to upgrade it to be able to run also in the background – and we will control it from notifications! Finally we will create a settings activity where a user will be able to set the length of the timer.
In the second part we’re coding the actual timer (CountdownTimer) which can run in the foreground. We’re also using shared preferences to persistently store certain data like timer length or seconds remaining.
This post contains all the code that’s been written in this YouTube video.
You can also check out this GitHub repository: https://github.com/ResoCoder/TimerAppAndroidTutorial
TimerActivity.kt
package com.resocoder.timertutorial
import android.os.Bundle
import android.os.CountDownTimer
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import com.resocoder.timertutorial.util.PrefUtil
import kotlinx.android.synthetic.main.activity_timer.*
import kotlinx.android.synthetic.main.content_timer.*
class TimerActivity : AppCompatActivity() {
enum class TimerState{
Stopped, Paused, Running
}
private lateinit var timer: CountDownTimer
private var timerLengthSeconds: Long = 0
private var timerState = TimerState.Stopped
private var secondsRemaining: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_timer)
setSupportActionBar(toolbar)
supportActionBar?.setIcon(R.drawable.ic_timer)
supportActionBar?.title = " Timer"
fab_start.setOnClickListener{v ->
startTimer()
timerState = TimerState.Running
updateButtons()
}
fab_pause.setOnClickListener { v ->
timer.cancel()
timerState = TimerState.Paused
updateButtons()
}
fab_stop.setOnClickListener { v ->
timer.cancel()
onTimerFinished()
}
}
override fun onResume() {
super.onResume()
initTimer()
//TODO: remove background timer, hide notification
}
override fun onPause() {
super.onPause()
if (timerState == TimerState.Running){
timer.cancel()
//TODO: start background timer and show notification
}
else if (timerState == TimerState.Paused){
//TODO: show notification
}
PrefUtil.setPreviousTimerLengthSeconds(timerLengthSeconds, this)
PrefUtil.setSecondsRemaining(secondsRemaining, this)
PrefUtil.setTimerState(timerState, this)
}
private fun initTimer(){
timerState = PrefUtil.getTimerState(this)
//we don't want to change the length of the timer which is already running
//if the length was changed in settings while it was backgrounded
if (timerState == TimerState.Stopped)
setNewTimerLength()
else
setPreviousTimerLength()
secondsRemaining = if (timerState == TimerState.Running || timerState == TimerState.Paused)
PrefUtil.getSecondsRemaining(this)
else
timerLengthSeconds
//TODO: change secondsRemaining according to where the background timer stopped
//resume where we left off
if (timerState == TimerState.Running)
startTimer()
updateButtons()
updateCountdownUI()
}
private fun onTimerFinished(){
timerState = TimerState.Stopped
//set the length of the timer to be the one set in SettingsActivity
//if the length was changed when the timer was running
setNewTimerLength()
progress_countdown.progress = 0
PrefUtil.setSecondsRemaining(timerLengthSeconds, this)
secondsRemaining = timerLengthSeconds
updateButtons()
updateCountdownUI()
}
private fun startTimer(){
timerState = TimerState.Running
timer = object : CountDownTimer(secondsRemaining * 1000, 1000) {
override fun onFinish() = onTimerFinished()
override fun onTick(millisUntilFinished: Long) {
secondsRemaining = millisUntilFinished / 1000
updateCountdownUI()
}
}.start()
}
private fun setNewTimerLength(){
val lengthInMinutes = PrefUtil.getTimerLength(this)
timerLengthSeconds = (lengthInMinutes * 60L)
progress_countdown.max = timerLengthSeconds.toInt()
}
private fun setPreviousTimerLength(){
timerLengthSeconds = PrefUtil.getPreviousTimerLengthSeconds(this)
progress_countdown.max = timerLengthSeconds.toInt()
}
private fun updateCountdownUI(){
val minutesUntilFinished = secondsRemaining / 60
val secondsInMinuteUntilFinished = secondsRemaining - minutesUntilFinished * 60
val secondsStr = secondsInMinuteUntilFinished.toString()
textView_countdown.text = "$minutesUntilFinished:${if (secondsStr.length == 2) secondsStr else "0" + secondsStr}"
progress_countdown.progress = (timerLengthSeconds - secondsRemaining).toInt()
}
private fun updateButtons(){
when (timerState) {
TimerState.Running ->{
fab_start.isEnabled = false
fab_pause.isEnabled = true
fab_stop.isEnabled = true
}
TimerState.Stopped -> {
fab_start.isEnabled = true
fab_pause.isEnabled = false
fab_stop.isEnabled = false
}
TimerState.Paused -> {
fab_start.isEnabled = true
fab_pause.isEnabled = false
fab_stop.isEnabled = true
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_timer, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
}
PrefUtil.kt
package com.resocoder.timertutorial.util
import android.content.Context
import android.preference.PreferenceManager
import com.resocoder.timertutorial.TimerActivity
class PrefUtil {
companion object {
fun getTimerLength(context: Context): Int{
//placeholder
return 1
}
private const val PREVIOUS_TIMER_LENGTH_SECONDS_ID = "com.resocoder.timer.previous_timer_length_seconds"
fun getPreviousTimerLengthSeconds(context: Context): Long{
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
return preferences.getLong(PREVIOUS_TIMER_LENGTH_SECONDS_ID, 0)
}
fun setPreviousTimerLengthSeconds(seconds: Long, context: Context){
val editor = PreferenceManager.getDefaultSharedPreferences(context).edit()
editor.putLong(PREVIOUS_TIMER_LENGTH_SECONDS_ID, seconds)
editor.apply()
}
private const val TIMER_STATE_ID = "com.resocoder.timer.timer_state"
fun getTimerState(context: Context): TimerActivity.TimerState{
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val ordinal = preferences.getInt(TIMER_STATE_ID, 0)
return TimerActivity.TimerState.values()[ordinal]
}
fun setTimerState(state: TimerActivity.TimerState, context: Context){
val editor = PreferenceManager.getDefaultSharedPreferences(context).edit()
val ordinal = state.ordinal
editor.putInt(TIMER_STATE_ID, ordinal)
editor.apply()
}
private const val SECONDS_REMAINING_ID = "com.resocoder.timer.seconds_remaining"
fun getSecondsRemaining(context: Context): Long{
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
return preferences.getLong(SECONDS_REMAINING_ID, 0)
}
fun setSecondsRemaining(seconds: Long, context: Context){
val editor = PreferenceManager.getDefaultSharedPreferences(context).edit()
editor.putLong(SECONDS_REMAINING_ID, seconds)
editor.apply()
}
}
}


Thank you very much for sharing! Very neat coding!
I appreciated!
Please I’m having a problem with the app, I reached chapter 4 of the tutorial but then I realized that the timer resets when every time I pause it and then minimize the app but if I resume in the notification, It would resume from the previousTimerLengthSeconds.
Great tut! Worth noting: PreferenceManager class was deprecated in API level 29.
Is there a graphices count down timer project coded with java in android studio
How to start a timer from another activity or button
Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?
Can you be more specific about the content of your article? After reading it, I still have some doubts. Hope you can help me.
Your article helped me a lot, is there any more related content? Thanks!
I don’t think the title of your article matches the content lol. Just kidding, mainly because I had some doubts after reading the article.
Can you be more specific about the content of your article? After reading it, I still have some doubts. Hope you can help me.
Your article helped me a lot, is there any more related content? Thanks!
Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?