androidkotlinsavestateandroid-savedstate

How to save the state of delayed posts in Kotlin


For an Android app I have some content that I want to show with a delay. Therefore I'm using a handler.

private lateinit var mHandler: Handler

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    linearLayout.isVisible = false

    mHandler = Handler()
    mHandler.postDelayed({
        linearLayout.isVisible = true 
    }, 1000)
}

How can I save this state (for example in case of rotating the app)? And is it better to use SharedPreferences or something like this:

override fun onSaveInstanceState(outState: Bundle?) {
    super.onSaveInstanceState(outState)

}

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    super.onRestoreInstanceState(savedInstanceState)

}

Solution

  • I would recommend using the newer ViewModel component to tackle something like this. The ViewModel doesn't get destroyed on configuration changes like activities and fragments do, so you can run your Handler (or Timer) without fear of losing its state.

    class MainViewModel : ViewModel() {
        private val layoutVisibility: MutableLiveData<Boolean> by lazy {
            MutableLiveData().also {
                delayVisibility()
            }
        }
    
        private fun delayVisibility() {
            Timer().schedule(1000) {
                layoutVisibility.postValue(true)
            }
        }
    }
    
    class MainActivity : AppCompatActivity() {
        private lateinit var model: MainViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            model = ViewModelProviders.of(this)[MainViewModel::class.java]
    
            linearLayout.isVisible = false
    
            model.layoutVisibility.observe(this, Observer<Boolean> { visibility ->
                linearLayout.isVisible = visibility == true
            })
        }
    }
    

    You could save the state of the handler—by saving the start time and calculating how much time has elapsed when the activity is recreated—but the ViewModel architecture seems more intuitive to me.

    class MainActivity : AppCompatActivity() {
        private lateinit var startTime: Long
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            linearLayout.isVisible = false
    
            var elapsedTime: Long = 0L    
            if (savedInstanceState != null) {
                startTime = savedInstanceState.getLong(KEY_START_TIME, System.currentTimeMillis())
                elapsedTime = System.currentTimeMillis() - startTime
            } else {
                startTime = System.currentTimeMillis()
            }
    
            if (elapsedTime >= 1000) {
                linearLayout.isVisible = true
            } else {
                Handler().postDelayed({
                    linearLayout.isVisible = true
                }, 1000 - elapsedTime)
            }
        }
    
        override fun onSaveInstanceState(outState: Bundle) {
            super.onSaveInstanceState(outState)
            outState.putLong(KEY_START_TIME, startTime)
        }
    
        companion object {
            private const val KEY_START_TIME = "start_time"
        }
    }