Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Commit

Permalink
Add delegated property to automatically handle Fragment view unbinding (
Browse files Browse the repository at this point in the history
closes #927) (#928)

* Create delegated property to automatically handle Fragment view unbinding

* Add new file at the end of file

* Remove empty line

* Update error message

* Add missing imports for other flavors.

Co-authored-by: darken <darken@darken.eu>
Co-authored-by: Matthias Urhahn <matthias.urhahn@sap.com>
  • Loading branch information
3 people authored Sep 14, 2020
1 parent 0c598b1 commit 7f3b2b1
Show file tree
Hide file tree
Showing 34 changed files with 151 additions and 258 deletions.
4 changes: 3 additions & 1 deletion Corona-Warn-App/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.work:work-runtime-ktx:2.3.4'
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0'
implementation 'androidx.annotation:annotation:1.1.0'

// DAGGER
Expand All @@ -247,6 +248,7 @@ dependencies {
kapt 'com.google.dagger:dagger-compiler:2.28.1'
kapt 'com.google.dagger:dagger-android-processor:2.28.1'


// QR
implementation('com.journeyapps:zxing-android-embedded:4.1.0') { transitive = false }
// noinspection GradleDependency - needed for SDK 23 compatibility, in combination with com.journeyapps:zxing-android-embedded:4.1.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import de.rki.coronawarnapp.risk.TimeVariables
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.timer.TimerHelper
import de.rki.coronawarnapp.ui.doNavigate
import de.rki.coronawarnapp.ui.viewLifecycle
import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
Expand Down Expand Up @@ -44,27 +45,21 @@ class MainFragment : Fragment() {
private val tracingViewModel: TracingViewModel by activityViewModels()
private val settingsViewModel: SettingsViewModel by activityViewModels()
private val submissionViewModel: SubmissionViewModel by activityViewModels()
private var _binding: FragmentMainBinding? = null
private val binding: FragmentMainBinding get() = _binding!!
private var binding: FragmentMainBinding by viewLifecycle()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentMainBinding.inflate(inflater)
binding = FragmentMainBinding.inflate(inflater)
binding.tracingViewModel = tracingViewModel
binding.settingsViewModel = settingsViewModel
binding.submissionViewModel = submissionViewModel
binding.lifecycleOwner = this
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setButtonOnClickListener()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import de.rki.coronawarnapp.storage.ExposureSummaryRepository
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository
import de.rki.coronawarnapp.transaction.RiskLevelTransaction
import de.rki.coronawarnapp.ui.viewLifecycle
import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
import de.rki.coronawarnapp.util.KeyFileHelper
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_api_enter_other_keys
Expand Down Expand Up @@ -118,8 +119,7 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel
private lateinit var qrPagerAdapter: RecyclerView.Adapter<QRPagerAdapter.QRViewHolder>

// Data and View binding
private var _binding: FragmentTestForAPIBinding? = null
private val binding: FragmentTestForAPIBinding get() = _binding!!
private var binding: FragmentTestForAPIBinding by viewLifecycle()

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -128,7 +128,7 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel
): View? {

// get the binding reference by inflating it with the current layout
_binding = FragmentTestForAPIBinding.inflate(inflater)
binding = FragmentTestForAPIBinding.inflate(inflater)

// set the viewmmodel variable that will be used for data binding
binding.tracingViewModel = tracingViewModel
Expand All @@ -140,11 +140,6 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.storage.RiskLevelRepository
import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction
import de.rki.coronawarnapp.transaction.RiskLevelTransaction
import de.rki.coronawarnapp.ui.viewLifecycle
import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
Expand All @@ -59,8 +60,7 @@ class TestRiskLevelCalculationFragment : Fragment() {
private val tracingViewModel: TracingViewModel by activityViewModels()
private val settingsViewModel: SettingsViewModel by activityViewModels()
private val submissionViewModel: SubmissionViewModel by activityViewModels()
private var _binding: FragmentTestRiskLevelCalculationBinding? = null
private val binding: FragmentTestRiskLevelCalculationBinding get() = _binding!!
private var binding: FragmentTestRiskLevelCalculationBinding by viewLifecycle()

// reference to the client from the Google framework with the given application context
private val exposureNotificationClient by lazy {
Expand All @@ -72,19 +72,14 @@ class TestRiskLevelCalculationFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentTestRiskLevelCalculationBinding.inflate(inflater)
binding = FragmentTestRiskLevelCalculationBinding.inflate(inflater)
binding.tracingViewModel = tracingViewModel
binding.settingsViewModel = settingsViewModel
binding.submissionViewModel = submissionViewModel
binding.lifecycleOwner = this
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import de.rki.coronawarnapp.risk.TimeVariables
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.timer.TimerHelper
import de.rki.coronawarnapp.ui.doNavigate
import de.rki.coronawarnapp.ui.viewLifecycle
import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
Expand Down Expand Up @@ -44,27 +45,21 @@ class MainFragment : Fragment() {
private val tracingViewModel: TracingViewModel by activityViewModels()
private val settingsViewModel: SettingsViewModel by activityViewModels()
private val submissionViewModel: SubmissionViewModel by activityViewModels()
private var _binding: FragmentMainBinding? = null
private val binding: FragmentMainBinding get() = _binding!!
private var binding: FragmentMainBinding by viewLifecycle()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentMainBinding.inflate(inflater)
binding = FragmentMainBinding.inflate(inflater)
binding.tracingViewModel = tracingViewModel
binding.settingsViewModel = settingsViewModel
binding.submissionViewModel = submissionViewModel
binding.lifecycleOwner = this
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setButtonOnClickListener()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package de.rki.coronawarnapp.ui

import android.os.Handler
import android.os.Looper
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

/**
* Extends NavController to prevent navigation error when the user clicks on two buttons at almost
Expand All @@ -13,3 +21,47 @@ fun NavController.doNavigate(direction: NavDirections) {
currentDestination?.getAction(direction.actionId)
?.let { navigate(direction) }
}

/**
* An extension to bind and unbind a value based on the view lifecycle of a Fragment.
* The binding will be unbound in onDestroyView.
*
* @throws IllegalStateException If the getter is invoked before the binding is set,
* or after onDestroyView an exception is thrown.
*/
fun <T> Fragment.viewLifecycle(): ReadWriteProperty<Fragment, T> {
return object : ReadWriteProperty<Fragment, T>, DefaultLifecycleObserver {

private var binding: T? = null

init {
this@viewLifecycle
.viewLifecycleOwnerLiveData
.observe(this@viewLifecycle, Observer { owner: LifecycleOwner? ->
owner?.lifecycle?.addObserver(this)
})
}

override fun onDestroy(owner: LifecycleOwner) {
val handler = Handler(Looper.getMainLooper())
handler.post {
binding = null
}
}

override fun getValue(
thisRef: Fragment,
property: KProperty<*>
): T {
return this.binding ?: error("Called before onCreateView or after onDestroyView.")
}

override fun setValue(
thisRef: Fragment,
property: KProperty<*>,
value: T
) {
this.binding = value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.fragment.app.Fragment
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentInformationAboutBinding
import de.rki.coronawarnapp.ui.main.MainActivity
import de.rki.coronawarnapp.ui.viewLifecycle

/**
* Basic Fragment which only displays static content.
Expand All @@ -20,23 +21,17 @@ class InformationAboutFragment : Fragment() {
private val TAG: String? = InformationAboutFragment::class.simpleName
}

private var _binding: FragmentInformationAboutBinding? = null
private val binding: FragmentInformationAboutBinding get() = _binding!!
private var binding: FragmentInformationAboutBinding by viewLifecycle()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentInformationAboutBinding.inflate(inflater)
binding = FragmentInformationAboutBinding.inflate(inflater)
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setButtonOnClickListener()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentInformationContactBinding
import de.rki.coronawarnapp.ui.main.MainActivity
import de.rki.coronawarnapp.ui.viewLifecycle
import de.rki.coronawarnapp.util.ExternalActionHelper

/**
Expand All @@ -19,23 +20,17 @@ class InformationContactFragment : Fragment() {
private val TAG: String? = InformationContactFragment::class.simpleName
}

private var _binding: FragmentInformationContactBinding? = null
private val binding: FragmentInformationContactBinding get() = _binding!!
private var binding: FragmentInformationContactBinding by viewLifecycle()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentInformationContactBinding.inflate(inflater)
binding = FragmentInformationContactBinding.inflate(inflater)
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setButtonOnClickListener()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentInformationBinding
import de.rki.coronawarnapp.ui.doNavigate
import de.rki.coronawarnapp.ui.main.MainActivity
import de.rki.coronawarnapp.ui.viewLifecycle
import de.rki.coronawarnapp.util.ExternalActionHelper

/**
Expand All @@ -22,23 +23,17 @@ class InformationFragment : Fragment() {
private val TAG: String? = InformationFragment::class.simpleName
}

private var _binding: FragmentInformationBinding? = null
private val binding: FragmentInformationBinding get() = _binding!!
private var binding: FragmentInformationBinding by viewLifecycle()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentInformationBinding.inflate(inflater)
binding = FragmentInformationBinding.inflate(inflater)
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setButtonOnClickListener()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentInformationLegalBinding
import de.rki.coronawarnapp.ui.main.MainActivity
import de.rki.coronawarnapp.util.convertToHyperlink
import de.rki.coronawarnapp.ui.viewLifecycle

/**
* Basic Fragment which only displays static content.
Expand All @@ -20,23 +21,17 @@ class InformationLegalFragment : Fragment() {
private val TAG: String? = InformationLegalFragment::class.simpleName
}

private var _binding: FragmentInformationLegalBinding? = null
private val binding: FragmentInformationLegalBinding get() = _binding!!
private var binding: FragmentInformationLegalBinding by viewLifecycle()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentInformationLegalBinding.inflate(inflater)
binding = FragmentInformationLegalBinding.inflate(inflater)
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setButtonOnClickListener()
Expand Down
Loading

0 comments on commit 7f3b2b1

Please sign in to comment.