I am an experienced iOS developer enjoying and finding useful this book. I do, however, have a suggestion.
Avoiding magic numbers and repetition of code are important principles in software development. The Timefighter example app does not demonstrate these principles, and I am concerned that newcomers to software development might learn bad habits and potentially miss out on job opportunities because of magic numbers or repeated code in their try-out apps. I recommend modifying Timefighter so that there are no magic numbers or repeated code.
Here is the repeated code:
countDownTimer = object : CountDownTimer(initialCountDown, countDownInterval) {
override fun onTick(millisUntilFinished: Long) {
timeLeft = millisUntilFinished.toInt() / 1000
val timeLeftString = getString(R.string.time_left, Integer.toString(timeLeft))
timeLeftTextView.text = timeLeftString
}
override fun onFinish() {
endGame()
}
}
...
countDownTimer = object : CountDownTimer((timeLeft * 1000).toLong(), countDownInterval) {
override fun onTick(millisUntilFinished: Long) {
timeLeft = millisUntilFinished.toInt() / 1000
val timeLeftString = getString(R.string.time_left, Integer.toString(timeLeft))
timeLeftTextView.text = timeLeftString
}
override fun onFinish() {
endGame()
}
}
Here is the magic number:
val initialTimeLeft = getString(R.string.time_left, Integer.toString(60))
This is also code repetition because the 60 represents the same concept as the 6000 in this line:
internal var initialCountDown: Long = 60000
I have refactored GameActivity.kt
to fix both problems.
class GameActivity : AppCompatActivity() {
internal lateinit var gameScoreTextView: TextView
internal lateinit var timeLeftTextView: TextView
internal lateinit var tapMeButton: Button
internal var gameStarted = false
internal lateinit var countDownTimer: CountDownTimer
internal var initialCountDown = 60000
internal var countDownInterval = 1000
internal var timeLeft = initialCountDown / countDownInterval
internal var score = 0
internal val TAG = GameActivity::class.java.simpleName
internal val millisecondsPerSecond = 1000
companion object {
private val SCORE_KEY = "SCORE_KEY"
private val TIME_LEFT_KEY = "TIME_LEFT_KEY"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game)
Log.d(TAG, "onCreate called. Score is: $score")
gameScoreTextView = findViewById<TextView>(R.id.game_score_text_view)
timeLeftTextView = findViewById<TextView>(R.id.time_left_text_view)
tapMeButton = findViewById<Button>(R.id.tap_me_button)
tapMeButton.setOnClickListener { v -> incrementScore() }
if (savedInstanceState != null) {
restoreGame(savedInstanceState.getInt(SCORE_KEY), savedInstanceState.getInt(TIME_LEFT_KEY))
} else {
resetGame()
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(SCORE_KEY, score)
outState.putInt(TIME_LEFT_KEY, timeLeft)
countDownTimer.cancel()
Log.d(TAG, "onSaveInstanceState: Saving Score: $score & Time Left: $timeLeft")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy called.")
}
private fun incrementScore() {
if (!gameStarted) {
startGame()
}
score++
gameScoreTextView.text = getString(R.string.your_score, Integer.toString(score))
}
private fun resetGame() {
score = 0
timeLeft = initialCountDown / countDownInterval
initializeGame()
gameStarted = false
}
private fun initializeGame() {
gameScoreTextView.text = getString(R.string.your_score, Integer.toString(score))
timeLeftTextView.text = getString(R.string.time_left, Integer.toString(timeLeft))
countDownTimer = object : CountDownTimer((timeLeft * millisecondsPerSecond).toLong(), countDownInterval.toLong()) {
override fun onTick(millisUntilFinished: Long) {
timeLeft = millisUntilFinished.toInt() / millisecondsPerSecond
val timeLeftString = getString(R.string.time_left, Integer.toString(timeLeft))
timeLeftTextView.text = timeLeftString
}
override fun onFinish() { endGame() }
}
}
private fun startGame() {
countDownTimer.start()
gameStarted = true
}
private fun endGame() {
Toast.makeText(this, getString(R.string.game_over_message, Integer.toString(score)), Toast.LENGTH_LONG).show()
resetGame()
}
private fun restoreGame(score: Int, timeLeft: Int) {
this.score = score
this.timeLeft = timeLeft
initializeGame()
startGame()
}
}