From 21353c5c4d5d2f640a6bacbacabb99199990c58a Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Fri, 13 Jun 2025 16:50:02 +0530 Subject: [PATCH 01/10] B Fix memory leak in fragments --- .../bookmarks/BookmarkListRootFragment.java | 6 ++++++ .../locations/BookmarkLocationsFragment.kt | 5 +++++ .../pictures/BookmarkPicturesFragment.java | 6 ++++++ .../contributions/ContributionsFragment.kt | 19 +++++++++++++++---- .../nrw/commons/contributions/MainActivity.kt | 5 +++++ .../nrw/commons/media/MediaDetailFragment.kt | 2 +- .../nrw/commons/profile/ProfileActivity.kt | 4 ++++ .../free/nrw/commons/upload/UploadActivity.kt | 2 ++ .../values-x-invalidLanguageCode/error.xml | 10 ---------- app/src/main/res/values/strings.xml | 4 ++-- gradle.properties | 3 ++- 11 files changed, 48 insertions(+), 18 deletions(-) delete mode 100644 app/src/main/res/values-x-invalidLanguageCode/error.xml diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java index ca7dd3f3b9..80fa901335 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java @@ -258,6 +258,12 @@ public void onBackStackChanged() { } + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.kt index f10e02ebcd..90f74bc96c 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.kt @@ -143,6 +143,11 @@ class BookmarkLocationsFragment : DaggerFragment() { } } + override fun onDestroyView() { + super.onDestroyView() + binding = null + } + override fun onDestroy() { super.onDestroy() // Make sure to null out the binding to avoid memory leaks diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java index 9f02e46311..99552f22db 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java @@ -67,6 +67,12 @@ public void onStop() { controller.stop(); } + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt index f50df13ff5..0245d3f2b0 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt @@ -685,14 +685,23 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On override fun onDestroy() { try { compositeDisposable.clear() + // Remove child fragment safely + contributionsListFragment?.let { + childFragmentManager.beginTransaction() + .remove(it) + .commitAllowingStateLoss() + } childFragmentManager.removeOnBackStackChangedListener(this) - locationManager!!.unregisterLocationManager() - locationManager!!.removeLocationListener(this) - super.onDestroy() + locationManager?.unregisterLocationManager() + locationManager?.removeLocationListener(this) + // Nullify locationManager to prevent leaks + locationManager = null } catch (exception: IllegalArgumentException) { Timber.e(exception) } catch (exception: IllegalStateException) { Timber.e(exception) + } finally { + super.onDestroy() } } @@ -757,7 +766,9 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On override fun onDestroyView() { super.onDestroyView() - presenter!!.onDetachView() + presenter?.onDetachView() + binding = null + contributionsListFragment = null } override fun notifyDataSetChanged() { diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt index a83532bdb5..f7a6b2f8ad 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt @@ -527,6 +527,11 @@ after opening the app. quizChecker!!.cleanup() locationManager!!.unregisterLocationManager() // Remove ourself from hashmap to prevent memory leaks + try { + locationManager?.unregisterLocationManager() + } catch (e: Exception) { + Timber.e(e, "Error while cleaning up locationManager") + } locationManager = null super.onDestroy() } diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt index 8a4d530c47..e62b99ba72 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt @@ -791,7 +791,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C } compositeDisposable.clear() - + _binding = null super.onDestroyView() } diff --git a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt index 164842c9ac..ad57378851 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt @@ -122,6 +122,10 @@ class ProfileActivity : BaseActivity() { public override fun onDestroy() { super.onDestroy() compositeDisposable.clear() + // Nullify fragment references to avoid memory leaks + if (::achievementsFragment.isInitialized) achievementsFragment.setHasOptionsMenu(false) + if (::leaderboardFragment.isInitialized) leaderboardFragment.setHasOptionsMenu(false) + contributionsFragment = null } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt index 38e7dace8f..85bb09a5c4 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt @@ -882,6 +882,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C uploadCategoriesFragment!!.callback = null } onBackPressedCallback.remove() + locationManager?.unregisterLocationManager() + UploadMediaPresenter.presenterCallback = null // Clearing reference } /** diff --git a/app/src/main/res/values-x-invalidLanguageCode/error.xml b/app/src/main/res/values-x-invalidLanguageCode/error.xml deleted file mode 100644 index f4e2fe125e..0000000000 --- a/app/src/main/res/values-x-invalidLanguageCode/error.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - کامَنٕز گوو رُکِتھ - Oops. کیہہ تام گوو غلط! - ؤنِیوٚ اَسہِ توٚہہِ کیاہ ٲسِیوٚ کران، تہٕ کٕریٚو تہِ اَسہِ سٕتی شیر بذریعہِ برقی خط. یُس مَدَتھ کَرِ اَسہِ اَتھ شہَرنَس منٛز! - شُکریہ! - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 68dba88bec..230c0ee448 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -381,8 +381,8 @@ Thanks Received Featured Images Images via \"Nearby Places\" - Level %d - %s (Level %s) + Level %d + %s (Level %s) Images Uploaded Images Not Reverted Images Used diff --git a/gradle.properties b/gradle.properties index 61c3584e58..bbe2f74961 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,8 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true #Thu Mar 01 15:28:48 IST 2018 -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4096m -Dkotlin.incremental=true +kotlin.daemon.jvmargs=-Xmx4096m org.gradle.caching=true android.enableR8.fullMode=false From 154cb0fa676cf19852904fb761c809eb01639e40 Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Fri, 13 Jun 2025 17:50:03 +0530 Subject: [PATCH 02/10] Revert local gradle.properties changes --- gradle.properties | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index bbe2f74961..61c3584e58 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,8 +13,7 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true #Thu Mar 01 15:28:48 IST 2018 -org.gradle.jvmargs=-Xmx4096m -Dkotlin.incremental=true -kotlin.daemon.jvmargs=-Xmx4096m +org.gradle.jvmargs=-Xmx1536M org.gradle.caching=true android.enableR8.fullMode=false From 0c6a0c53076d9f5d38c80692f7e46541514e3ef3 Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Wed, 18 Jun 2025 09:37:17 +0530 Subject: [PATCH 03/10] Fix: safely access binding in MediaDetailFragment to prevent crash on delayed callbacks --- .../nrw/commons/media/MediaDetailFragment.kt | 103 +++++++++++------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt index e62b99ba72..c006ad2091 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt @@ -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( } } -} +} \ No newline at end of file From 5b5ec1707d14bbac092d68a5cb58ab2936f6754f Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Wed, 18 Jun 2025 10:38:54 +0530 Subject: [PATCH 04/10] Fix: safely access binding in delayed view callbacks --- .../nrw/commons/media/MediaDetailFragment.kt | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt index c006ad2091..90855b53bf 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt @@ -561,7 +561,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C } } ) - binding.progressBarEdit.visibility = View.GONE + bindingOrNull?.progressBarEdit?.visibility = View.GONE } override fun onConfigurationChanged(newConfig: Configuration) { @@ -636,7 +636,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C } private fun onMediaRefreshed(media: Media) { - media.categories = this.media!!.categories + media.categories = this.media?.categories this.media = media setTextFields(media) compositeDisposable.addAll( @@ -650,29 +650,30 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C } private fun onDiscussionLoaded(discussion: String) { - binding.mediaDetailDisc.text = prettyDiscussion(discussion.trim { it <= ' ' }) + bindingOrNull?.mediaDetailDisc?.text = prettyDiscussion(discussion.trim { it <= ' ' }) } private fun onDeletionPageExists(deletionPageExists: Boolean) { - if (getUserName(requireContext()) == null && getUserName(requireContext()) != media!!.author) { - binding.nominateDeletion.visibility = View.GONE - binding.nominatedDeletionBanner.visibility = View.GONE + val safeBinding = bindingOrNull ?: return + + if (getUserName(requireContext()) == null && getUserName(requireContext()) != media?.author) { + safeBinding.nominateDeletion.visibility = View.GONE + safeBinding.nominatedDeletionBanner.visibility = View.GONE } else if (deletionPageExists) { if (applicationKvStore.getBoolean( - String.format(NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl), false + String.format(NOMINATING_FOR_DELETION_MEDIA, media?.imageUrl), false ) ) { applicationKvStore.remove( - String.format(NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl) + String.format(NOMINATING_FOR_DELETION_MEDIA, media?.imageUrl) ) - binding.progressBarDeletion.visibility = View.GONE + safeBinding.progressBarDeletion.visibility = View.GONE } - binding.nominateDeletion.visibility = View.GONE - - binding.nominatedDeletionBanner.visibility = View.VISIBLE + safeBinding.nominateDeletion.visibility = View.GONE + safeBinding.nominatedDeletionBanner.visibility = View.VISIBLE } else if (!isCategoryImage) { - binding.nominateDeletion.visibility = View.VISIBLE - binding.nominatedDeletionBanner.visibility = View.GONE + safeBinding.nominateDeletion.visibility = View.VISIBLE + safeBinding.nominatedDeletionBanner.visibility = View.GONE } } From c525e260b38927584c5948c563806dfb2d20790e Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Wed, 18 Jun 2025 10:54:48 +0530 Subject: [PATCH 05/10] Trigger CI rebuild for final verification From 1fb9783039263cf165d3029c5b54835e1998bc76 Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Wed, 18 Jun 2025 18:30:03 +0530 Subject: [PATCH 06/10] Fix: Safe binding usage in MediaDetailFragment --- .../java/fr/free/nrw/commons/media/MediaDetailFragment.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt index 90855b53bf..222c359d09 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt @@ -248,7 +248,9 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C private val scrollPosition: Unit get() { - initialListTop = binding.mediaDetailScrollView.scrollY + bindingOrNull?.let { + initialListTop = it.mediaDetailScrollView.scrollY + } } override fun onCreateView( @@ -736,7 +738,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C object : BaseControllerListener() { override fun onIntermediateImageSet(id: String, imageInfo: ImageInfo?) { imageInfoCache = imageInfo - updateAspectRatio(binding.mediaDetailScrollView.width) + bindingOrNull?.let { updateAspectRatio(it.mediaDetailScrollView.width) } } override fun onFinalImageSet( @@ -745,7 +747,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C animatable: Animatable? ) { imageInfoCache = imageInfo - updateAspectRatio(binding.mediaDetailScrollView.width) + bindingOrNull?.let { updateAspectRatio(it.mediaDetailScrollView.width) } } } From 151ab48b744f4f759b41ab45789c1fc777a92fc9 Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Wed, 18 Jun 2025 19:22:17 +0530 Subject: [PATCH 07/10] Fix: Nullify binding in MediaDetailPagerFragment to prevent memory leaks --- .../fr/free/nrw/commons/media/MediaDetailPagerFragment.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index cba582a35a..18d0667bae 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -160,6 +160,12 @@ else if (getActivity() instanceof MainActivity) { return binding.getRoot(); } + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); From f508dcca9e8b6eb0893b0cc1a408aa464d14c591 Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Fri, 20 Jun 2025 20:31:42 +0530 Subject: [PATCH 08/10] Fix: Prevent crash in onSaveInstanceState by checking binding --- .../fr/free/nrw/commons/media/MediaDetailPagerFragment.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index 18d0667bae..3cc1477216 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -169,11 +169,14 @@ public void onDestroyView() { @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putInt("current-page", binding.mediaDetailsPager.getCurrentItem()); + if (binding != null && binding.mediaDetailsPager != null) { + outState.putInt("current-page", binding.mediaDetailsPager.getCurrentItem()); + } outState.putBoolean("editable", editable); outState.putBoolean("isFeaturedImage", isFeaturedImage); } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); From 31eeaa95ed022ac17f85537d27ec0e3310df86e4 Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Fri, 20 Jun 2025 20:33:10 +0530 Subject: [PATCH 09/10] Fix: Prevent crash in onSaveInstanceState by checking binding --- .../java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index 3cc1477216..b383d64a0d 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -176,7 +176,6 @@ public void onSaveInstanceState(Bundle outState) { outState.putBoolean("isFeaturedImage", isFeaturedImage); } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); From 99e46aa6b9e8dd5775949a1cd3deeb894b6213ea Mon Sep 17 00:00:00 2001 From: Dev Jadiya Date: Fri, 20 Jun 2025 20:57:23 +0530 Subject: [PATCH 10/10] Fix: Prevent crash in onSaveInstanceState by checking binding --- .../java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index b383d64a0d..4ed4ce8952 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -164,6 +164,7 @@ else if (getActivity() instanceof MainActivity) { public void onDestroyView() { super.onDestroyView(); binding = null; + imageProgressBar = null; } @Override