Allow fragments to use member variable bindings
Create a binder delegate so that some fragments can use a binding as a member variable without nullability issues or memory leaks.
This commit is contained in:
parent
71cd15bbf7
commit
4933531ca3
3 changed files with 89 additions and 9 deletions
|
@ -5,7 +5,6 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
|
@ -15,6 +14,7 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.ui.memberBinding
|
||||
import org.oxycblt.auxio.ui.isLandscape
|
||||
|
||||
/**
|
||||
|
@ -26,14 +26,15 @@ import org.oxycblt.auxio.ui.isLandscape
|
|||
*/
|
||||
class CompactPlaybackFragment : Fragment() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val binding: FragmentCompactPlaybackBinding by memberBinding(
|
||||
FragmentCompactPlaybackBinding::inflate
|
||||
)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val binding = FragmentCompactPlaybackBinding.inflate(inflater)
|
||||
|
||||
val isLandscape = isLandscape(resources)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
@ -94,7 +95,7 @@ class CompactPlaybackFragment : Fragment() {
|
|||
|
||||
// Use the caveman method of getting a view as storing the binding will cause a memory
|
||||
// leak.
|
||||
val playbackControls = requireView().findViewById<ImageButton>(R.id.playback_controls)
|
||||
val playbackControls = binding.playbackControls
|
||||
|
||||
val iconPauseToPlay = ContextCompat.getDrawable(
|
||||
requireContext(), R.drawable.ic_pause_to_play
|
||||
|
|
|
@ -8,7 +8,6 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
|
@ -18,6 +17,7 @@ import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
|
|||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.playback.state.LoopMode
|
||||
import org.oxycblt.auxio.ui.accent
|
||||
import org.oxycblt.auxio.ui.memberBinding
|
||||
import org.oxycblt.auxio.ui.toColor
|
||||
|
||||
/**
|
||||
|
@ -28,14 +28,13 @@ import org.oxycblt.auxio.ui.toColor
|
|||
*/
|
||||
class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val binding: FragmentPlaybackBinding by memberBinding(FragmentPlaybackBinding::inflate)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val binding = FragmentPlaybackBinding.inflate(inflater)
|
||||
|
||||
// TODO: Add a swipe-to-next-track function using a ViewPager
|
||||
|
||||
// Create accents & icons to use
|
||||
|
@ -203,8 +202,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
// Stop the marqueeing of the song name to prevent a weird memory leak
|
||||
requireView().findViewById<TextView>(R.id.playback_song).isSelected = false
|
||||
binding.playbackSong.isSelected = false
|
||||
}
|
||||
|
||||
// Seeking callbacks
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package org.oxycblt.auxio.ui
|
||||
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import java.lang.IllegalStateException
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* A delegate that creates a binding that can be used as a member variable without nullability or
|
||||
* memory leaks.
|
||||
*/
|
||||
fun <T : ViewBinding> Fragment.memberBinding(
|
||||
viewBindingFactory: (LayoutInflater) -> T,
|
||||
disposeEvents: T.() -> Unit = {}
|
||||
) = FragmentBinderDelegate(this, viewBindingFactory, disposeEvents)
|
||||
|
||||
/**
|
||||
* The delegate for the [binder] shortcut function.
|
||||
* Adapted from KAHelpers (https://github.com/FunkyMuse/KAHelpers/tree/master/viewbinding)
|
||||
*/
|
||||
class FragmentBinderDelegate<T : ViewBinding>(
|
||||
private val fragment: Fragment,
|
||||
private val binder: (LayoutInflater) -> T,
|
||||
private val disposeEvents: T.() -> Unit
|
||||
) : ReadOnlyProperty<Fragment, T>, LifecycleObserver {
|
||||
private var fragmentBinding: T? = null
|
||||
|
||||
init {
|
||||
fragment.observeOwnerThroughCreation {
|
||||
lifecycle.addObserver(this@FragmentBinderDelegate)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun Fragment.observeOwnerThroughCreation(
|
||||
crossinline viewOwner: LifecycleOwner.() -> Unit
|
||||
) {
|
||||
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
super.onCreate(owner)
|
||||
|
||||
viewLifecycleOwnerLiveData.observe(this@observeOwnerThroughCreation) { owner ->
|
||||
owner.viewOwner()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
throw IllegalThreadStateException("View can only be accessed on the main thread.")
|
||||
}
|
||||
|
||||
val binding = fragmentBinding
|
||||
if (binding != null) {
|
||||
return binding
|
||||
}
|
||||
|
||||
val lifecycle = fragment.viewLifecycleOwner.lifecycle
|
||||
|
||||
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
|
||||
throw IllegalStateException("Fragment views are destroyed.")
|
||||
}
|
||||
|
||||
return binder(LayoutInflater.from(thisRef.requireContext())).also { fragmentBinding = it }
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
fun dispose() {
|
||||
fragmentBinding?.disposeEvents()
|
||||
fragmentBinding = null
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue