Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2549be1b authored by Sayantan Roychowdhury's avatar Sayantan Roychowdhury
Browse files

Merge branch '487-memory_leak_fix' into 'main'

Issue 487: Fix memory leaks in App Lounge

See merge request !152
parents 134c7764 1263d08a
Loading
Loading
Loading
Loading
Loading
+13 −4
Original line number Diff line number Diff line
@@ -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!!

@@ -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()
            }
@@ -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)
            )
@@ -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?) {
+19 −10
Original line number Diff line number Diff line
@@ -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()
@@ -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 =
@@ -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
    }
}
+20 −5
Original line number Diff line number Diff line
@@ -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)
@@ -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
        }
    }
}
+22 −4
Original line number Diff line number Diff line
@@ -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
@@ -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) {
@@ -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
@@ -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,
@@ -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
    }

@@ -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 {