Android AsyncTask is not updating Progress Bar - android

Hello I have a problem with asynctask.I play a song then I update duration to progressbar. But when I play a new song progressbar don't back to 0th position and progressbar is continuing with old value
Here is my code:
class Task(context: Context, progressBar: ProgressBar) : AsyncTask<Int, Int, String>() {
#SuppressLint("StaticFieldLeak")
private var progressBar: ProgressBar? = progressBar
private var count = 0
override fun doInBackground(vararg input: Int?): String {
while (count <= input[0]!!) {
count++
publishProgress(count)
Thread.sleep(1000)
if (isCancelled){
count=0
}
}
return "Task completed"
}
override fun onPreExecute() {
progressBar!!.progress = 0
}
override fun onProgressUpdate(vararg values: Int?) {
progressBar!!.progress = values[0]!!
}
}
when I play song :
override fun onItemClicked(position: Int, song: Song) {
val secondsDuration = song.duration!! / 1000
activity!!.pgbSong.max = secondsDuration
val task = Task(context!!, activity!!.pgbSong)
if (task.status == AsyncTask.Status.RUNNING) {
task.cancel(true)
}
task.execute(song.duration)
}

Well, what to say - you never cancel previous async tasks. Cause you're calling cancel(true) on just created async tasks every time:
val task = Task(context!!, activity!!.pgbSong)
if (task.status == AsyncTask.Status.RUNNING) {
task.cancel(true)
}
task.execute(song.duration)
Instead, you should save previous async task in an object variable (something like this):
private var asyncTask : AsyncTask<*,*,*>? = null
And after in the method call:
override fun onItemClicked(position: Int, song: Song) {
if (asyncTask.status == AsyncTask.Status.RUNNING) {
asyncTask.cancel(true)
}
val secondsDuration = song.duration!! / 1000
activity!!.pgbSong.max = secondsDuration
asyncTask = Task(context!!, activity!!.pgbSong)
asyncTask.execute(song.duration)
}
And, I guess, you should do a return in an AsyncTask when you're checking if it canceled or not.
But please don't use AsyncTask in this manner. Cause it holds links views and activity which can prevent those of being garbage collected and so cause a memory leak.
And please don't use !! with Kotlin. Instead use null check or provide default value if null. Examples:
val int = object?.int ?: 0
val context = activity ?: return
val view = activity?.pgbSong ?: return // or if (view != null)

Related

Kotlin recycler view cannot change background when later calling a function

I'm new to Kotlin and so if you do find rubbish code here and poor practices, do let me know. Otherwise, here is the issue I am having.
I am writing a tiny app that presents users with multiple questions from which they have to select the correct answer. If they select the correct answer, the option is supposed to be highlighted green for 250ms and then they move on to the next question. Otherwise, select the incorrect answer. The logic for moving onto the next question is defined in the main activity, and the background change logic is defined in the adapter class. Below is what the adapter class looks like at the moment (I've only included that which I think is relevant just to add too much faff):
class QuestionOptionAdapter(
private val items: ArrayList<String>,
private val correctAnswer: String,
) : RecyclerView.Adapter<QuestionOptionAdapter.ViewHolder>() {
var onSelectedAnswer: (String) -> Unit = {}
var onSelectedCorrectAnswer: () -> Unit = {}
var onSelectedIncorrectAnswer: () -> Unit = {}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.tvOption.text = item
holder.tvOption.setOnClickListener {
if (item == correctAnswer) {
runBlocking {
it.background =
ContextCompat.getDrawable(it.context, R.drawable.question_opt_correct)
delay(250)
}
onSelectedCorrectAnswer()
} else {
it.background =
ContextCompat.getDrawable(it.context, R.drawable.question_opt_incorrect)
onSelectedIncorrectAnswer()
}
}
}
}
I realised that although the code to changes the background is executed before onSelectedCorrectAnswer(), it won't change the background colour until the entire block has finished executing. Therefore, the user never sees the updated background.
Is there a way to show an update before the block finishes executing?
runBlocking doesn't work because it blocks. It will just wait for the whole time you delay and block the main thread so the device will be frozen and not show any visual changes until it returns.
You need to pass the Activity or Fragment's CoroutineScope into the adapter for the adapter to use. You can then launch a coroutine that won't block the main thread when you delay inside it.
Here I lifted the coroutine to encompass all your click listener logic. That will make it easier to modify the behavior later if you want.
class QuestionOptionAdapter(
private val scope: CoroutineScope,
private val items: ArrayList<String>,
private val correctAnswer: String,
) : RecyclerView.Adapter<QuestionOptionAdapter.ViewHolder>() {
var onSelectedAnswer: (String) -> Unit = {}
var onSelectedCorrectAnswer: () -> Unit = {}
var onSelectedIncorrectAnswer: () -> Unit = {}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.tvOption.text = item
holder.tvOption.setOnClickListener { view ->
scope.launch {
val isCorrect = item == correctAnswer
val colorDrawable =
if (isCorrect) R.drawable.question_opt_correct
else R.drawable.question_opt_incorrect
view.background = ContextCompat.getDrawable(view.context, colorDrawable)
if (isCorrect) {
delay(250)
onSelectedCorrectAnswer()
} else {
onSelectedIncorrectAnswer()
}
}
}
}
}
Actually, you probably want to also prevent the user from clicking other options during that 250ms delay, so you should set a Boolean that can disable further clicking of items during the delay:
class QuestionOptionAdapter(
private val scope: CoroutineScope,
private val items: ArrayList<String>,
private val correctAnswer: String,
) : RecyclerView.Adapter<QuestionOptionAdapter.ViewHolder>() {
var onSelectedAnswer: (String) -> Unit = {}
var onSelectedCorrectAnswer: () -> Unit = {}
var onSelectedIncorrectAnswer: () -> Unit = {}
private var isLockClickListeners = false
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.tvOption.text = item
holder.tvOption.setOnClickListener { view ->
if (isLockClickListeners) {
return#setOnClickListener
}
scope.launch {
val isCorrect = item == correctAnswer
val colorDrawable =
if (isCorrect) R.drawable.question_opt_correct
else R.drawable.question_opt_incorrect
view.background = ContextCompat.getDrawable(view.context, colorDrawable)
if (isCorrect) {
isLockClickListeners = true
delay(250)
onSelectedCorrectAnswer()
isLockClickListeners = false
} else {
onSelectedIncorrectAnswer()
}
}
}
}
}

Android-the system recycled the activity I am currently running

I clicked the download button in the song list activity, but after a few seconds, the system recycled my current activity, and did not generate an error, but entered the onDestroy() method.
Specific steps: Start a service, download it in the service, and call back to the page to update the progress through EventBus. I haven't started that activity.
-I want to know why I recycled my current activity
my code is as following:
fun download(){
if (downloadList.size == 0)return
if (current >= downloadList.size){
ToastUtil.setSuccessToast(HomePageActivity.MA,"下载完成!")
current = 0
downloadList.clear()
return
}
DownloadUtils.startDownload(downloadList[current],object : DownloadUtils.FileDownloaderCallback{
override fun pending(task: BaseDownloadTask) {
//status = 6
EventBus.getDefault().postSticky(DownloadingBean(task.status,"",percentage,"", downloadList[current]))
}
override fun start(task: BaseDownloadTask) {
//已经进入下载队列,正在等待下载
//status = 6
EventBus.getDefault().postSticky(DownloadingBean(task.status,"",percentage,"", downloadList[current]))
}
override fun running(task: BaseDownloadTask, speed: Int, current: Int, total: Int) {
//status = 3
// kb/s-> KB/s
Log.d("downloadTAG","running:$speed")
percentage = ((current*1.0 /total)*100).toInt()
EventBus.getDefault().postSticky(DownloadingBean(task.status,"${remainDigit(speed/8.0)}KB/s",percentage,"", downloadList[DownloadBinder.current]))
}
override fun pause(task: BaseDownloadTask) {
Log.d("downloadTAG","pause:${task.status}")
EventBus.getDefault().postSticky(DownloadingBean(task.status,"",percentage,"", downloadList[current]))
}
override fun completed(task: BaseDownloadTask) {
//status = -3
/**
* 除2个1024的到大小MB
* 记得最后保留一位小数*/
val primary = "${downloadList[current].songId}${downloadList[current].songName}"
/**
* 下载完成之后,更新数据库字段*/
PlayListDataBase.getDBInstance().downloadDao().updateComplete(primary,true)
PlayListDataBase.getDBInstance().downloadDao().updatePath(primary,task.path)
PlayListDataBase.getDBInstance().downloadDao().updateUrl(primary,task.url)
val size = remainDigit(task.smallFileTotalBytes*1.0/1024/1024)
PlayListDataBase.getDBInstance().downloadDao().updateSize(primary,"${size}MB")
EventBus.getDefault().postSticky(DownloadingBean(task.status,"",percentage,"", downloadList[current]))
current++
download()
}
override fun failed(task: BaseDownloadTask, message: String?) {
// error = -1
Log.d("downloadTAG","failed:${task.status}")
Log.d("downloadTAG","failed:$message")
EventBus.getDefault().postSticky(DownloadingBean(task.status,"",percentage,message!!, downloadList[current]))
}
override fun exist(task: BaseDownloadTask) {
/**
* 不会进入此处
* 因为外面已经判断过重复项*/
Log.d("downloadTAG","exist:${task.status}")
EventBus.getDefault().postSticky(DownloadingBean(task.status,"",percentage,"", downloadList[current]))
}
})
}

Android-Kotlin: best way to start and stop a cyclic execution of a task, repeating it every few seconds from its conclusion

My goal:
in the view of the fragment I have a button that, when pressed once, launches a method in the viewModel which cyclically calls a suspend function to be repeated every few seconds from its conclusion. Pressing the button again stops this cycle.
My approach:
inside the fragment I set the onclicklistener of the button
binding.demoButton.setOnClickListener {
viewModel.toggleDemo()
}
in the viewModel:
private var startDemo : Boolean = false //I need to know whether to start the loop or stop it
private var isBusy : Boolean = false //I need to know if my task is running or finished
fun toggleDemo(){
val oldValue : Boolean = startDemo
val newValue = !oldValue
startDemo = newValue
if(startDemo){
saveLogLine("** start demo **") //method that passes some log strings to the fragment
startDemo()
}else{
saveLogLine("NO start demo!!")
}
}
private fun startDemo(){
GlobalScope.launch(Dispatchers.IO) {
saveLogLineAsync("before while loop")
while(startDemo){
if(!isBusy){
isBusy = true
Handler(Looper.getMainLooper()).postDelayed({
runBlocking(Dispatchers.IO) {
saveLogLineAsync("inside runBlocking")
initDemo()
}
isBusy = false
saveLogLineAsync("inside handler")
}, 5000)
}
}
saveLogLineAsync("after while loop")
}
}
private suspend fun initDemo(){ //my task
}
Is there a more elegant way to do this?
I would have liked to use a Service () or a BroadcastReceiver () but in both cases I would not know how to make them communicate with the fragment or with the viewModel (more precisely, they should be able to use the 2 methods 'saveLogLineAsync' and 'intDemo')
You can simplify your code with this:
private var demoRunning = false
private var demoJob: Job? = null
fun toggleDemo() {
if (!demoRunning) {
startDemo()
} else {
demoJob?.cancel()
}
demoRunning = !demoRunning
}
private fun startDemo() {
demoJob = viewModelScope.launch(Dispatchers.IO) {
while (true) {
initDemo()
delay(5000)
}
}
}
private suspend fun initDemo() { //my task
Log.e("INIT DEMO", "initDemo Ran")
}

Progressbar not updating after Redux Event

I know this question has been asked quite often here, but non of the answers helped me.
I am writting a gallery app with a thumbes-regeneration feature. In oder to show the progress i added the progressbar which should count the number of created thumbnails. After each finished thumbnail-generation i dispatch a Redux event and listen to it in my Fragement, in order to change the progressbar.
Generating all thumbnails for all visible photos/videos
private fun onMenuRefreshThumbs(activity: Activity) {
val mediaPath = Redux.store.currentState.mediaPath
val fileRepository = FileRepository(context = activity, mediaPath = mediaPath)
activity.runOnUiThread {
fileRepository.regenerateThumbs(activity)
}
}
Functions inside the above used FileRepository:
fun regenerateThumbs(context: Context) {
val success = File(getAbsoluteThumbsDir(context, mediaPath)).deleteRecursively()
getMediaItems()
}
fun getMediaItems(): MediaItemList {
val success = File(thumbPath).mkdirs()
val isThumbsEmpty = File(thumbPath).listFiles().isEmpty()
val mediaFileList = File(mediaPath).listFiles().
.sortedByDescending { it.lastModified() }
val list = MediaItemList()
mediaFileList.apply {
forEach {
list.add(MediaItem(it.name, 0, 0))
if (isThumbsEmpty) {
getOrCreateThumb(it)
Redux.store.dispatch(FileUpdateAction(it))
}
}
}
return list
}
Subscribing to Redux in the Fragement:
private fun subscribeRedux() {
val handler = Handler(Looper.getMainLooper())
val activity = requireActivity()
subscriber = { state: AppState ->
when (state.action) {
...
is ClearSelection -> {
progressCounter = 0
// fragment_gallery_progress.visibility = View.GONE
}
is FileUpdateAction -> {
Handler().post {
progressCounter++
fragment_gallery_progress.visibility = View.VISIBLE
fragment_gallery_progress.progress = progressCounter
// fragment_gallery_progress.invalidate()
log.d("test: Thumb Index $progressCounter ${state.action.mediaItem.name} was created")
}
Unit
}
}
}.apply {
Redux.store.subscribe(this)
}
}
I tried all difference version of calling a thread in both cases. But no matter if its done with the handler or by activity.runOnUiThread, the progressbar never changes untill all thumbs are finished and the progressbar jumps from 0 to the maximum number. I can see the logs which are written in the right time, but not the progressbar changing.
I could fix my problem with following steps:
Removing the runOnUiThread() call
private fun onMenuRefreshThumbs(activity: Activity) {
val mediaPath = Redux.store.currentState.mediaPath
val fileRepository = FileRepository(context = activity, mediaPath = mediaPath)
fileRepository.regenerateThumbs(activity)
}
Adding a thread for each Thumbs-Generation:
fun getMediaItems(): MediaItemList {
val success = File(thumbPath).mkdirs()
val isThumbsEmpty = File(thumbPath).listFiles().isEmpty()
val mediaFileList = File(mediaPath).listFiles().
.sortedByDescending { it.lastModified() }
val list = MediaItemList()
mediaFileList.apply {
forEach {
list.add(MediaItem(it.name, 0, 0))
if (isThumbsEmpty) {
Thread {
getOrCreateThumb(it)
Redux.store.dispatch(FileUpdateAction(it))
}.start()
}
}
...

ExoPlayer: Custom AudioProcessor - Equalizer

I'm working on an ExoPlayer based media player for Android, and I'm attempting to write my own Equalizer.
I've looked fairly deeply into ExoPlayer, and I believe the best place to manipulate samples in order to apply Equalier changes, is in a custom AudioProcessor.
I've used ChannelMappingAudioProcessor as a starting point, and cloned what I think are the relevant aspects:
class EqualizerAudioProcessor : BaseAudioProcessor() {
private lateinit var outputChannels: IntArray
override fun configure(sampleRateHz: Int, channelCount: Int, encoding: Int): Boolean {
outputChannels = IntArray(channelCount)
for (i in 0 until channelCount) {
outputChannels[i] = i
}
return true
}
override fun isActive(): Boolean {
return true
}
override fun getOutputChannelCount(): Int {
return outputChannels.size
}
override fun queueInput(inputBuffer: ByteBuffer) {
var position = inputBuffer.position()
val limit = inputBuffer.limit()
val frameCount = (limit - position) / (2 * channelCount)
val outputSize = frameCount * outputChannels.size * 2
val buffer = replaceOutputBuffer(outputSize)
while (position < limit) {
for (element in outputChannels) {
var sample = inputBuffer.getShort(position + 2 * element)
// Todo: Manipulate sample
buffer.putShort(sample)
}
position += channelCount * 2
}
inputBuffer.position(limit)
buffer.flip()
}
override fun onReset() {
}
}
It seems that if I enable this AudioProcessor, playback doesn't occur (it seems stuck in a 'paused state', as if the samples aren't being passed along, and interestingly, queueInput() is not called. If I disable the AudioProcessor, playback works fine.
I'm hoping someone can help me understand if I'm making a mistake here, and how to get this working.
For reference, the ExoPlayer instance is initialised like so:
private fun initPlayer(context: Context): ExoPlayer {
val audioProcessor = EqualizerAudioProcessor()
val renderersFactory = object : DefaultRenderersFactory(context) {
override fun buildAudioProcessors(): Array<AudioProcessor> {
return arrayOf(audioProcessor)
}
}
val player: SimpleExoPlayer = ExoPlayerFactory.newSimpleInstance(
context,
renderersFactory,
DefaultTrackSelector(),
DefaultLoadControl()
)
player.addListener(object : Player.EventListener {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
callback?.onPlayStateChanged(playWhenReady)
}
})
return player
}
Thanks in advance
The problem is that you must call setInputFormat() in configure() of the AudioProcessor, or queueInput() will not be called.

Categories

Resources