None of the other instances of this question are solving my problem. I have a Fragment that appears at the end of a transaction sequence. It is meant to close the app when a CountDownTimer contained within it counts down:
class TerminalFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onStart() {
super.onStart()
startOverButton.setOnClickListener {
returnToStart()
}
initUi()
startCountDown()
}
override fun onStop() {
super.onStop()
AppLog.i(TAG, "onStop()")
stopCountdown()
}
}
private fun startCountDown() {
terminalCountdown = object : CountDownTimer(5000, 1000) {
override fun onFinish() {
AppLog.i(TAG, "Terminal countdown finished")
(context as MainActivity).finish()
}
override fun onTick(millisUntilFinished: Long) {
}
}
.start()
}
private fun stopCountdown() {
AppLog.i(TAG, "stopCountDown() - Terminal countdown stopped")
terminalCountdown?.cancel()
terminalCountdown = null
}
private fun returnToStart() {
AppLog.i(TAG, "returnToStart()")
stopCountdown()
(context as MainActivity).restartFlow()
}
stopCountDown() is being called whenever the fragment is navigated away from, but it somehow survives sometimes and closes the app from another Fragment. Using logs, I've also discovered that there appears to be 2 instances of this countdown sometimes. How do I insure that this countdown is never active outside of this fragment?
As Shrey Greg mentioned in a comment, I was creating a new instance of CountdownTimer every time I wanted to restart it. This lost the reference to the previous instance, making it impossible to cancel.
Related
I want to recreate a timer like Google Authenticator, which never stops and if the app is killed and you reopen it, the timer still working.
I tried to make a timer but every time I destroy the fragment, it resets. How it is possible to do this?
val timer = object: CountDownTimer(10000,1000){
override fun onTick(millisecondsLeft:Long){
//Do something
}
override fun onFinish(){
this.start //this resets the timer when it reach 0
}
}
timer.start()
It's not a timer. It simply uses realtime clock.
The "start" event is that you take the current RTC value, save it and then display the difference as "elapsed time". This way, there is nothing to worry about when the app is not running. On app restart, re-read the value and you're there.
Keep your timer in a ViewModel, which is Lifecycle aware, so your timer will keep working as long as the parent activity "exists".
class YourViewModel: ViewModel() {
private val timer: CountDownTimer
init {
timer = object: CountDownTimer(10000, 1000) {
override fun onTick(p0: Long) {
//Do something
}
override fun onFinish() {
this.start() //this resets the timer when it reach 0
}
}
}
override fun onCleared() {
super.onCleared()
timer.cancel()
}
}
I have a simple MainActivity and if the app is completely killed it looks like onCreate() is called once. If however I back out of the app so it still appears in the background, when I re-open it I get every log message twice. The weirdest part is if I generate a random number it is always the same in the 2 log messages.
I've tried adding android:LaunchMode="singleTop" (also singleInstance singleTask) in the activity and application tags of the Manifest.
class MainActivity : AppCompatActivity() {
private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val view = binding.root
setContentView(view)
setupViews()
val data: Uri? = intent?.data
DataHolder.getInstance().setItem(data)
Timber.plant(Timber.DebugTree())
setupInjection()
Timber.d("review nanoTime = ${System.nanoTime()}")
Timber.d("review savedInstance = $savedInstanceState")
Timber.d("review random = ${Random.nextInt()}")
}
override fun onPause() {
Timber.d("review onPause()")
super.onPause()
}
override fun onStop() {
Timber.d("review onStop()")
super.onStop()
}
override fun onDestroy() {
Timber.d("review onDestroy()")
super.onDestroy()
finish()
}
override fun onStart() {
Timber.d("review onStart()")
super.onStart()
}
override fun onRestart() {
Timber.d("review onRestart()")
super.onRestart()
}
override fun onResume() {
Timber.d("review onResume()")
super.onResume()
}
private fun setupInjection() {
val appInjector = InjectorImpl(
firebaseAuth = FirebaseAuth.getInstance()
)
Injector.initialize(appInjector)
}
private fun setupViews() = binding.apply {
val navController = findNavController(R.id.nav_host_fragment)
navView.setupWithNavController(navController)
navView.setOnItemSelectedListener { item ->
when (item.itemId){
R.id.navigation_item_calculator -> {
navController.navigate(BuilderFragmentDirections.actionBuilderToCalculator())
}
R.id.navigation_item_builder -> {
navController.navigate(CalculatorFragmentDirections.actionCalculatorToBuilder())
}
}
true
}
navView.setOnItemReselectedListener { }
}
}
Here is a table of the log trace I get when I run the app on my phone from Android studio. Since the random numbers are the same I feel like this is actually a Logging bug in Android studio and the app isn't actually opened twice.
Realized my problem was with my logging library I used.
Timber was planting a new tree but wasn't uprooting old ones from being backed out so there were 2 instances of them. I fixed by putting a Timber.uprootAll() just before Timber.plant(Timber.DebugTree())
im new at coding, i dont know even basics but
I need make somekind 20 seconds countdown timer and i didin't found any working tutorials.
I tried my best but always some error came up.
pass time in milliseconds.
var countDownTimer: CountDownTimer? = null //declare it as global variable
fun startCountDown(time: Long) {
countDownTimer = object: CountDownTimer(time,1000){
override fun onFinish() {
Timber.v("Countdown: Finished")
visibility = View.GONE
}
override fun onTick(millisUntilFinished: Long) {
Timber.v("Countdown: $millisUntilFinished")
visibility = View.VISIBLE
updateTimeView(millisUntilFinished)
}
}
countDownTimer?.start()
}
For example:
startCountDown(30000), will countdown from 30 to 0.
Note:
Don't forget to stop the timer when your app stops:
override fun onStop() {
super.onStop()
countDownTimer?.cancel()
}
None of the other instances of this question are solving my problem. I have a Fragment that appears at the end of a transaction sequence. It is meant to close the app when a Timer contained within it completes:
var terminalTimer: Timer? = null
class TerminalFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_terminal, container, false)
}
override fun onStart() {
super.onStart()
initUi()
startCountDown()
}
override fun onStop() {
super.onStop()
AppLog.i(TAG, "onStop()")
stopCountDown()
}
private fun startCountDown() {
if (terminalTimer == null) {
terminalTimer = Timer()
terminalTimer!!.schedule(object : TimerTask() {
override fun run() {
AppLog.i(TAG, "Terminal countdown finished")
{
activity?.finish()
}
}, 5000)
}
}
private fun stopCountDown() {
AppLog.i(TAG, "stopCountDown()")
terminalTimer?.cancel()
terminalTimer?.purge()
terminalTimer = null
}
private fun returnToStart() {
AppLog.i(TAG, "returnToStart()")
(context as MainActivity).restartFlow() // calls popBackStackImmediate() for every fragment in the backstack, returning user to the beginning of their flow
}
companion object {
#JvmStatic
fun newInstance(terminalType: String, amountLoaded: Double) =
TerminalFragment().apply {
arguments = Bundle().apply {
}
}
}
}
stopCountDown() is being called whenever the fragment is navigated away from, but it somehow survives sometimes and closes the app from another Fragment. Using logs, I've also discovered that there appears to be 2 instances of this timer sometimes. How do I insure that this countdown is never active outside of this fragment and is cancelled/ destroyed in the Fragment's onStop()?
I've created a simple app with one activity and I create a MediaSession in its onCreate method
However when I run the application and use external media buttons the callback is never called. Any ideas what I might be missing?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
this.mediaSession = MediaSessionCompat(this, "TAG")
this.mediaSession?.setCallback(object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
return super.onMediaButtonEvent(mediaButtonIntent)
}
override fun onPlay() {
super.onPlay()
}
})
val builder = PlaybackStateCompat.Builder()
builder.setActions(PlaybackStateCompat.ACTION_PLAY)
builder.setState(PlaybackStateCompat.STATE_STOPPED, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0.0f)
this.mediaSession?.setPlaybackState(builder.build())
this.mediaSession?.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
this.mediaSession?.isActive = true
}
You need to implement BroadcastReceiver. One example here: https://github.com/tutsplus/background-audio-in-android-with-mediasessioncompat/blob/master/app/src/main/java/com/tutsplus/backgroundaudio/BackgroundAudioService.java