ExoPlayer: Custom AudioProcessor - Equalizer - android

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.

Related

Kotlin Dynamic Array implementation with generics

I'm learning data structures and trying to implement a dynamic array from scratch in Kotlin using generics. I came up with the following implementation using a MutableList but that feels like cheating 😅. Am I doing this correctly or is there another/better way that allows me to learn by implementing the individual operations manually? What's the usual way others go about this?
class DynamicArray<T>(
private var values: MutableList<T>
) {
var length: Int = values.size
private set
var isEmpty: Boolean = length > 0
private set
fun getValues() = values
// O(1) time complexity
fun lookup(index: Int) = values[index]
// O(1) time complexity
fun push(item: T): MutableList<T> {
values.add(length, item)
length++
return values
}
// O(n) time complexity because we have to shift remaining items
fun remove(item: T): MutableList<T> {
values.remove(item)
length--
return values
}
}
Specifically you're saying that in order for your DynamicArray to exist, you need an already implemented mutable list (dynamic array) structure to rely upon.
So if you're trying to learn how these data structures are made, you should try to make one instead of using one. At the moment you're just delegating the difficult parts to someone else's work.
Try implementing this using only the Array type to make an ArrayList style implementation, or try not using an Array at all to implement a LinkedList type structure.
Here's a quick implementation that uses Array as the underlying DS.
It implements a MutableIterable which is basically an Iterable that also allows you to remove an element while iterating.
#Suppress("UNCHECKED_CAST")
class DynamicArray<T>(
private var size: Int,
private val expansionFactor: Float = 2f,
private val init: (Int) -> T) : MutableIterable<T> {
var capacity: Int = size
private set
private var arr: Array<Any?> = Array(size, init)
inner class DynamicArrayIterator: MutableIterator<T>{
val iterator = arr.iterator()
var index = -1
override fun hasNext(): Boolean {
return index<size-1
}
override fun next(): T {
if(iterator.hasNext()) index++
else throw NoSuchElementException()
return iterator.next() as T
}
override fun remove() {
removeAt(index--)
}
}
override fun iterator(): MutableIterator<T> = DynamicArrayIterator()
fun size(): Int = size
//O(1)
fun get(index: Int): T {
if(index > size-1) throw IndexOutOfBoundsException()
return arr[index] as T
}
//O(1)
fun set(index: Int, element: T) {
if(index > size-1) throw IndexOutOfBoundsException()
arr[index] = element
}
//O(1) amortized
fun add(element: T){
if(size == capacity){
capacity = (expansionFactor*size).toInt()
val newArr = Array<Any?>(capacity, init = init)
arr.forEachIndexed { index, any -> newArr[index] = any as T }
arr = newArr
}
arr[size] = element
size++
}
//O(n)
fun removeAt(index: Int): T{
if(index > size-1) throw IndexOutOfBoundsException()
val element = arr[index] as T
for( i in index until size){
arr[i] = arr[i+1]
}
size--
return element
}
//O(n)
fun remove(element: T): Boolean{
for(i in 0 until size){
if(arr[i] == element) {
removeAt(i)
return true
}
}
return false
}
//O(n)
override fun toString(): String {
val stringBuilder = StringBuilder()
stringBuilder.append("[")
var index = 0
do{
if (index in 1 until size)
stringBuilder.append(", ")
stringBuilder.append(arr[index])
index++
} while (index < size)
return stringBuilder.append("]").toString()
}
}

CameraX change from alpha-04 to beta-01 broke the code

I have added CameraX to my ongoing development app a while ago. I know it was in alpha but I was ready to make the change when beta or final release will be available.
So I started working on it today. I have updated from
implementation 'androidx.camera:camera-core:1.0.0-alpha04'
implementation 'androidx.camera:camera-camera2:1.0.0-alpha04'
to this:
implementation 'androidx.camera:camera-core:1.0.0-beta01'
implementation 'androidx.camera:camera-camera2:1.0.0-beta01'
implementation 'androidx.camera:camera-lifecycle:1.0.0-beta01'
My Previous Working Code (alpha-04):
class ScannerX : AppCompatActivity() {
private lateinit var context: Context
var isOtpAuthCode = true
private val immersiveFlagTimeout = 500L
private val flagsFullscreen = View.SYSTEM_UI_FLAG_LOW_PROFILE or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
private var preview: Preview? = null
private var lensFacing = CameraX.LensFacing.BACK
private var imageAnalyzer: ImageAnalysis? = null
private lateinit var analyzerThread: HandlerThread
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scanner_x)
context = this
btnCancel.setOnClickListener {
finish()
}
analyzerThread = if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
HandlerThread("BarcodeFirebaseAnalyzer").apply { start() }
} else {
HandlerThread("BarcodeZxingAnalyzer").apply { start() }
}
Dexter.withActivity(this)
.withPermissions(Manifest.permission.CAMERA)
.withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
textureView.post {
val metrics = DisplayMetrics().also { textureView.display.getRealMetrics(it) }
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
val previewConfig = PreviewConfig.Builder().apply {
setLensFacing(lensFacing)
// We request aspect ratio but no resolution to let CameraX optimize our use cases
setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation, we will have to call this again if rotation changes
// during the lifecycle of this use case
setTargetRotation(textureView.display.rotation)
}.build()
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setLensFacing(lensFacing)
// Use a worker thread for image analysis to prevent preview glitches
setCallbackHandler(Handler(analyzerThread.looper))
// In our analysis, we care more about the latest image than analyzing *every* image
setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
// Set initial target rotation, we will have to call this again if rotation changes
// during the lifecycle of this use case
setTargetRotation(textureView.display.rotation)
}.build()
preview = AutoFitPreviewBuilder.build(previewConfig, textureView)
imageAnalyzer = ImageAnalysis(analyzerConfig).apply {
analyzer = if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
BarcodeFirebaseAnalyzer { qrCode ->
if (isOtpAuthCode) {
if (qrCode.startsWith("otpauth")) {
toAddAuth(qrCode)
}
} else {
toAddAuth(qrCode)
}
}
} else {
BarcodeZxingAnalyzer { qrCode ->
if (isOtpAuthCode) {
if (qrCode.startsWith("otpauth")) {
toAddAuth(qrCode)
}
} else {
toAddAuth(qrCode)
}
}
}
}
// Apply declared configs to CameraX using the same lifecycle owner
CameraX.bindToLifecycle(this#ScannerX, preview, imageAnalyzer)
}
}
override fun onPermissionRationaleShouldBeShown(permissions: MutableList<PermissionRequest>?, token: PermissionToken?) {
//
}
}).check()
}
override fun onStart() {
super.onStart()
// Before setting full screen flags, we must wait a bit to let UI settle; otherwise, we may
// be trying to set app to immersive mode before it's ready and the flags do not stick
textureView.postDelayed({
textureView.systemUiVisibility = flagsFullscreen
}, immersiveFlagTimeout)
}
override fun onDestroy() {
analyzerThread.quit()
super.onDestroy()
}
private fun toAddAuth(scannedCode: String) {
if (CameraX.isBound(imageAnalyzer)) {
CameraX.unbind(imageAnalyzer)
}
val intent = Intent()
intent.putExtra("scanResult", scannedCode)
setResult(RESULT_OK, intent)
finish()
}
companion object {
private const val RESULT_OK = 666
}
}
And the code I have changed is as follows (beta-01):
class ScannerX : AppCompatActivity() {
private lateinit var context: Context
var isOtpAuthCode = true
private val immersiveFlagTimeout = 500L
private val flagsFullscreen = View.SYSTEM_UI_FLAG_LOW_PROFILE or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
private var preview: Preview? = null
private var lensFacing = CameraSelector.DEFAULT_BACK_CAMERA
private var imageAnalyzer: ImageAnalysis? = null
private lateinit var analysisExecutor: ExecutorService
private lateinit var processCameraProvider: ListenableFuture<ProcessCameraProvider>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scanner_x)
context = this
btnCancel.setOnClickListener {
finish()
}
Dexter.withActivity(this)
.withPermissions(Manifest.permission.CAMERA)
.withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
textureView.post {
analysisExecutor = Executors.newSingleThreadExecutor()
processCameraProvider = ProcessCameraProvider.getInstance(context)
preview = Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setTargetRotation(textureView.display.rotation)
.build()
imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetRotation(textureView.display.rotation)
.build()
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
imageAnalyzer?.apply {
setAnalyzer(analysisExecutor, BarcodeFirebaseAnalyzer { qrCode ->
if (isOtpAuthCode) {
if (qrCode.startsWith("otpauth")) {
toAddAuth(qrCode)
}
} else {
toAddAuth(qrCode)
}
})
}
} else {
imageAnalyzer?.apply {
setAnalyzer(analysisExecutor, BarcodeZxingAnalyzer { qrCode ->
if (isOtpAuthCode) {
if (qrCode.startsWith("otpauth")) {
toAddAuth(qrCode)
}
} else {
toAddAuth(qrCode)
}
})
}
}
processCameraProvider.get().bindToLifecycle(this#ScannerX, lensFacing, imageAnalyzer)
}
}
override fun onPermissionRationaleShouldBeShown(permissions: MutableList<PermissionRequest>?, token: PermissionToken?) {
//
}
}).check()
}
override fun onStart() {
super.onStart()
// Before setting full screen flags, we must wait a bit to let UI settle; otherwise, we may
// be trying to set app to immersive mode before it's ready and the flags do not stick
textureView.postDelayed({
textureView.systemUiVisibility = flagsFullscreen
}, immersiveFlagTimeout)
}
override fun onDestroy() {
if (!analysisExecutor.isShutdown) {
analysisExecutor.shutdown()
}
super.onDestroy()
}
private fun toAddAuth(scannedCode: String) {
/*if (CameraX.isBound(imageAnalyzer)) {
CameraX.unbind(imageAnalyzer)
}*/
val intent = Intent()
intent.putExtra("scanResult", scannedCode)
setResult(RESULT_OK, intent)
finish()
}
companion object {
private const val RESULT_OK = 666
}
}
After I upgraded there were so many changes in library and now I cant make it work.
I also cant use Google Provided AutoFitPreview Class along with initial alpha release of this library. This was not necessary even with alpha04 since the only problem without this class was camera view little bit stretched out but scanning and analyzing worked properly.
/**
* Builder for [Preview] that takes in a [WeakReference] of the view finder and [PreviewConfig],
* then instantiates a [Preview] which automatically resizes and rotates reacting to config changes.
*/
class AutoFitPreviewBuilder private constructor(config: PreviewConfig, viewFinderRef: WeakReference<TextureView>) {
/** Public instance of preview use-case which can be used by consumers of this adapter */
val useCase: Preview
/** Internal variable used to keep track of the use case's output rotation */
private var bufferRotation: Int = 0
/** Internal variable used to keep track of the view's rotation */
private var viewFinderRotation: Int? = null
/** Internal variable used to keep track of the use-case's output dimension */
private var bufferDimens: Size = Size(0, 0)
/** Internal variable used to keep track of the view's dimension */
private var viewFinderDimens: Size = Size(0, 0)
/** Internal variable used to keep track of the view's display */
private var viewFinderDisplay: Int = -1
/** Internal reference of the [DisplayManager] */
private lateinit var displayManager: DisplayManager
/**
* We need a display listener for orientation changes that do not trigger a configuration
* change, for example if we choose to override config change in manifest or for 180-degree
* orientation changes.
*/
private val displayListener = object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) = Unit
override fun onDisplayRemoved(displayId: Int) = Unit
override fun onDisplayChanged(displayId: Int) {
val viewFinder = viewFinderRef.get() ?: return
if (displayId == viewFinderDisplay) {
val display = displayManager.getDisplay(displayId)
val rotation = getDisplaySurfaceRotation(display)
updateTransform(viewFinder, rotation, bufferDimens, viewFinderDimens)
}
}
}
init {
// Make sure that the view finder reference is valid
val viewFinder = viewFinderRef.get() ?:
throw IllegalArgumentException("Invalid reference to view finder used")
// Initialize the display and rotation from texture view information
viewFinderDisplay = viewFinder.display.displayId
viewFinderRotation = getDisplaySurfaceRotation(viewFinder.display) ?: 0
// Initialize public use-case with the given config
useCase = Preview(config)
// Every time the view finder is updated, recompute layout
useCase.onPreviewOutputUpdateListener = Preview.OnPreviewOutputUpdateListener {
val viewFinderI = viewFinderRef.get() ?: return#OnPreviewOutputUpdateListener
Log.d(TAG, "Preview output changed. " +
"Size: ${it.textureSize}. Rotation: ${it.rotationDegrees}")
// To update the SurfaceTexture, we have to remove it and re-add it
val parent = viewFinderI.parent as ViewGroup
parent.removeView(viewFinderI)
parent.addView(viewFinderI, 0)
// Update internal texture
viewFinderI.surfaceTexture = it.surfaceTexture
// Apply relevant transformations
bufferRotation = it.rotationDegrees
val rotation = getDisplaySurfaceRotation(viewFinderI.display)
updateTransform(viewFinderI, rotation, it.textureSize, viewFinderDimens)
}
// Every time the provided texture view changes, recompute layout
viewFinder.addOnLayoutChangeListener { view, left, top, right, bottom, _, _, _, _ ->
val viewFinderII = view as TextureView
val newViewFinderDimens = Size(right - left, bottom - top)
Log.d(TAG, "View finder layout changed. Size: $newViewFinderDimens")
val rotation = getDisplaySurfaceRotation(viewFinderII.display)
updateTransform(viewFinderII, rotation, bufferDimens, newViewFinderDimens)
}
// Every time the orientation of device changes, recompute layout
// NOTE: This is unnecessary if we listen to display orientation changes in the camera
// fragment and call [Preview.setTargetRotation()] (like we do in this sample), which will
// trigger [Preview.OnPreviewOutputUpdateListener] with a new
// [PreviewOutput.rotationDegrees]. CameraX Preview use case will not rotate the frames for
// us, it will just tell us about the buffer rotation with respect to sensor orientation.
// In this sample, we ignore the buffer rotation and instead look at the view finder's
// rotation every time [updateTransform] is called, which gets triggered by
// [CameraFragment] display listener -- but the approach taken in this sample is not the
// only valid one.
displayManager = viewFinder.context
.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
displayManager.registerDisplayListener(displayListener, null)
// Remove the display listeners when the view is detached to avoid holding a reference to
// it outside of the Fragment that owns the view.
// NOTE: Even though using a weak reference should take care of this, we still try to avoid
// unnecessary calls to the listener this way.
viewFinder.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(view: View?) =
displayManager.registerDisplayListener(displayListener, null)
override fun onViewDetachedFromWindow(view: View?) =
displayManager.unregisterDisplayListener(displayListener)
})
}
/** Helper function that fits a camera preview into the given [TextureView] */
private fun updateTransform(textureView: TextureView?, rotation: Int?, newBufferDimens: Size, newViewFinderDimens: Size) {
// This should not happen anyway, but now the linter knows
val textureViewI = textureView ?: return
if (rotation == viewFinderRotation &&
Objects.equals(newBufferDimens, bufferDimens) &&
Objects.equals(newViewFinderDimens, viewFinderDimens)) {
// Nothing has changed, no need to transform output again
return
}
if (rotation == null) {
// Invalid rotation - wait for valid inputs before setting matrix
return
} else {
// Update internal field with new inputs
viewFinderRotation = rotation
}
if (newBufferDimens.width == 0 || newBufferDimens.height == 0) {
// Invalid buffer dimens - wait for valid inputs before setting matrix
return
} else {
// Update internal field with new inputs
bufferDimens = newBufferDimens
}
if (newViewFinderDimens.width == 0 || newViewFinderDimens.height == 0) {
// Invalid view finder dimens - wait for valid inputs before setting matrix
return
} else {
// Update internal field with new inputs
viewFinderDimens = newViewFinderDimens
}
val matrix = Matrix()
Log.d(TAG, "Applying output transformation.\n" +
"View finder size: $viewFinderDimens.\n" +
"Preview output size: $bufferDimens\n" +
"View finder rotation: $viewFinderRotation\n" +
"Preview output rotation: $bufferRotation")
// Compute the center of the view finder
val centerX = viewFinderDimens.width / 2f
val centerY = viewFinderDimens.height / 2f
// Correct preview output to account for display rotation
matrix.postRotate(-viewFinderRotation!!.toFloat(), centerX, centerY)
// Buffers are rotated relative to the device's 'natural' orientation: swap width and height
val bufferRatio = bufferDimens.height / bufferDimens.width.toFloat()
val scaledWidth: Int
val scaledHeight: Int
// Match longest sides together -- i.e. apply center-crop transformation
if (viewFinderDimens.width > viewFinderDimens.height) {
scaledHeight = viewFinderDimens.width
scaledWidth = (viewFinderDimens.width * bufferRatio).roundToInt()
} else {
scaledHeight = viewFinderDimens.height
scaledWidth = (viewFinderDimens.height * bufferRatio).roundToInt()
}
// Compute the relative scale value
val xScale = scaledWidth / viewFinderDimens.width.toFloat()
val yScale = scaledHeight / viewFinderDimens.height.toFloat()
// Scale input buffers to fill the view finder
matrix.preScale(xScale, yScale, centerX, centerY)
// Finally, apply transformations to our TextureView
textureViewI.setTransform(matrix)
}
companion object {
private val TAG = AutoFitPreviewBuilder::class.java.simpleName
/** Helper function that gets the rotation of a [Display] in degrees */
fun getDisplaySurfaceRotation(display: Display?) = when(display?.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> null
}
/**
* Main entry point for users of this class: instantiates the adapter and returns an instance
* of [Preview] which automatically adjusts in size and rotation to compensate for
* config changes.
*/
fun build(config: PreviewConfig, viewFinder: TextureView) =
AutoFitPreviewBuilder(config, WeakReference(viewFinder)).useCase
}
}
Please Help

Android Data Binding Problem: Missing return statement in generated code while using with a custom view?

I am using data binding in my current application, and so far so good. However, when I tried to use it with a custom data binding adapter I wrote for my custom view, I got an error from auto generated file as the title says, missing return statement. This error does not occur when I used this data binding on only one view, but more than one gives the error. Below are my custom view and adapters, and usage in xml file. I already checked the answer of kinda duplicated question but it doesn't worked in my case, and does not have enough explanation.
class NeonTextView(context: Context, attrs: AttributeSet) : TextView(context, attrs) {
private val drawableClear: Drawable?
get() = ContextCompat.getDrawable(context, R.drawable.ic_clear)
lateinit var actionMethod: () -> Unit
lateinit var clearMethod: () -> Unit
var hasActionMethod = false
var hasClearMethod = false
init {
setupAttributes(attrs)
}
private fun setupAttributes(attrs: AttributeSet) {
val typedArray =
context.theme.obtainStyledAttributes(attrs, R.styleable.NeonTextView, 0, 0)
hasActionMethod = typedArray.getBoolean(
R.styleable.NeonTextView_hasActionMethod,
false
)
hasClearMethod = typedArray.getBoolean(
R.styleable.NeonTextView_hasClearMethod,
false
)
typedArray.recycle()
}
override fun onTextChanged(
text: CharSequence?,
start: Int,
lengthBefore: Int,
lengthAfter: Int
) {
text?.let { text ->
drawableClear?.let {
it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
}
setCompoundDrawablesWithIntrinsicBounds(
null,
null,
if (text.isNotEmpty()) drawableClear!! else null,
null
)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
return when (it.action) {
ACTION_DOWN -> return true
ACTION_UP -> {
if (compoundDrawables[2] == null && hasActionMethod) {
actionMethod()
} else {
if (it.x > (width - paddingRight - compoundDrawables[2]!!.intrinsicWidth)) {
if (hasClearMethod) clearMethod()
text = ""
} else {
if (hasActionMethod) actionMethod()
}
}
performClick()
true
}
else -> false
}
}.run {
return false
}
}
override fun performClick(): Boolean {
super.performClick()
return true
}
}
And here is my binding adapters for binding methods that are used inside this custom text view:
#BindingAdapter("actionMethod")
fun NeonTextView.setActionMethod(actionMethod: () -> Unit) {
this.actionMethod = actionMethod
this.hasActionMethod = true
}
#BindingAdapter("clearMethod")
fun NeonTextView.setClearMethod(clearMethod: () -> Unit) {
this.clearMethod = clearMethod
this.hasClearMethod = true
}
And here is how I applied inside xml file:
<com.android.platform.NeonTextView
android:id="#+id/textViewSectionField"
style="#style/HeaderTextView.SubHeader"
app:hasActionMethod="true"
app:actionMethod="#{() -> viewModel.getDepartmentList()}"/>
Any ideas why I am getting error from generated file when I used this binding in more than one view inside xml?
Thanks in advance.
The problem is with Java<->Kotlin compatibility. In Kotlin if you declare function
interface Func {
fun test(): Unit {
}
}
And use it from java
class FuncImpl implements Func {
#Override
public Unit test() {
return Unit.INSTANCE;
}
}
Note, please, that in that case in java code you need return statement. The same is for lambdas.
So when you set up lambda from xml using databinding, it's treated as java's lambda, so generated code wasn't correctly processed in that case.

Turning listeners into kotlin coroutine channels

I have several functions that I want to use to do pipelines with Channels. The main one is globalLayouts, where I create a Channel from the framework listener:
fun View.globalLayouts(): ReceiveChannel<View> =
Channel<View>().apply {
val view = this#globalLayouts
val listener = ViewTreeObserver.OnGlobalLayoutListener {
offer(view)
}
invokeOnClose {
viewTreeObserver.removeOnGlobalLayoutListener(listener)
}
viewTreeObserver.addOnGlobalLayoutListener(listener)
}
#UseExperimental(InternalCoroutinesApi::class)
fun <E> ReceiveChannel<E>.distinctUntilChanged(context: CoroutineContext = Dispatchers.Unconfined): ReceiveChannel<E> =
GlobalScope.produce(context, onCompletion = consumes()) {
var last: Any? = Any()
consumeEach {
if (it != last) {
send(it)
last = it
}
}
}
fun View.keyboardVisibility(): ReceiveChannel<KeyboardVisibility> {
val rect = Rect()
return globalLayouts()
.map {
getWindowVisibleDisplayFrame(rect)
when (rect.height()) {
height -> KeyboardVisibility.HIDDEN
else -> KeyboardVisibility.SHOWN
}
}
.distinctUntilChanged()
}
I have a CoroutineScope called alive:
val ControllerLifecycle.alive: CoroutineScope
get() {
val scope = MainScope()
addLifecycleListener(object : Controller.LifecycleListener() {
override fun preDestroyView(controller: Controller, view: View) {
removeLifecycleListener(this)
scope.cancel()
}
})
return scope
}
then I do:
alive.launch {
root.keyboardVisibility().consumeEach {
appbar.setExpanded(it == KeyboardVisibility.HIDDEN)
}
}
This code starts working just fine, but I get
kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelled}#811031f
once my alive scope is destroyed. Right after invokeOnClose is called in globalLayouts. What am I doing wrong and how do I debug this?
Figured it out - the code works fine, but
viewTreeObserver.removeOnGlobalLayoutListener(listener)
is bugged for CoordinatorLayout.

Android AsyncTask is not updating Progress Bar

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)

Categories

Resources