Spin off audio focus into seperate object
Move the code responsible for audio focus into a seperate object to reduce the amount of code in PlaybackService.
This commit is contained in:
parent
3851c59f4b
commit
27d39a1364
5 changed files with 151 additions and 139 deletions
|
@ -16,7 +16,7 @@ import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
|
|
||||||
// SettingsManager is lazy-initted to prevent it from being used before its initialized.
|
// SettingsManager is lazy-initted to prevent it from being used before its initialized.
|
||||||
val settingsManager: SettingsManager by lazy {
|
private val settingsManager: SettingsManager by lazy {
|
||||||
SettingsManager.getInstance()
|
SettingsManager.getInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
107
app/src/main/java/org/oxycblt/auxio/playback/AudioReactor.kt
Normal file
107
app/src/main/java/org/oxycblt/auxio/playback/AudioReactor.kt
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package org.oxycblt.auxio.playback
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.media.AudioManager
|
||||||
|
import androidx.core.animation.addListener
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.media.AudioFocusRequestCompat
|
||||||
|
import androidx.media.AudioManagerCompat
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||||
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object that manages the AudioFocus state.
|
||||||
|
* Adapted from NewPipe (https://github.com/TeamNewPipe/NewPipe)
|
||||||
|
*/
|
||||||
|
class AudioReactor(
|
||||||
|
context: Context,
|
||||||
|
private val player: SimpleExoPlayer
|
||||||
|
) : AudioManager.OnAudioFocusChangeListener {
|
||||||
|
private val audioManager = ContextCompat.getSystemService(
|
||||||
|
context, AudioManager::class.java
|
||||||
|
) ?: error("Cannot obtain AudioManager.")
|
||||||
|
|
||||||
|
private val settingsManager = SettingsManager.getInstance()
|
||||||
|
private val playbackManager = PlaybackStateManager.getInstance()
|
||||||
|
|
||||||
|
private val request = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
|
||||||
|
.setWillPauseWhenDucked(true)
|
||||||
|
.setOnAudioFocusChangeListener(this)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private var pauseWasFromAudioFocus = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the android system for audio focus
|
||||||
|
*/
|
||||||
|
fun requestFocus() {
|
||||||
|
AudioManagerCompat.requestAudioFocus(audioManager, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy this object and abandon its audio focus request, should be ran on destruction to
|
||||||
|
* prevent memory leaks.
|
||||||
|
*/
|
||||||
|
fun destroy() {
|
||||||
|
AudioManagerCompat.abandonAudioFocusRequest(audioManager, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAudioFocusChange(focusChange: Int) {
|
||||||
|
when (focusChange) {
|
||||||
|
AudioManager.AUDIOFOCUS_GAIN -> onGain()
|
||||||
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> onDuck()
|
||||||
|
AudioManager.AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> onLoss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onGain() {
|
||||||
|
if (settingsManager.doAudioFocus) {
|
||||||
|
if (player.volume == VOLUME_DUCK && playbackManager.isPlaying) {
|
||||||
|
unduck()
|
||||||
|
} else if (pauseWasFromAudioFocus) {
|
||||||
|
playbackManager.setPlaying(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseWasFromAudioFocus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onLoss() {
|
||||||
|
if (settingsManager.doAudioFocus && playbackManager.isPlaying) {
|
||||||
|
pauseWasFromAudioFocus = true
|
||||||
|
playbackManager.setPlaying(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDuck() {
|
||||||
|
if (settingsManager.doAudioFocus) {
|
||||||
|
player.volume = VOLUME_DUCK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unduck() {
|
||||||
|
player.volume = VOLUME_DUCK
|
||||||
|
|
||||||
|
ValueAnimator().apply {
|
||||||
|
setFloatValues(VOLUME_DUCK, VOLUME_FULL)
|
||||||
|
duration = DUCK_DURATION
|
||||||
|
addListener(
|
||||||
|
onStart = { player.volume = VOLUME_DUCK },
|
||||||
|
onCancel = { player.volume = VOLUME_FULL },
|
||||||
|
onEnd = { player.volume = VOLUME_FULL }
|
||||||
|
)
|
||||||
|
addUpdateListener {
|
||||||
|
player.volume = it.animatedValue as Float
|
||||||
|
}
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val VOLUME_DUCK = 0.2f
|
||||||
|
private const val DUCK_DURATION = 1500L
|
||||||
|
private const val VOLUME_FULL = 1.0f
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package org.oxycblt.auxio.playback
|
package org.oxycblt.auxio.playback
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
|
@ -16,11 +15,7 @@ import android.os.Parcelable
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.core.animation.addListener
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.media.AudioFocusRequestCompat
|
|
||||||
import androidx.media.AudioManagerCompat
|
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException
|
import com.google.android.exoplayer2.ExoPlaybackException
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
|
@ -60,8 +55,8 @@ import org.oxycblt.auxio.settings.SettingsManager
|
||||||
* - Audio Focus
|
* - Audio Focus
|
||||||
* - Headset management
|
* - Headset management
|
||||||
*
|
*
|
||||||
* This service relies on [PlaybackStateManager.Callback], so therefore there's no need to bind
|
* This service relies on [PlaybackStateManager.Callback] and [SettingsManager.Callback],
|
||||||
* to it to deliver commands.
|
* so therefore there's no need to bind to it to deliver commands.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Callback, SettingsManager.Callback {
|
class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Callback, SettingsManager.Callback {
|
||||||
|
@ -72,7 +67,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
|
|
||||||
private lateinit var mediaSession: MediaSessionCompat
|
private lateinit var mediaSession: MediaSessionCompat
|
||||||
private lateinit var systemReceiver: SystemEventReceiver
|
private lateinit var systemReceiver: SystemEventReceiver
|
||||||
private val audioAttributes = AudioAttributes.Builder()
|
private val playerAttributes = AudioAttributes.Builder()
|
||||||
.setUsage(C.USAGE_MEDIA)
|
.setUsage(C.USAGE_MEDIA)
|
||||||
.setContentType(C.CONTENT_TYPE_MUSIC)
|
.setContentType(C.CONTENT_TYPE_MUSIC)
|
||||||
.build()
|
.build()
|
||||||
|
@ -80,7 +75,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
private lateinit var notificationManager: NotificationManager
|
private lateinit var notificationManager: NotificationManager
|
||||||
private lateinit var notification: NotificationCompat.Builder
|
private lateinit var notification: NotificationCompat.Builder
|
||||||
|
|
||||||
private lateinit var audioFocusManager: AudioFocusManager
|
private lateinit var audioReactor: AudioReactor
|
||||||
private var isForeground = false
|
private var isForeground = false
|
||||||
|
|
||||||
private val serviceJob = Job()
|
private val serviceJob = Job()
|
||||||
|
@ -104,18 +99,12 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
|
|
||||||
// --- PLAYER SETUP ---
|
// --- PLAYER SETUP ---
|
||||||
|
|
||||||
player.addListener(this)
|
player.apply {
|
||||||
|
addListener(this@PlaybackService)
|
||||||
// Set up AudioFocus/AudioAttributes
|
setAudioAttributes(playerAttributes, false)
|
||||||
player.setAudioAttributes(
|
|
||||||
audioAttributes, false
|
|
||||||
)
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
player.experimentalSetOffloadSchedulingEnabled(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
audioFocusManager = AudioFocusManager()
|
audioReactor = AudioReactor(this, player)
|
||||||
|
|
||||||
// --- SYSTEM RECEIVER SETUP ---
|
// --- SYSTEM RECEIVER SETUP ---
|
||||||
|
|
||||||
|
@ -153,13 +142,11 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
// --- NOTIFICATION SETUP ---
|
// --- NOTIFICATION SETUP ---
|
||||||
|
|
||||||
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
notification = notificationManager.createMediaNotification(this, mediaSession)
|
notification = notificationManager.createMediaNotification(this, mediaSession)
|
||||||
|
|
||||||
// --- PLAYBACKSTATEMANAGER SETUP ---
|
// --- PLAYBACKSTATEMANAGER SETUP ---
|
||||||
|
|
||||||
playbackManager.resetHasPlayedStatus()
|
playbackManager.resetHasPlayedStatus()
|
||||||
|
|
||||||
playbackManager.addCallback(this)
|
playbackManager.addCallback(this)
|
||||||
|
|
||||||
if (playbackManager.song != null || playbackManager.isRestored) {
|
if (playbackManager.song != null || playbackManager.isRestored) {
|
||||||
|
@ -181,7 +168,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
// Release everything that could cause a memory leak if left around
|
// Release everything that could cause a memory leak if left around
|
||||||
player.release()
|
player.release()
|
||||||
mediaSession.release()
|
mediaSession.release()
|
||||||
audioFocusManager.destroy()
|
audioReactor.destroy()
|
||||||
playbackManager.removeCallback(this)
|
playbackManager.removeCallback(this)
|
||||||
settingsManager.removeCallback(this)
|
settingsManager.removeCallback(this)
|
||||||
|
|
||||||
|
@ -261,7 +248,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
if (isPlaying && !player.isPlaying) {
|
if (isPlaying && !player.isPlaying) {
|
||||||
player.play()
|
player.play()
|
||||||
notification.updatePlaying(this)
|
notification.updatePlaying(this)
|
||||||
audioFocusManager.requestFocus()
|
audioReactor.requestFocus()
|
||||||
startForegroundOrNotify()
|
startForegroundOrNotify()
|
||||||
|
|
||||||
startPollingPosition()
|
startPollingPosition()
|
||||||
|
@ -298,12 +285,6 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
player.seekTo(position)
|
player.seekTo(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRestoreFinish() {
|
|
||||||
logD("Restore done")
|
|
||||||
|
|
||||||
restorePlayer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- SETTINGSMANAGER OVERRIDES ---
|
// --- SETTINGSMANAGER OVERRIDES ---
|
||||||
|
|
||||||
override fun onColorizeNotifUpdate(doColorize: Boolean) {
|
override fun onColorizeNotifUpdate(doColorize: Boolean) {
|
||||||
|
@ -518,86 +499,10 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Object that manages the AudioFocus state.
|
|
||||||
* Adapted from NewPipe (https://github.com/TeamNewPipe/NewPipe)
|
|
||||||
*/
|
|
||||||
inner class AudioFocusManager : AudioManager.OnAudioFocusChangeListener {
|
|
||||||
private val audioManager = ContextCompat.getSystemService(
|
|
||||||
this@PlaybackService, AudioManager::class.java
|
|
||||||
) ?: error("Cannot obtain AudioManager.")
|
|
||||||
|
|
||||||
private val request = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
|
|
||||||
.setWillPauseWhenDucked(true)
|
|
||||||
.setOnAudioFocusChangeListener(this)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private var pauseWasFromAudioFocus = false
|
|
||||||
|
|
||||||
fun requestFocus() {
|
|
||||||
AudioManagerCompat.requestAudioFocus(audioManager, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun destroy() {
|
|
||||||
AudioManagerCompat.abandonAudioFocusRequest(audioManager, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAudioFocusChange(focusChange: Int) {
|
|
||||||
when (focusChange) {
|
|
||||||
AudioManager.AUDIOFOCUS_GAIN -> onGain()
|
|
||||||
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> onDuck()
|
|
||||||
AudioManager.AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> onLoss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onGain() {
|
|
||||||
if (settingsManager.doAudioFocus) {
|
|
||||||
if (player.volume == VOLUME_DUCK && playbackManager.isPlaying) {
|
|
||||||
unduck()
|
|
||||||
} else if (pauseWasFromAudioFocus) {
|
|
||||||
playbackManager.setPlaying(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pauseWasFromAudioFocus = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onLoss() {
|
|
||||||
if (settingsManager.doAudioFocus && playbackManager.isPlaying) {
|
|
||||||
pauseWasFromAudioFocus = true
|
|
||||||
playbackManager.setPlaying(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onDuck() {
|
|
||||||
if (settingsManager.doAudioFocus) {
|
|
||||||
player.volume = VOLUME_DUCK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unduck() {
|
|
||||||
player.volume = VOLUME_DUCK
|
|
||||||
|
|
||||||
ValueAnimator().apply {
|
|
||||||
setFloatValues(VOLUME_DUCK, VOLUME_FULL)
|
|
||||||
duration = DUCK_DURATION
|
|
||||||
addListener(
|
|
||||||
onStart = { player.volume = VOLUME_DUCK },
|
|
||||||
onCancel = { player.volume = VOLUME_FULL },
|
|
||||||
onEnd = { player.volume = VOLUME_FULL }
|
|
||||||
)
|
|
||||||
addUpdateListener {
|
|
||||||
player.volume = it.animatedValue as Float
|
|
||||||
}
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [BroadcastReceiver] for receiving system events from the media notification or the headset.
|
* A [BroadcastReceiver] for receiving system events from the media notification or the headset.
|
||||||
*/
|
*/
|
||||||
private inner class SystemEventReceiver : BroadcastReceiver() {
|
inner class SystemEventReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
|
|
||||||
|
@ -605,12 +510,15 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
when (it) {
|
when (it) {
|
||||||
NotificationUtils.ACTION_LOOP ->
|
NotificationUtils.ACTION_LOOP ->
|
||||||
playbackManager.setLoopMode(playbackManager.loopMode.increment())
|
playbackManager.setLoopMode(playbackManager.loopMode.increment())
|
||||||
|
|
||||||
NotificationUtils.ACTION_SHUFFLE ->
|
NotificationUtils.ACTION_SHUFFLE ->
|
||||||
playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true)
|
playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true)
|
||||||
|
|
||||||
NotificationUtils.ACTION_SKIP_PREV -> playbackManager.prev()
|
NotificationUtils.ACTION_SKIP_PREV -> playbackManager.prev()
|
||||||
NotificationUtils.ACTION_PLAY_PAUSE -> {
|
|
||||||
|
NotificationUtils.ACTION_PLAY_PAUSE ->
|
||||||
playbackManager.setPlaying(!playbackManager.isPlaying)
|
playbackManager.setPlaying(!playbackManager.isPlaying)
|
||||||
}
|
|
||||||
NotificationUtils.ACTION_SKIP_NEXT -> playbackManager.next()
|
NotificationUtils.ACTION_SKIP_NEXT -> playbackManager.next()
|
||||||
NotificationUtils.ACTION_EXIT -> stop()
|
NotificationUtils.ACTION_EXIT -> stop()
|
||||||
|
|
||||||
|
@ -670,9 +578,5 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
companion object {
|
companion object {
|
||||||
private const val DISCONNECTED = 0
|
private const val DISCONNECTED = 0
|
||||||
private const val CONNECTED = 1
|
private const val CONNECTED = 1
|
||||||
|
|
||||||
private const val VOLUME_DUCK = 0.2f
|
|
||||||
private const val DUCK_DURATION = 1500L
|
|
||||||
private const val VOLUME_FULL = 1.0f
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,32 +246,6 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the current position. Will not notify any listeners of a seek event, that's what [seekTo] is for.
|
|
||||||
* @param position The new position in millis.
|
|
||||||
* @see seekTo
|
|
||||||
*/
|
|
||||||
fun setPosition(position: Long) {
|
|
||||||
mSong?.let {
|
|
||||||
// Don't accept any bugged positions that are over the duration of the song.
|
|
||||||
if (position <= it.duration) {
|
|
||||||
mPosition = position
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* **Seek** to a position, this calls [PlaybackStateManager.Callback.onSeek] to notify
|
|
||||||
* elements that rely on that.
|
|
||||||
* @param position The position to seek to in millis.
|
|
||||||
* @see setPosition
|
|
||||||
*/
|
|
||||||
fun seekTo(position: Long) {
|
|
||||||
mPosition = position
|
|
||||||
|
|
||||||
callbacks.forEach { it.onSeek(position) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- QUEUE FUNCTIONS ---
|
// --- QUEUE FUNCTIONS ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -574,6 +548,32 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current position. Will not notify any listeners of a seek event, that's what [seekTo] is for.
|
||||||
|
* @param position The new position in millis.
|
||||||
|
* @see seekTo
|
||||||
|
*/
|
||||||
|
fun setPosition(position: Long) {
|
||||||
|
mSong?.let {
|
||||||
|
// Don't accept any bugged positions that are over the duration of the song.
|
||||||
|
if (position <= it.duration) {
|
||||||
|
mPosition = position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Seek** to a position, this calls [PlaybackStateManager.Callback.onSeek] to notify
|
||||||
|
* elements that rely on that.
|
||||||
|
* @param position The position to seek to in millis.
|
||||||
|
* @see setPosition
|
||||||
|
*/
|
||||||
|
fun seekTo(position: Long) {
|
||||||
|
mPosition = position
|
||||||
|
|
||||||
|
callbacks.forEach { it.onSeek(position) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewind to the beginning of a song.
|
* Rewind to the beginning of a song.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,6 +16,7 @@ import kotlin.reflect.KProperty
|
||||||
* A delegate that creates a binding that can be used as a member variable without nullability or
|
* A delegate that creates a binding that can be used as a member variable without nullability or
|
||||||
* memory leaks.
|
* memory leaks.
|
||||||
* @param inflate The ViewBinding inflation method that should be used
|
* @param inflate The ViewBinding inflation method that should be used
|
||||||
|
* @param onDestroy What to do when the binding is destroyed
|
||||||
*/
|
*/
|
||||||
fun <T : ViewDataBinding> Fragment.memberBinding(
|
fun <T : ViewDataBinding> Fragment.memberBinding(
|
||||||
inflate: (LayoutInflater) -> T,
|
inflate: (LayoutInflater) -> T,
|
||||||
|
|
Loading…
Reference in a new issue