mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Fix: safely access binding in MediaDetailFragment to prevent crash on delayed callbacks
This commit is contained in:
parent
154cb0fa67
commit
0c6a0c5307
1 changed files with 65 additions and 38 deletions
|
|
@ -190,8 +190,13 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
|||
|
||||
private var initialListTop: Int = 0
|
||||
|
||||
/**
|
||||
* Holds the view binding reference for this fragment.
|
||||
* Use bindingOrNull for safe access after view destruction.
|
||||
*/
|
||||
private var _binding: FragmentMediaDetailBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val bindingOrNull get() = _binding
|
||||
|
||||
private var descriptionHtmlCode: String? = null
|
||||
|
||||
|
|
@ -340,25 +345,27 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
|||
|
||||
handleBackEvent(view)
|
||||
|
||||
//set onCLick listeners
|
||||
binding.mediaDetailLicense.setOnClickListener { onMediaDetailLicenceClicked() }
|
||||
binding.mediaDetailCoordinates.setOnClickListener { onMediaDetailCoordinatesClicked() }
|
||||
binding.sendThanks.setOnClickListener { sendThanksToAuthor() }
|
||||
binding.dummyCaptionDescriptionContainer.setOnClickListener { showCaptionAndDescription() }
|
||||
binding.mediaDetailImageView.setOnClickListener {
|
||||
launchZoomActivity(
|
||||
binding.mediaDetailImageView
|
||||
)
|
||||
/**
|
||||
* Safely sets click listeners on media detail UI elements using bindingOrNull.
|
||||
* Prevents potential crashes caused by view being destroyed during delayed callbacks.
|
||||
*/
|
||||
bindingOrNull?.let { binding ->
|
||||
binding.mediaDetailLicense.setOnClickListener { onMediaDetailLicenceClicked() }
|
||||
binding.mediaDetailCoordinates.setOnClickListener { onMediaDetailCoordinatesClicked() }
|
||||
binding.sendThanks.setOnClickListener { sendThanksToAuthor() }
|
||||
binding.dummyCaptionDescriptionContainer.setOnClickListener { showCaptionAndDescription() }
|
||||
binding.mediaDetailImageView.setOnClickListener {
|
||||
launchZoomActivity(binding.mediaDetailImageView)
|
||||
}
|
||||
binding.categoryEditButton.setOnClickListener { onCategoryEditButtonClicked() }
|
||||
binding.depictionsEditButton.setOnClickListener { onDepictionsEditButtonClicked() }
|
||||
binding.seeMore.setOnClickListener { onSeeMoreClicked() }
|
||||
binding.mediaDetailAuthor.setOnClickListener { onAuthorViewClicked() }
|
||||
binding.nominateDeletion.setOnClickListener { onDeleteButtonClicked() }
|
||||
binding.descriptionEdit.setOnClickListener { onDescriptionEditClicked() }
|
||||
binding.coordinateEdit.setOnClickListener { onUpdateCoordinatesClicked() }
|
||||
binding.copyWikicode.setOnClickListener { onCopyWikicodeClicked() }
|
||||
}
|
||||
binding.categoryEditButton.setOnClickListener { onCategoryEditButtonClicked() }
|
||||
binding.depictionsEditButton.setOnClickListener { onDepictionsEditButtonClicked() }
|
||||
binding.seeMore.setOnClickListener { onSeeMoreClicked() }
|
||||
binding.mediaDetailAuthor.setOnClickListener { onAuthorViewClicked() }
|
||||
binding.nominateDeletion.setOnClickListener { onDeleteButtonClicked() }
|
||||
binding.descriptionEdit.setOnClickListener { onDescriptionEditClicked() }
|
||||
binding.coordinateEdit.setOnClickListener { onUpdateCoordinatesClicked() }
|
||||
binding.copyWikicode.setOnClickListener { onCopyWikicodeClicked() }
|
||||
|
||||
binding.fileUsagesComposeView.apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
|
|
@ -404,14 +411,19 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
|||
/**
|
||||
* Gets the height of the frame layout as soon as the view is ready and updates aspect ratio
|
||||
* of the picture.
|
||||
* Updated to use bindingOrNull inside delayed callbacks to prevent crashes
|
||||
* when view is destroyed before post/postDelayed is executed.
|
||||
*/
|
||||
view.post{
|
||||
val width = binding.mediaDetailScrollView.width
|
||||
view.post {
|
||||
val safeBinding = bindingOrNull ?: return@post
|
||||
val width = safeBinding.mediaDetailScrollView.width
|
||||
if (width > 0) {
|
||||
frameLayoutHeight = binding.mediaDetailFrameLayout.measuredHeight
|
||||
frameLayoutHeight = safeBinding.mediaDetailFrameLayout.measuredHeight
|
||||
updateAspectRatio(width)
|
||||
} else {
|
||||
view.postDelayed({ updateAspectRatio(binding.root.width) }, 1)
|
||||
view.postDelayed({
|
||||
bindingOrNull?.let { updateAspectRatio(it.root.width) }
|
||||
}, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -526,19 +538,25 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
|||
binding.sendThanks.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
binding.mediaDetailScrollView.viewTreeObserver.addOnGlobalLayoutListener(
|
||||
/**
|
||||
* Sets a one-time global layout listener to safely access view dimensions.
|
||||
* Uses bindingOrNull to avoid crash if the fragment view is destroyed
|
||||
* before the callback is executed.
|
||||
*/
|
||||
bindingOrNull?.mediaDetailScrollView?.viewTreeObserver?.addOnGlobalLayoutListener(
|
||||
object : OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
val safeBinding = bindingOrNull ?: return
|
||||
if (context == null) {
|
||||
return
|
||||
}
|
||||
binding.mediaDetailScrollView.viewTreeObserver.removeOnGlobalLayoutListener(
|
||||
safeBinding.mediaDetailScrollView.viewTreeObserver.removeOnGlobalLayoutListener(
|
||||
this
|
||||
)
|
||||
oldWidthOfImageView = binding.mediaDetailScrollView.width
|
||||
if (media != null) {
|
||||
oldWidthOfImageView = safeBinding.mediaDetailScrollView.width
|
||||
media?.filename?.let {
|
||||
displayMediaDetails()
|
||||
fetchFileUsages(media?.filename!!)
|
||||
fetchFileUsages(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -548,23 +566,28 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
|||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
binding.mediaDetailScrollView.viewTreeObserver.addOnGlobalLayoutListener(
|
||||
/**
|
||||
* Sets a global layout listener to update the aspect ratio when device configuration changes.
|
||||
* Uses bindingOrNull to avoid crashes in case the view is destroyed before callback.
|
||||
*/
|
||||
bindingOrNull?.mediaDetailScrollView?.viewTreeObserver?.addOnGlobalLayoutListener(
|
||||
object : OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
val safeBinding = bindingOrNull ?: return
|
||||
/**
|
||||
* We update the height of the frame layout as the configuration changes.
|
||||
*/
|
||||
binding.mediaDetailFrameLayout.post {
|
||||
frameLayoutHeight = binding.mediaDetailFrameLayout.measuredHeight
|
||||
updateAspectRatio(binding.mediaDetailScrollView.width)
|
||||
safeBinding.mediaDetailFrameLayout.post {
|
||||
frameLayoutHeight = safeBinding.mediaDetailFrameLayout.measuredHeight
|
||||
updateAspectRatio(safeBinding.mediaDetailScrollView.width)
|
||||
}
|
||||
if (binding.mediaDetailScrollView.width != oldWidthOfImageView) {
|
||||
if (safeBinding.mediaDetailScrollView.width != oldWidthOfImageView) {
|
||||
if (newWidthOfImageView == 0) {
|
||||
newWidthOfImageView = binding.mediaDetailScrollView.width
|
||||
newWidthOfImageView = safeBinding.mediaDetailScrollView.width
|
||||
updateAspectRatio(newWidthOfImageView)
|
||||
}
|
||||
binding.mediaDetailScrollView.viewTreeObserver.removeOnGlobalLayoutListener(
|
||||
this
|
||||
safeBinding.mediaDetailScrollView.viewTreeObserver.removeOnGlobalLayoutListener(
|
||||
this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -784,9 +807,13 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears binding and removes layout listener to prevent memory leaks
|
||||
* or crashes from delayed UI callbacks after the view is destroyed.
|
||||
*/
|
||||
override fun onDestroyView() {
|
||||
if (layoutListener != null && view != null) {
|
||||
requireView().viewTreeObserver.removeGlobalOnLayoutListener(layoutListener) // old Android was on crack. CRACK IS WHACK
|
||||
layoutListener?.let {
|
||||
view?.viewTreeObserver?.removeOnGlobalLayoutListener(it)
|
||||
layoutListener = null
|
||||
}
|
||||
|
||||
|
|
@ -2263,4 +2290,4 @@ fun FileUsagesContainer(
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue