Loading app/src/main/java/foundation/e/apps/home/HomeFragment.kt +13 −4 Original line number Diff line number Diff line Loading @@ -54,7 +54,11 @@ import javax.inject.Inject @AndroidEntryPoint class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface { private lateinit var homeParentRVAdapter: HomeParentRVAdapter /* * Make adapter nullable to avoid memory leaks. * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 */ private var homeParentRVAdapter: HomeParentRVAdapter? = null private var _binding: FragmentHomeBinding? = null private val binding get() = _binding!! Loading Loading @@ -155,7 +159,7 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface stopLoadingUI() if (it.second == ResultStatus.OK) { dismissTimeoutDialog() homeParentRVAdapter.setData(it.first) homeParentRVAdapter?.setData(it.first) } else { onTimeout() } Loading Loading @@ -211,10 +215,10 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface } private fun updateProgressOfDownloadingAppItemViews( homeParentRVAdapter: HomeParentRVAdapter, homeParentRVAdapter: HomeParentRVAdapter?, downloadProgress: DownloadProgress ) { homeParentRVAdapter.currentList.forEach { fusedHome -> homeParentRVAdapter?.currentList?.forEach { fusedHome -> val viewHolder = binding.parentRV.findViewHolderForAdapterPosition( homeParentRVAdapter.currentList.indexOf(fusedHome) ) Loading Loading @@ -269,6 +273,11 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface override fun onDestroyView() { super.onDestroyView() _binding = null /* * Nullify adapter to avoid leaks. * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 */ homeParentRVAdapter = null } override fun getApplication(app: FusedApp, appIcon: ImageView?) { Loading app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt +19 −10 Original line number Diff line number Diff line Loading @@ -48,14 +48,14 @@ import foundation.e.apps.utils.enums.User import foundation.e.apps.utils.modules.PWAManagerModule class HomeChildRVAdapter( private val fusedAPIInterface: FusedAPIInterface, private var fusedAPIInterface: FusedAPIInterface?, private val pkgManagerModule: PkgManagerModule, private val pwaManagerModule: PWAManagerModule, private val appInfoFetchViewModel: AppInfoFetchViewModel, private val mainActivityViewModel: MainActivityViewModel, private val user: User, private val lifecycleOwner: LifecycleOwner, private val paidAppHandler: ((FusedApp) -> Unit)? = null private var lifecycleOwner: LifecycleOwner?, private var paidAppHandler: ((FusedApp) -> Unit)? = null ) : ListAdapter<FusedApp, HomeChildRVAdapter.ViewHolder>(HomeChildFusedAppDiffUtil()) { private val shimmer = Shimmer.ColorHighlightBuilder() Loading Loading @@ -293,7 +293,8 @@ class HomeChildRVAdapter( materialButton.isEnabled = false materialButton.text = "" homeChildListItemBinding.progressBarInstall.visibility = View.VISIBLE appInfoFetchViewModel.isAppPurchased(homeApp).observe(lifecycleOwner) { lifecycleOwner?.let { appInfoFetchViewModel.isAppPurchased(homeApp).observe(it) { materialButton.isEnabled = true homeChildListItemBinding.progressBarInstall.visibility = View.GONE materialButton.text = Loading @@ -302,16 +303,24 @@ class HomeChildRVAdapter( } } } } fun setData(newList: List<FusedApp>) { this.submitList(newList.map { it.copy() }) } private fun installApplication(homeApp: FusedApp, appIcon: ImageView) { fusedAPIInterface.getApplication(homeApp, appIcon) fusedAPIInterface?.getApplication(homeApp, appIcon) } private fun cancelDownload(homeApp: FusedApp) { fusedAPIInterface.cancelDownload(homeApp) fusedAPIInterface?.cancelDownload(homeApp) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { super.onDetachedFromRecyclerView(recyclerView) lifecycleOwner = null paidAppHandler = null fusedAPIInterface = null } } app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt +20 −5 Original line number Diff line number Diff line Loading @@ -41,12 +41,12 @@ class HomeParentRVAdapter( private val user: User, private val mainActivityViewModel: MainActivityViewModel, private val appInfoFetchViewModel: AppInfoFetchViewModel, private val lifecycleOwner: LifecycleOwner, private var lifecycleOwner: LifecycleOwner?, private val paidAppHandler: ((FusedApp) -> Unit)? = null ) : ListAdapter<FusedHome, HomeParentRVAdapter.ViewHolder>(FusedHomeDiffUtil()) { private val viewPool = RecyclerView.RecycledViewPool() private var isDownloadObserverAdded = false private var isDetachedFromRecyclerView = false inner class ViewHolder(val binding: HomeParentListItemBinding) : RecyclerView.ViewHolder(binding.root) Loading Loading @@ -91,13 +91,28 @@ class HomeParentRVAdapter( fusedHome: FusedHome, homeChildRVAdapter: RecyclerView.Adapter<*>? ) { mainActivityViewModel.downloadList.observe(lifecycleOwner) { lifecycleOwner?.let { mainActivityViewModel.downloadList.observe(it) { mainActivityViewModel.updateStatusOfFusedApps(fusedHome.list, it) (homeChildRVAdapter as HomeChildRVAdapter).setData(fusedHome.list) } } } fun setData(newList: List<FusedHome>) { submitList(newList.map { it.copy() }) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { super.onDetachedFromRecyclerView(recyclerView) isDetachedFromRecyclerView = true lifecycleOwner = null } override fun onViewDetachedFromWindow(holder: ViewHolder) { super.onViewDetachedFromWindow(holder) if(isDetachedFromRecyclerView) { holder.binding.childRV.adapter = null } } } app/src/main/java/foundation/e/apps/setup/tos/TOSFragment.kt +22 −4 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ package foundation.e.apps.setup.tos import android.content.res.Configuration import android.os.Bundle import android.view.View import android.webkit.WebView import androidx.constraintlayout.widget.ConstraintSet import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels Loading @@ -18,11 +19,18 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { private var _binding: FragmentTosBinding? = null private val binding get() = _binding!! /* * Fix memory leaks related to WebView. * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 */ private var webView: WebView? = null private val viewModel: TOSViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = FragmentTosBinding.bind(view) webView = binding.tosWebView var canNavigate = false viewModel.tocStatus.observe(viewLifecycleOwner) { Loading @@ -30,7 +38,7 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { view.findNavController().navigate(R.id.action_TOSFragment_to_signInFragment) } if (it == true) { if (it == true && webView != null) { binding.TOSWarning.visibility = View.GONE binding.TOSButtons.visibility = View.GONE binding.toolbar.visibility = View.VISIBLE Loading @@ -38,7 +46,7 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { val constraintSet = ConstraintSet() constraintSet.clone(binding.root) constraintSet.connect( binding.tosWebView.id, webView!!.id, ConstraintSet.TOP, binding.acceptDateTV.id, ConstraintSet.BOTTOM, Loading Loading @@ -66,6 +74,16 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { override fun onDestroyView() { super.onDestroyView() /* * Fix WebView memory leaks. https://stackoverflow.com/a/19391512 * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 */ webView?.run { setOnScrollChangeListener(null) removeAllViews() destroy() } webView = null _binding = null } Loading @@ -92,12 +110,12 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { ) .append(body.toString()) .append("</HTML>") binding.tosWebView.loadDataWithBaseURL( webView?.loadDataWithBaseURL( "file:///android_asset/", sb.toString(), "text/html", "utf-8", null ) binding.tosWebView.setOnScrollChangeListener { _, scrollX, scrollY, _, _ -> webView?.setOnScrollChangeListener { _, scrollX, scrollY, _, _ -> if (scrollX == 0 && scrollY == 0 && viewModel.tocStatus.value == true) { binding.acceptDateTV.visibility = View.VISIBLE } else { Loading Loading
app/src/main/java/foundation/e/apps/home/HomeFragment.kt +13 −4 Original line number Diff line number Diff line Loading @@ -54,7 +54,11 @@ import javax.inject.Inject @AndroidEntryPoint class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface { private lateinit var homeParentRVAdapter: HomeParentRVAdapter /* * Make adapter nullable to avoid memory leaks. * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 */ private var homeParentRVAdapter: HomeParentRVAdapter? = null private var _binding: FragmentHomeBinding? = null private val binding get() = _binding!! Loading Loading @@ -155,7 +159,7 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface stopLoadingUI() if (it.second == ResultStatus.OK) { dismissTimeoutDialog() homeParentRVAdapter.setData(it.first) homeParentRVAdapter?.setData(it.first) } else { onTimeout() } Loading Loading @@ -211,10 +215,10 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface } private fun updateProgressOfDownloadingAppItemViews( homeParentRVAdapter: HomeParentRVAdapter, homeParentRVAdapter: HomeParentRVAdapter?, downloadProgress: DownloadProgress ) { homeParentRVAdapter.currentList.forEach { fusedHome -> homeParentRVAdapter?.currentList?.forEach { fusedHome -> val viewHolder = binding.parentRV.findViewHolderForAdapterPosition( homeParentRVAdapter.currentList.indexOf(fusedHome) ) Loading Loading @@ -269,6 +273,11 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface override fun onDestroyView() { super.onDestroyView() _binding = null /* * Nullify adapter to avoid leaks. * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 */ homeParentRVAdapter = null } override fun getApplication(app: FusedApp, appIcon: ImageView?) { Loading
app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt +19 −10 Original line number Diff line number Diff line Loading @@ -48,14 +48,14 @@ import foundation.e.apps.utils.enums.User import foundation.e.apps.utils.modules.PWAManagerModule class HomeChildRVAdapter( private val fusedAPIInterface: FusedAPIInterface, private var fusedAPIInterface: FusedAPIInterface?, private val pkgManagerModule: PkgManagerModule, private val pwaManagerModule: PWAManagerModule, private val appInfoFetchViewModel: AppInfoFetchViewModel, private val mainActivityViewModel: MainActivityViewModel, private val user: User, private val lifecycleOwner: LifecycleOwner, private val paidAppHandler: ((FusedApp) -> Unit)? = null private var lifecycleOwner: LifecycleOwner?, private var paidAppHandler: ((FusedApp) -> Unit)? = null ) : ListAdapter<FusedApp, HomeChildRVAdapter.ViewHolder>(HomeChildFusedAppDiffUtil()) { private val shimmer = Shimmer.ColorHighlightBuilder() Loading Loading @@ -293,7 +293,8 @@ class HomeChildRVAdapter( materialButton.isEnabled = false materialButton.text = "" homeChildListItemBinding.progressBarInstall.visibility = View.VISIBLE appInfoFetchViewModel.isAppPurchased(homeApp).observe(lifecycleOwner) { lifecycleOwner?.let { appInfoFetchViewModel.isAppPurchased(homeApp).observe(it) { materialButton.isEnabled = true homeChildListItemBinding.progressBarInstall.visibility = View.GONE materialButton.text = Loading @@ -302,16 +303,24 @@ class HomeChildRVAdapter( } } } } fun setData(newList: List<FusedApp>) { this.submitList(newList.map { it.copy() }) } private fun installApplication(homeApp: FusedApp, appIcon: ImageView) { fusedAPIInterface.getApplication(homeApp, appIcon) fusedAPIInterface?.getApplication(homeApp, appIcon) } private fun cancelDownload(homeApp: FusedApp) { fusedAPIInterface.cancelDownload(homeApp) fusedAPIInterface?.cancelDownload(homeApp) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { super.onDetachedFromRecyclerView(recyclerView) lifecycleOwner = null paidAppHandler = null fusedAPIInterface = null } }
app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt +20 −5 Original line number Diff line number Diff line Loading @@ -41,12 +41,12 @@ class HomeParentRVAdapter( private val user: User, private val mainActivityViewModel: MainActivityViewModel, private val appInfoFetchViewModel: AppInfoFetchViewModel, private val lifecycleOwner: LifecycleOwner, private var lifecycleOwner: LifecycleOwner?, private val paidAppHandler: ((FusedApp) -> Unit)? = null ) : ListAdapter<FusedHome, HomeParentRVAdapter.ViewHolder>(FusedHomeDiffUtil()) { private val viewPool = RecyclerView.RecycledViewPool() private var isDownloadObserverAdded = false private var isDetachedFromRecyclerView = false inner class ViewHolder(val binding: HomeParentListItemBinding) : RecyclerView.ViewHolder(binding.root) Loading Loading @@ -91,13 +91,28 @@ class HomeParentRVAdapter( fusedHome: FusedHome, homeChildRVAdapter: RecyclerView.Adapter<*>? ) { mainActivityViewModel.downloadList.observe(lifecycleOwner) { lifecycleOwner?.let { mainActivityViewModel.downloadList.observe(it) { mainActivityViewModel.updateStatusOfFusedApps(fusedHome.list, it) (homeChildRVAdapter as HomeChildRVAdapter).setData(fusedHome.list) } } } fun setData(newList: List<FusedHome>) { submitList(newList.map { it.copy() }) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { super.onDetachedFromRecyclerView(recyclerView) isDetachedFromRecyclerView = true lifecycleOwner = null } override fun onViewDetachedFromWindow(holder: ViewHolder) { super.onViewDetachedFromWindow(holder) if(isDetachedFromRecyclerView) { holder.binding.childRV.adapter = null } } }
app/src/main/java/foundation/e/apps/setup/tos/TOSFragment.kt +22 −4 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ package foundation.e.apps.setup.tos import android.content.res.Configuration import android.os.Bundle import android.view.View import android.webkit.WebView import androidx.constraintlayout.widget.ConstraintSet import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels Loading @@ -18,11 +19,18 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { private var _binding: FragmentTosBinding? = null private val binding get() = _binding!! /* * Fix memory leaks related to WebView. * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 */ private var webView: WebView? = null private val viewModel: TOSViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = FragmentTosBinding.bind(view) webView = binding.tosWebView var canNavigate = false viewModel.tocStatus.observe(viewLifecycleOwner) { Loading @@ -30,7 +38,7 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { view.findNavController().navigate(R.id.action_TOSFragment_to_signInFragment) } if (it == true) { if (it == true && webView != null) { binding.TOSWarning.visibility = View.GONE binding.TOSButtons.visibility = View.GONE binding.toolbar.visibility = View.VISIBLE Loading @@ -38,7 +46,7 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { val constraintSet = ConstraintSet() constraintSet.clone(binding.root) constraintSet.connect( binding.tosWebView.id, webView!!.id, ConstraintSet.TOP, binding.acceptDateTV.id, ConstraintSet.BOTTOM, Loading Loading @@ -66,6 +74,16 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { override fun onDestroyView() { super.onDestroyView() /* * Fix WebView memory leaks. https://stackoverflow.com/a/19391512 * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 */ webView?.run { setOnScrollChangeListener(null) removeAllViews() destroy() } webView = null _binding = null } Loading @@ -92,12 +110,12 @@ class TOSFragment : Fragment(R.layout.fragment_tos) { ) .append(body.toString()) .append("</HTML>") binding.tosWebView.loadDataWithBaseURL( webView?.loadDataWithBaseURL( "file:///android_asset/", sb.toString(), "text/html", "utf-8", null ) binding.tosWebView.setOnScrollChangeListener { _, scrollX, scrollY, _, _ -> webView?.setOnScrollChangeListener { _, scrollX, scrollY, _, _ -> if (scrollX == 0 && scrollY == 0 && viewModel.tocStatus.value == true) { binding.acceptDateTV.visibility = View.VISIBLE } else { Loading