diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index aa533f1bc245557afe5139cf802e509b43525f98..334cbf113bc1c42348ed76a582f1ddacf28ddb5e 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -127,6 +127,10 @@
       <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
       <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
       <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
+      <option name="FIELD_ANNOTATION_WRAP" value="1" />
+      <indentOptions>
+        <option name="CONTINUATION_INDENT_SIZE" value="4" />
+      </indentOptions>
     </codeStyleSettings>
   </code_scheme>
-</component>
+</component>
\ No newline at end of file
diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle
index c4da9b2427af1bed9279c60ca405422493902501..63c99edc3af68ee60b7955e94f4c8376e34b8a96 100644
--- a/Corona-Warn-App/build.gradle
+++ b/Corona-Warn-App/build.gradle
@@ -137,6 +137,14 @@ android {
         jvmTarget = "1.8"
     }
 
+    def jvmCompilerArgs = ["-Xno-kotlin-nothing-value-exception"]
+
+    tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile) {
+        kotlinOptions {
+            freeCompilerArgs = jvmCompilerArgs
+        }
+    }
+
     lintOptions {
         checkAllWarnings = true
     }
@@ -241,6 +249,9 @@ dependencies {
     implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0'
     implementation 'androidx.annotation:annotation:1.1.0'
 
+    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
+
+
     // DAGGER
     implementation 'com.google.dagger:dagger:2.28.1'
     implementation 'com.google.dagger:dagger-android:2.28.1'
@@ -248,6 +259,8 @@ dependencies {
     kapt 'com.google.dagger:dagger-compiler:2.28.1'
     kapt 'com.google.dagger:dagger-android-processor:2.28.1'
 
+    compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2'
+    kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.2'
 
     // QR
     implementation('com.journeyapps:zxing-android-embedded:4.1.0') { transitive = false }
diff --git a/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8cf4950155fce0e5dd3255ed9d5f8b1c7d511a4a
--- /dev/null
+++ b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
@@ -0,0 +1,9 @@
+package de.rki.coronawarnapp.ui.main
+
+import dagger.Module
+
+@Module
+abstract class MainActivityTestModule {
+
+    // This is an empty placeholder so we can use the same one, but not empty via deviceForTesters
+}
diff --git a/Corona-Warn-App/src/device/java/de.rki.coronawarnapp/ui/main/MainFragment.kt b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainFragment.kt
similarity index 93%
rename from Corona-Warn-App/src/device/java/de.rki.coronawarnapp/ui/main/MainFragment.kt
rename to Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainFragment.kt
index 4b2300bc5c27b440054e145844cf415acfcb9370..a6a339419ee9e366314f67cad85d81345bf607a4 100644
--- a/Corona-Warn-App/src/device/java/de.rki.coronawarnapp/ui/main/MainFragment.kt
+++ b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainFragment.kt
@@ -1,9 +1,7 @@
 package de.rki.coronawarnapp.ui.main
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import android.widget.PopupMenu
 import androidx.fragment.app.Fragment
@@ -16,12 +14,12 @@ 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
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.ExternalActionHelper
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -36,32 +34,19 @@ import kotlinx.coroutines.withContext
  * @see submissionViewModel
  * @see PopupMenu
  */
-class MainFragment : Fragment() {
-
-    companion object {
-        private val TAG: String? = MainFragment::class.simpleName
-    }
-
+class MainFragment : Fragment(R.layout.fragment_main) {
     private val tracingViewModel: TracingViewModel by activityViewModels()
     private val settingsViewModel: SettingsViewModel by activityViewModels()
     private val submissionViewModel: SubmissionViewModel by activityViewModels()
-    private var binding: FragmentMainBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentMainBinding.inflate(inflater)
+    private val binding: FragmentMainBinding by viewBindingLazy()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
         binding.tracingViewModel = tracingViewModel
         binding.settingsViewModel = settingsViewModel
         binding.submissionViewModel = submissionViewModel
         binding.lifecycleOwner = this
-        return binding.root
-    }
 
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
         setButtonOnClickListener()
         setContentDescription()
 
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/TestRiskLevelCalculationFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/TestRiskLevelCalculationFragment.kt
deleted file mode 100644
index 268a583555dcfa109ed6f2248f20ca53b19e726e..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/TestRiskLevelCalculationFragment.kt
+++ /dev/null
@@ -1,356 +0,0 @@
-package de.rki.coronawarnapp.test
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.EditText
-import android.widget.Toast
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.viewModelScope
-import com.google.android.gms.nearby.Nearby
-import com.google.android.gms.nearby.exposurenotification.ExposureInformation
-import com.google.zxing.integration.android.IntentIntegrator
-import com.google.zxing.integration.android.IntentResult
-import de.rki.coronawarnapp.CoronaWarnApplication
-import de.rki.coronawarnapp.databinding.FragmentTestRiskLevelCalculationBinding
-import de.rki.coronawarnapp.exception.ExceptionCategory
-import de.rki.coronawarnapp.exception.TransactionException
-import de.rki.coronawarnapp.exception.reporting.report
-import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
-import de.rki.coronawarnapp.risk.DefaultRiskLevelCalculation
-import de.rki.coronawarnapp.risk.RiskLevel
-import de.rki.coronawarnapp.risk.TimeVariables
-import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange
-import de.rki.coronawarnapp.service.applicationconfiguration.ApplicationConfigurationService
-import de.rki.coronawarnapp.sharing.ExposureSharingService
-import de.rki.coronawarnapp.storage.AppDatabase
-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
-import de.rki.coronawarnapp.util.KeyFileHelper
-import de.rki.coronawarnapp.util.di.AppInjector
-import de.rki.coronawarnapp.util.security.SecurityHelper
-import kotlinx.android.synthetic.deviceForTesters.fragment_test_risk_level_calculation.*
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import timber.log.Timber
-import java.io.File
-import java.util.UUID
-import java.util.concurrent.TimeUnit
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
-import kotlin.coroutines.suspendCoroutine
-
-@Suppress("MagicNumber", "LongMethod")
-class TestRiskLevelCalculationFragment : Fragment() {
-    companion object {
-        val TAG: String? = TestRiskLevelCalculationFragment::class.simpleName
-    }
-
-    private val tracingViewModel: TracingViewModel by activityViewModels()
-    private val settingsViewModel: SettingsViewModel by activityViewModels()
-    private val submissionViewModel: SubmissionViewModel by activityViewModels()
-    private var binding: FragmentTestRiskLevelCalculationBinding by viewLifecycle()
-
-    // reference to the client from the Google framework with the given application context
-    private val exposureNotificationClient by lazy {
-        Nearby.getExposureNotificationClient(CoronaWarnApplication.getAppContext())
-    }
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentTestRiskLevelCalculationBinding.inflate(inflater)
-        binding.tracingViewModel = tracingViewModel
-        binding.settingsViewModel = settingsViewModel
-        binding.submissionViewModel = submissionViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-
-        binding.buttonRetrieveDiagnosisKeys.setOnClickListener {
-            tracingViewModel.viewModelScope.launch {
-                retrieveDiagnosisKeys()
-            }
-        }
-
-        binding.buttonProvideKeyViaQr.setOnClickListener {
-            scanLocalQRCodeAndProvide()
-        }
-
-        binding.buttonCalculateRiskLevel.setOnClickListener {
-            tracingViewModel.viewModelScope.launch {
-                calculateRiskLevel()
-            }
-        }
-
-        binding.buttonResetRiskLevel.setOnClickListener {
-            tracingViewModel.viewModelScope.launch {
-                withContext(Dispatchers.IO) {
-                    try {
-                        // Preference reset
-                        SecurityHelper.resetSharedPrefs()
-                        // Database Reset
-                        AppDatabase.reset(requireContext())
-                        // Export File Reset
-                        AppInjector.component.keyCacheRepository.clear()
-
-                        LocalData.lastCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw)
-                        LocalData.lastSuccessfullyCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw)
-                        LocalData.lastTimeDiagnosisKeysFromServerFetch(null)
-                        LocalData.googleApiToken(null)
-                    } catch (e: java.lang.Exception) {
-                        e.report(ExceptionCategory.INTERNAL)
-                    }
-                }
-                RiskLevelTransaction.start()
-                Toast.makeText(
-                    requireContext(), "Reset done, please fetch diagnosis keys from server again",
-                    Toast.LENGTH_SHORT
-                ).show()
-            }
-        }
-
-        binding.buttonClearDiagnosisKeyCache.setOnClickListener {
-            lifecycleScope.launch {
-                AppInjector.component.keyCacheRepository.clear()
-            }
-        }
-
-        startObserving()
-    }
-
-    override fun onResume() {
-        super.onResume()
-        tracingViewModel.viewModelScope.launch {
-            calculateRiskLevel()
-        }
-    }
-
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-
-        val result: IntentResult? =
-            IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
-        if (result != null) {
-            if (result.contents == null) {
-                Toast.makeText(requireContext(), "Cancelled", Toast.LENGTH_LONG).show()
-            } else {
-                ExposureSharingService.getOthersKeys(result.contents, onScannedKey)
-            }
-        } else {
-            super.onActivityResult(requestCode, resultCode, data)
-        }
-    }
-
-    private suspend fun retrieveDiagnosisKeys() {
-        try {
-            RetrieveDiagnosisKeysTransaction.start()
-            calculateRiskLevel()
-        } catch (e: TransactionException) {
-            e.report(ExceptionCategory.INTERNAL)
-        }
-    }
-
-    private fun scanLocalQRCodeAndProvide() {
-        IntentIntegrator.forSupportFragment(this)
-            .setOrientationLocked(false)
-            .setBeepEnabled(false)
-            .initiateScan()
-    }
-
-    private val onScannedKey = { key: AppleLegacyKeyExchange.Key? ->
-        Timber.i("keys scanned..")
-        provideDiagnosisKey(key)
-    }
-
-    private fun provideDiagnosisKey(key: AppleLegacyKeyExchange.Key?) {
-        if (null == key) {
-            Toast.makeText(requireContext(), "No Key data found in QR code", Toast.LENGTH_SHORT)
-                .show()
-        } else {
-            val token = UUID.randomUUID().toString()
-            LocalData.googleApiToken(token)
-
-            val appleKeyList = mutableListOf<AppleLegacyKeyExchange.Key>()
-
-            val text = (transmission_number as EditText).text.toString()
-            var number = 5
-            if (!text.isBlank()) {
-                number = Integer.valueOf(text)
-            }
-
-            appleKeyList.add(
-                AppleLegacyKeyExchange.Key.newBuilder()
-                    .setKeyData(key.keyData)
-                    .setRollingPeriod(144)
-                    .setRollingStartNumber(key.rollingStartNumber)
-                    .setTransmissionRiskLevel(number)
-                    .build()
-            )
-
-            val appleFiles = listOf(
-                AppleLegacyKeyExchange.File.newBuilder()
-                    .addAllKeys(appleKeyList)
-                    .build()
-            )
-
-            val dir =
-                File(File(requireContext().getExternalFilesDir(null), "key-export"), token)
-            dir.mkdirs()
-
-            var googleFileList: List<File>
-            lifecycleScope.launch {
-                googleFileList = KeyFileHelper.asyncCreateExportFiles(appleFiles, dir)
-
-                Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys with token $token")
-                try {
-                    // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API
-                    InternalExposureNotificationClient.asyncProvideDiagnosisKeys(
-                        googleFileList,
-                        ApplicationConfigurationService.asyncRetrieveExposureConfiguration(),
-                        token
-                    )
-                    Toast.makeText(
-                        requireContext(),
-                        "Provided ${appleKeyList.size} keys to Google API with token $token",
-                        Toast.LENGTH_SHORT
-                    ).show()
-                } catch (e: Exception) {
-                    e.report(ExceptionCategory.EXPOSURENOTIFICATION)
-                }
-            }
-        }
-    }
-
-    private suspend fun calculateRiskLevel() {
-        try {
-            RiskLevelTransaction.start()
-        } catch (e: TransactionException) {
-            e.report(ExceptionCategory.INTERNAL)
-        }
-    }
-
-    private fun startObserving() {
-        tracingViewModel.viewModelScope.launch {
-            try {
-                val googleToken = LocalData.googleApiToken() ?: UUID.randomUUID().toString()
-                val exposureSummary =
-                    InternalExposureNotificationClient.asyncGetExposureSummary(googleToken)
-
-                val appConfig =
-                    ApplicationConfigurationService.asyncRetrieveApplicationConfiguration()
-
-                val riskLevelScore = DefaultRiskLevelCalculation().calculateRiskScore(
-                    appConfig.attenuationDuration,
-                    exposureSummary
-                )
-
-                val riskAsString = "Level: ${RiskLevelRepository.getLastCalculatedScore()}\n" +
-                        "Last successful Level: " +
-                        "${LocalData.lastSuccessfullyCalculatedRiskLevel()}\n" +
-                        "Calculated Score: ${riskLevelScore}\n" +
-                        "Last Time Server Fetch: ${LocalData.lastTimeDiagnosisKeysFromServerFetch()}\n" +
-                        "Tracing Duration: " +
-                        "${TimeUnit.MILLISECONDS.toDays(TimeVariables.getTimeActiveTracingDuration())} days \n" +
-                        "Tracing Duration in last 14 days: " +
-                        "${TimeVariables.getActiveTracingDaysInRetentionPeriod()} days \n" +
-                        "Last time risk level calculation ${LocalData.lastTimeRiskLevelCalculation()}"
-
-                binding.labelRiskScore.text = riskAsString
-
-                val lowClass =
-                    appConfig.riskScoreClasses?.riskClassesList?.find { low -> low.label == "LOW" }
-                val highClass =
-                    appConfig.riskScoreClasses?.riskClassesList?.find { high -> high.label == "HIGH" }
-
-                val configAsString =
-                    "Attenuation Weight Low: ${appConfig.attenuationDuration?.weights?.low}\n" +
-                            "Attenuation Weight Mid: ${appConfig.attenuationDuration?.weights?.mid}\n" +
-                            "Attenuation Weight High: ${appConfig.attenuationDuration?.weights?.high}\n\n" +
-                            "Attenuation Offset: ${appConfig.attenuationDuration?.defaultBucketOffset}\n" +
-                            "Attenuation Normalization: " +
-                            "${appConfig.attenuationDuration?.riskScoreNormalizationDivisor}\n\n" +
-                            "Risk Score Low Class: ${lowClass?.min ?: 0} - ${lowClass?.max ?: 0}\n" +
-                            "Risk Score High Class: ${highClass?.min ?: 0} - ${highClass?.max ?: 0}"
-
-                binding.labelBackendParameters.text = configAsString
-
-                val summaryAsString =
-                    "Days Since Last Exposure: ${exposureSummary.daysSinceLastExposure}\n" +
-                            "Matched Key Count: ${exposureSummary.matchedKeyCount}\n" +
-                            "Maximum Risk Score: ${exposureSummary.maximumRiskScore}\n" +
-                            "Attenuation Durations: [${exposureSummary.attenuationDurationsInMinutes?.get(
-                                0
-                            )}," +
-                            "${exposureSummary.attenuationDurationsInMinutes?.get(1)}," +
-                            "${exposureSummary.attenuationDurationsInMinutes?.get(2)}]\n" +
-                            "Summation Risk Score: ${exposureSummary.summationRiskScore}"
-
-                binding.labelExposureSummary.text = summaryAsString
-
-                val maxRisk = exposureSummary.maximumRiskScore
-                val atWeights = appConfig.attenuationDuration?.weights
-                val attenuationDurationInMin =
-                    exposureSummary.attenuationDurationsInMinutes
-                val attenuationConfig = appConfig.attenuationDuration
-                val formulaString =
-                    "($maxRisk / ${attenuationConfig?.riskScoreNormalizationDivisor}) * " +
-                            "(${attenuationDurationInMin?.get(0)} * ${atWeights?.low} " +
-                            "+ ${attenuationDurationInMin?.get(1)} * ${atWeights?.mid} " +
-                            "+ ${attenuationDurationInMin?.get(2)} * ${atWeights?.high} " +
-                            "+ ${attenuationConfig?.defaultBucketOffset})"
-
-                binding.labelFormula.text = formulaString
-
-                binding.labelFullConfig.text = appConfig.toString()
-
-                val token = LocalData.googleApiToken()
-                if (token != null) {
-                    val exposureInformation = asyncGetExposureInformation(token)
-
-                    var infoString = ""
-                    exposureInformation.forEach {
-                        infoString += "Attenuation duration in min.: " +
-                                "[${it.attenuationDurationsInMinutes?.get(0)}, " +
-                                "${it.attenuationDurationsInMinutes?.get(1)}," +
-                                "${it.attenuationDurationsInMinutes?.get(2)}]\n" +
-                                "Attenuation value: ${it.attenuationValue}\n" +
-                                "Duration in min.: ${it.durationMinutes}\n" +
-                                "Risk Score: ${it.totalRiskScore}\n" +
-                                "Transmission Risk Level: ${it.transmissionRiskLevel}\n" +
-                                "Date Millis Since Epoch: ${it.dateMillisSinceEpoch}\n\n"
-                    }
-
-                    binding.labelExposureInfo.text = infoString
-                }
-            } catch (e: Exception) {
-                e.report(ExceptionCategory.EXPOSURENOTIFICATION)
-            }
-        }
-    }
-
-    suspend fun asyncGetExposureInformation(token: String): List<ExposureInformation> =
-        suspendCoroutine { cont ->
-            exposureNotificationClient.getExposureInformation(token)
-                .addOnSuccessListener {
-                    cont.resume(it)
-                }.addOnFailureListener {
-                    cont.resumeWithException(it)
-                }
-        }
-}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt
similarity index 94%
rename from Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/TestForAPIFragment.kt
rename to Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt
index 15b56532f1ddd85ff49a0bf773ccead970ec028b..f7094337bcd777cb4f52ec1e89e2fface92cbe1c 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/TestForAPIFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.test
+package de.rki.coronawarnapp.test.api.ui
 
 import android.content.Context
 import android.content.Intent
@@ -52,10 +52,15 @@ 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 de.rki.coronawarnapp.util.di.AppInjector
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
+import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.*
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -65,9 +70,14 @@ import timber.log.Timber
 import java.io.File
 import java.lang.reflect.Type
 import java.util.UUID
+import javax.inject.Inject
 
 @SuppressWarnings("TooManyFunctions", "MagicNumber", "LongMethod")
-class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHelper.Callback {
+class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
+    InternalExposureNotificationPermissionHelper.Callback, AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: TestForApiFragmentViewModel by cwaViewModels { viewModelFactory }
 
     companion object {
         const val CONFIG_SCORE = 8
@@ -98,32 +108,15 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel
     private lateinit var qrPagerAdapter: RecyclerView.Adapter<QRPagerAdapter.QRViewHolder>
 
     // Data and View binding
-    private var binding: FragmentTestForAPIBinding by viewLifecycle()
+    private val binding: FragmentTestForAPIBinding by viewBindingLazy()
 
     private var lastSetCountries: List<String>? = null
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-
-        // get the binding reference by inflating it with the current layout
-        binding = FragmentTestForAPIBinding.inflate(inflater)
-
-        // set the viewmmodel variable that will be used for data binding
-        binding.tracingViewModel = tracingViewModel
-
-        // set the lifecycleowner for LiveData
-        binding.lifecycleOwner = this
-
-        // Inflate the layout for this fragment
-        return binding.root
-    }
-
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
+        binding.tracingViewModel = tracingViewModel
+
         val v: Long = PackageInfoCompat.getLongVersionCode(
             activity?.packageManager!!.getPackageInfo(
                 GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE,
@@ -170,9 +163,11 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel
         val last3HoursSwitch = binding.testApiSwitchLastThreeHoursFromServer
         last3HoursSwitch.isChecked = LocalData.last3HoursMode()
         last3HoursSwitch.setOnClickListener {
-            val isLast3HoursModeEnabled = last3HoursSwitch.isChecked
-            showToast("Last 3 Hours Mode is activated: $isLast3HoursModeEnabled")
-            LocalData.last3HoursMode(isLast3HoursModeEnabled)
+            vm.setLast3HoursMode(last3HoursSwitch.isChecked)
+        }
+
+        vm.last3HourToggleEvent.observe2(this) {
+            showToast("Last 3 Hours Mode is activated: $it")
         }
 
         val backgroundNotificationSwitch = binding.testApiSwitchBackgroundNotifications
@@ -354,7 +349,6 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel
         key?.let {
             binding.textScannedKey.text = prettyKey(key)
             binding.textScannedKey.visibility = View.VISIBLE
-//            text_scanned_key.movementMethod = ScrollingMovementMethod.getInstance()
         }
         otherExposureKeyList.add(key!!)
         otherExposureKey = key
@@ -512,7 +506,9 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel
         binding.labelMyKeys.text = myKeysLabelAndCount
         binding.textMyKeys.text = myExposureKeysJSON
 
-        myKeys?.maxBy { it.rollingStartIntervalNumber }?.rollingStartIntervalNumber?.toLong()
+        myKeys
+            ?.maxByOrNull { it.rollingStartIntervalNumber }
+            ?.rollingStartIntervalNumber?.toLong()
             ?.let {
                 val ms = it * 60L * 10L * 1000L
                 val dateString = DateTime(ms, DateTimeZone.UTC)
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..38504b80936698889c42139f31388e5d4cf1f2e9
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentModule.kt
@@ -0,0 +1,16 @@
+package de.rki.coronawarnapp.test.api.ui
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class TestForApiFragmentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(TestForApiFragmentViewModel::class)
+    abstract fun testRiskLevelFragment(factory: TestForApiFragmentViewModel.Factory): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..10abbd1afbd9a95163f3c6a2fa098b0b967d54cf
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt
@@ -0,0 +1,20 @@
+package de.rki.coronawarnapp.test.api.ui
+
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.storage.LocalData
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+
+class TestForApiFragmentViewModel @AssistedInject constructor() : CWAViewModel() {
+
+    val last3HourToggleEvent = SingleLiveEvent<Boolean>()
+
+    fun setLast3HoursMode(isLast3HoursModeEnabled: Boolean) {
+        LocalData.last3HoursMode(isLast3HoursModeEnabled)
+        last3HourToggleEvent.postValue(isLast3HoursModeEnabled)
+    }
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<TestForApiFragmentViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..710555ae5067a96f872f02cdb7ff3caaad3477dc
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt
@@ -0,0 +1,125 @@
+package de.rki.coronawarnapp.test.risklevel.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.navArgs
+import com.google.zxing.integration.android.IntentIntegrator
+import com.google.zxing.integration.android.IntentResult
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentTestRiskLevelCalculationBinding
+import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange
+import de.rki.coronawarnapp.sharing.ExposureSharingService
+import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
+import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
+import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
+import timber.log.Timber
+import javax.inject.Inject
+
+@Suppress("MagicNumber", "LongMethod")
+class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_level_calculation),
+    AutoInject {
+    private val navArgs by navArgs<TestRiskLevelCalculationFragmentArgs>()
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: TestRiskLevelCalculationFragmentCWAViewModel by cwaViewModelsAssisted(
+        { viewModelFactory },
+        { factory, handle ->
+            factory as TestRiskLevelCalculationFragmentCWAViewModel.Factory
+            factory.create(handle, navArgs.exampleArgument)
+        }
+    )
+
+    private val tracingViewModel: TracingViewModel by activityViewModels()
+    private val settingsViewModel: SettingsViewModel by activityViewModels()
+    private val submissionViewModel: SubmissionViewModel by activityViewModels()
+
+    private val binding: FragmentTestRiskLevelCalculationBinding by viewBindingLazy()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        binding.tracingViewModel = tracingViewModel
+        binding.settingsViewModel = settingsViewModel
+        binding.submissionViewModel = submissionViewModel
+
+        binding.buttonRetrieveDiagnosisKeys.setOnClickListener { vm.retrieveDiagnosisKeys() }
+        binding.buttonProvideKeyViaQr.setOnClickListener { vm.scanLocalQRCodeAndProvide() }
+        binding.buttonCalculateRiskLevel.setOnClickListener { vm.calculateRiskLevel() }
+        binding.buttonClearDiagnosisKeyCache.setOnClickListener { vm.clearKeyCache() }
+
+        binding.buttonResetRiskLevel.setOnClickListener { vm.resetRiskLevel() }
+        vm.riskLevelResetEvent.observe2(this) {
+            Toast.makeText(
+                requireContext(), "Reset done, please fetch diagnosis keys from server again",
+                Toast.LENGTH_SHORT
+            ).show()
+        }
+
+        vm.riskScoreState.observe2(this) { state ->
+            binding.labelRiskScore.text = state.riskScoreMsg
+            binding.labelBackendParameters.text = state.backendParameters
+            binding.labelExposureSummary.text = state.exposureSummary
+            binding.labelFormula.text = state.formula
+            binding.labelFullConfig.text = state.fullConfig
+            binding.labelExposureInfo.text = state.exposureInfo
+        }
+        vm.startENFObserver()
+
+        vm.apiKeysProvidedEvent.observe2(this) { event ->
+            Toast.makeText(
+                requireContext(),
+                "Provided ${event.keyCount} keys to Google API with token ${event.token}",
+                Toast.LENGTH_SHORT
+            ).show()
+        }
+
+        vm.startLocalQRCodeScanEvent.observe2(this) {
+            IntentIntegrator.forSupportFragment(this)
+                .setOrientationLocked(false)
+                .setBeepEnabled(false)
+                .initiateScan()
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        vm.calculateRiskLevel()
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        val result: IntentResult =
+            IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
+                ?: return super.onActivityResult(requestCode, resultCode, data)
+
+        if (result.contents == null) {
+            Toast.makeText(requireContext(), "Cancelled", Toast.LENGTH_LONG).show()
+            return
+        }
+
+        ExposureSharingService.getOthersKeys(result.contents) { key: AppleLegacyKeyExchange.Key? ->
+            Timber.i("Keys scanned: %s", key)
+            if (key == null) {
+                Toast.makeText(
+                    requireContext(), "No Key data found in QR code", Toast.LENGTH_SHORT
+                ).show()
+                return@getOthersKeys Unit
+            }
+
+            val text = binding.transmissionNumber.text.toString()
+            val number = if (!text.isBlank()) Integer.valueOf(text) else 5
+            vm.provideDiagnosisKey(number, key)
+        }
+    }
+
+    companion object {
+        val TAG: String = TestRiskLevelCalculationFragment::class.simpleName!!
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5628d6c968e6d6870818ee6cca169335f89d306f
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt
@@ -0,0 +1,296 @@
+package de.rki.coronawarnapp.test.risklevel.ui
+
+import android.content.Context
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.viewModelScope
+import com.google.android.gms.nearby.exposurenotification.ExposureInformation
+import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.TransactionException
+import de.rki.coronawarnapp.exception.reporting.report
+import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
+import de.rki.coronawarnapp.risk.DefaultRiskLevelCalculation
+import de.rki.coronawarnapp.risk.RiskLevel
+import de.rki.coronawarnapp.risk.TimeVariables
+import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange
+import de.rki.coronawarnapp.service.applicationconfiguration.ApplicationConfigurationService
+import de.rki.coronawarnapp.storage.AppDatabase
+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.util.KeyFileHelper
+import de.rki.coronawarnapp.util.security.SecurityHelper
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import timber.log.Timber
+import java.io.File
+import java.util.UUID
+import java.util.concurrent.TimeUnit
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
+    @Assisted private val handle: SavedStateHandle,
+    @Assisted private val exampleArg: String?,
+    private val context: Context, // App context
+    private val exposureNotificationClient: ExposureNotificationClient,
+    private val keyCacheRepository: KeyCacheRepository
+) : CWAViewModel() {
+
+    val startLocalQRCodeScanEvent = SingleLiveEvent<Unit>()
+    val riskLevelResetEvent = SingleLiveEvent<Unit>()
+    val apiKeysProvidedEvent = SingleLiveEvent<DiagnosisKeyProvidedEvent>()
+    val riskScoreState = MutableLiveData<RiskScoreState>(RiskScoreState())
+
+    init {
+        Timber.d("CWAViewModel: %s", this)
+        Timber.d("SavedStateHandle: %s", handle)
+        Timber.d("Example arg: %s", exampleArg)
+    }
+
+    fun retrieveDiagnosisKeys() {
+        viewModelScope.launch {
+            try {
+                RetrieveDiagnosisKeysTransaction.start()
+                calculateRiskLevel()
+            } catch (e: TransactionException) {
+                e.report(ExceptionCategory.INTERNAL)
+            }
+        }
+    }
+
+    fun calculateRiskLevel() {
+        viewModelScope.launch {
+            try {
+                RiskLevelTransaction.start()
+            } catch (e: TransactionException) {
+                e.report(ExceptionCategory.INTERNAL)
+            }
+        }
+    }
+
+    fun resetRiskLevel() {
+        viewModelScope.launch {
+            withContext(Dispatchers.IO) {
+                try {
+                    // Preference reset
+                    SecurityHelper.resetSharedPrefs()
+                    // Database Reset
+                    AppDatabase.reset(context)
+                    // Export File Reset
+                    keyCacheRepository.clear()
+
+                    LocalData.lastCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw)
+                    LocalData.lastSuccessfullyCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw)
+                    LocalData.lastTimeDiagnosisKeysFromServerFetch(null)
+                    LocalData.googleApiToken(null)
+                } catch (e: Exception) {
+                    e.report(ExceptionCategory.INTERNAL)
+                }
+            }
+            RiskLevelTransaction.start()
+            riskLevelResetEvent.postValue(Unit)
+        }
+    }
+
+    data class RiskScoreState(
+        val riskScoreMsg: String = "",
+        val backendParameters: String = "",
+        val exposureSummary: String = "",
+        val formula: String = "",
+        val fullConfig: String = "",
+        val exposureInfo: String = ""
+    )
+
+    fun startENFObserver() {
+        viewModelScope.launch {
+            try {
+                var workState = riskScoreState.value!!
+
+                val googleToken = LocalData.googleApiToken() ?: UUID.randomUUID().toString()
+                val exposureSummary =
+                    InternalExposureNotificationClient.asyncGetExposureSummary(googleToken)
+
+                val appConfig =
+                    ApplicationConfigurationService.asyncRetrieveApplicationConfiguration()
+
+                val riskLevelScore = DefaultRiskLevelCalculation().calculateRiskScore(
+                    appConfig.attenuationDuration,
+                    exposureSummary
+                )
+
+                val riskAsString = "Level: ${RiskLevelRepository.getLastCalculatedScore()}\n" +
+                    "Last successful Level: " +
+                    "${LocalData.lastSuccessfullyCalculatedRiskLevel()}\n" +
+                    "Calculated Score: ${riskLevelScore}\n" +
+                    "Last Time Server Fetch: ${LocalData.lastTimeDiagnosisKeysFromServerFetch()}\n" +
+                    "Tracing Duration: " +
+                    "${TimeUnit.MILLISECONDS.toDays(TimeVariables.getTimeActiveTracingDuration())} days \n" +
+                    "Tracing Duration in last 14 days: " +
+                    "${TimeVariables.getActiveTracingDaysInRetentionPeriod()} days \n" +
+                    "Last time risk level calculation ${LocalData.lastTimeRiskLevelCalculation()}"
+
+                workState = workState.copy(riskScoreMsg = riskAsString)
+
+                val lowClass =
+                    appConfig.riskScoreClasses?.riskClassesList?.find { low -> low.label == "LOW" }
+                val highClass =
+                    appConfig.riskScoreClasses?.riskClassesList?.find { high -> high.label == "HIGH" }
+
+                val configAsString =
+                    "Attenuation Weight Low: ${appConfig.attenuationDuration?.weights?.low}\n" +
+                        "Attenuation Weight Mid: ${appConfig.attenuationDuration?.weights?.mid}\n" +
+                        "Attenuation Weight High: ${appConfig.attenuationDuration?.weights?.high}\n\n" +
+                        "Attenuation Offset: ${appConfig.attenuationDuration?.defaultBucketOffset}\n" +
+                        "Attenuation Normalization: " +
+                        "${appConfig.attenuationDuration?.riskScoreNormalizationDivisor}\n\n" +
+                        "Risk Score Low Class: ${lowClass?.min ?: 0} - ${lowClass?.max ?: 0}\n" +
+                        "Risk Score High Class: ${highClass?.min ?: 0} - ${highClass?.max ?: 0}"
+
+                workState = workState.copy(backendParameters = configAsString)
+
+                val summaryAsString =
+                    "Days Since Last Exposure: ${exposureSummary.daysSinceLastExposure}\n" +
+                        "Matched Key Count: ${exposureSummary.matchedKeyCount}\n" +
+                        "Maximum Risk Score: ${exposureSummary.maximumRiskScore}\n" +
+                        "Attenuation Durations: [${
+                            exposureSummary.attenuationDurationsInMinutes?.get(
+                                0
+                            )
+                        }," +
+                        "${exposureSummary.attenuationDurationsInMinutes?.get(1)}," +
+                        "${exposureSummary.attenuationDurationsInMinutes?.get(2)}]\n" +
+                        "Summation Risk Score: ${exposureSummary.summationRiskScore}"
+
+                workState = workState.copy(exposureSummary = summaryAsString)
+
+                val maxRisk = exposureSummary.maximumRiskScore
+                val atWeights = appConfig.attenuationDuration?.weights
+                val attenuationDurationInMin =
+                    exposureSummary.attenuationDurationsInMinutes
+                val attenuationConfig = appConfig.attenuationDuration
+                val formulaString =
+                    "($maxRisk / ${attenuationConfig?.riskScoreNormalizationDivisor}) * " +
+                        "(${attenuationDurationInMin?.get(0)} * ${atWeights?.low} " +
+                        "+ ${attenuationDurationInMin?.get(1)} * ${atWeights?.mid} " +
+                        "+ ${attenuationDurationInMin?.get(2)} * ${atWeights?.high} " +
+                        "+ ${attenuationConfig?.defaultBucketOffset})"
+
+                workState =
+                    workState.copy(formula = formulaString, fullConfig = appConfig.toString())
+
+                val token = LocalData.googleApiToken()
+                if (token != null) {
+                    val exposureInformation = asyncGetExposureInformation(token)
+
+                    var infoString = ""
+                    exposureInformation.forEach {
+                        infoString += "Attenuation duration in min.: " +
+                            "[${it.attenuationDurationsInMinutes?.get(0)}, " +
+                            "${it.attenuationDurationsInMinutes?.get(1)}," +
+                            "${it.attenuationDurationsInMinutes?.get(2)}]\n" +
+                            "Attenuation value: ${it.attenuationValue}\n" +
+                            "Duration in min.: ${it.durationMinutes}\n" +
+                            "Risk Score: ${it.totalRiskScore}\n" +
+                            "Transmission Risk Level: ${it.transmissionRiskLevel}\n" +
+                            "Date Millis Since Epoch: ${it.dateMillisSinceEpoch}\n\n"
+                    }
+
+                    workState = workState.copy(exposureInfo = infoString)
+                }
+
+                riskScoreState.postValue(workState)
+            } catch (e: Exception) {
+                e.report(ExceptionCategory.EXPOSURENOTIFICATION)
+            }
+        }
+    }
+
+    private suspend fun asyncGetExposureInformation(token: String): List<ExposureInformation> =
+        suspendCoroutine { cont ->
+            exposureNotificationClient.getExposureInformation(token)
+                .addOnSuccessListener {
+                    cont.resume(it)
+                }.addOnFailureListener {
+                    cont.resumeWithException(it)
+                }
+        }
+
+    data class DiagnosisKeyProvidedEvent(
+        val keyCount: Int,
+        val token: String
+    )
+
+    fun provideDiagnosisKey(transmissionNumber: Int, key: AppleLegacyKeyExchange.Key) {
+        val token = UUID.randomUUID().toString()
+        LocalData.googleApiToken(token)
+
+        val appleKeyList = mutableListOf<AppleLegacyKeyExchange.Key>()
+
+        AppleLegacyKeyExchange.Key.newBuilder()
+            .setKeyData(key.keyData)
+            .setRollingPeriod(144)
+            .setRollingStartNumber(key.rollingStartNumber)
+            .setTransmissionRiskLevel(transmissionNumber)
+            .build()
+            .also { appleKeyList.add(it) }
+
+        val appleFiles = listOf(
+            AppleLegacyKeyExchange.File.newBuilder()
+                .addAllKeys(appleKeyList)
+                .build()
+        )
+
+        val dir = File(File(context.getExternalFilesDir(null), "key-export"), token)
+        dir.mkdirs()
+
+        var googleFileList: List<File>
+        viewModelScope.launch {
+            googleFileList = KeyFileHelper.asyncCreateExportFiles(appleFiles, dir)
+
+            Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys with token $token")
+            try {
+                // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API
+                InternalExposureNotificationClient.asyncProvideDiagnosisKeys(
+                    googleFileList,
+                    ApplicationConfigurationService.asyncRetrieveExposureConfiguration(),
+                    token
+                )
+                apiKeysProvidedEvent.postValue(
+                    DiagnosisKeyProvidedEvent(
+                        keyCount = appleFiles.size,
+                        token = token
+                    )
+                )
+            } catch (e: Exception) {
+                e.report(ExceptionCategory.EXPOSURENOTIFICATION)
+            }
+        }
+    }
+
+    fun scanLocalQRCodeAndProvide() {
+        startLocalQRCodeScanEvent.postValue(Unit)
+    }
+
+    fun clearKeyCache() {
+        viewModelScope.launch { keyCacheRepository.clear() }
+    }
+
+    @AssistedInject.Factory
+    interface Factory : CWAViewModelFactory<TestRiskLevelCalculationFragmentCWAViewModel> {
+        fun create(
+            handle: SavedStateHandle,
+            exampleArg: String?
+        ): TestRiskLevelCalculationFragmentCWAViewModel
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1824e96499f7de067d31fa2630bf874b13d30cf5
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentModule.kt
@@ -0,0 +1,16 @@
+package de.rki.coronawarnapp.test.risklevel.ui
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class TestRiskLevelCalculationFragmentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(TestRiskLevelCalculationFragmentCWAViewModel::class)
+    abstract fun testRiskLevelFragment(factory: TestRiskLevelCalculationFragmentCWAViewModel.Factory): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e4371986903d63f93649895ecb25372344ad7e2c
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.ui.main
+
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.test.api.ui.TestForAPIFragment
+import de.rki.coronawarnapp.test.api.ui.TestForApiFragmentModule
+import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment
+import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragmentModule
+
+@Module
+abstract class MainActivityTestModule {
+
+    @ContributesAndroidInjector(modules = [TestRiskLevelCalculationFragmentModule::class])
+    abstract fun testRiskLevelCalculationFragment(): TestRiskLevelCalculationFragment
+
+    @ContributesAndroidInjector(modules = [TestForApiFragmentModule::class])
+    abstract fun testRiskLevelApiFragment(): TestForAPIFragment
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainFragment.kt
index add678b4840fa4a359551dc0edb97f8dea0ab5c0..1ae185f1a87a56320eb6d204153c41c9c7feca0b 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainFragment.kt
@@ -1,9 +1,7 @@
 package de.rki.coronawarnapp.ui.main
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import android.widget.PopupMenu
 import androidx.fragment.app.Fragment
@@ -16,12 +14,12 @@ 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
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.ExternalActionHelper
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -36,32 +34,19 @@ import kotlinx.coroutines.withContext
  * @see submissionViewModel
  * @see PopupMenu
  */
-class MainFragment : Fragment() {
-
-    companion object {
-        private val TAG: String? = MainFragment::class.simpleName
-    }
+class MainFragment : Fragment(R.layout.fragment_main) {
 
     private val tracingViewModel: TracingViewModel by activityViewModels()
     private val settingsViewModel: SettingsViewModel by activityViewModels()
     private val submissionViewModel: SubmissionViewModel by activityViewModels()
-    private var binding: FragmentMainBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentMainBinding.inflate(inflater)
+    private val binding: FragmentMainBinding by viewBindingLazy()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
         binding.tracingViewModel = tracingViewModel
         binding.settingsViewModel = settingsViewModel
         binding.submissionViewModel = submissionViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
 
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
         setButtonOnClickListener()
 
         showOneTimeTracingExplanationDialog()
@@ -171,7 +156,9 @@ class MainFragment : Fragment() {
                 }
                 R.id.menu_test_risk_level -> {
                     findNavController().doNavigate(
-                        MainFragmentDirections.actionMainFragmentToTestRiskLevelCalculation()
+                        MainFragmentDirections.actionMainFragmentToTestRiskLevelCalculation(
+                            exampleArgument = null
+                        )
                     )
                     true
                 }
diff --git a/Corona-Warn-App/src/deviceForTesters/res/navigation/nav_graph.xml b/Corona-Warn-App/src/deviceForTesters/res/navigation/nav_graph.xml
index 29c72aa41190ca9bb3b03a6318f1e155d74c97f9..43fb6ab2caa3ce823f5a146236074240695ab3f8 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/navigation/nav_graph.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/navigation/nav_graph.xml
@@ -174,7 +174,7 @@
     <!-- Submission -->
     <fragment
         android:id="@+id/testForAPIFragment"
-        android:name="de.rki.coronawarnapp.test.TestForAPIFragment"
+        android:name="de.rki.coronawarnapp.test.api.ui.TestForAPIFragment"
         android:label="@layout/fragment_test_for_a_p_i"
         tools:layout="@layout/fragment_test_for_a_p_i" />
 
@@ -315,7 +315,12 @@
     </fragment>
     <fragment
         android:id="@+id/testRiskLevelCalculation"
-        android:name="de.rki.coronawarnapp.test.TestRiskLevelCalculationFragment"
+        android:name="de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment"
         android:label="fragment_test_risk_level_calculation"
-        tools:layout="@layout/fragment_test_risk_level_calculation" />
+        tools:layout="@layout/fragment_test_risk_level_calculation">
+        <argument
+            android:name="exampleArgument"
+            app:argType="string"
+            app:nullable="true" />
+    </fragment>
 </navigation>
diff --git a/Corona-Warn-App/src/main/assets/technical_de.html b/Corona-Warn-App/src/main/assets/technical_de.html
index 8fef2897c5e5f5d4e4fd0e5d0f06ab0db5d5fd31..cb8322cc84be6d36cdaf9fe0731b9a6bab8317f6 100644
--- a/Corona-Warn-App/src/main/assets/technical_de.html
+++ b/Corona-Warn-App/src/main/assets/technical_de.html
@@ -62,6 +62,14 @@
     Licensor: Google
     Website: https://github.com/google/protobuf-gradle-plugin
     License: BSD 3-Clause</p>
+<p>Component: Dagger
+    Licensor: Google
+    Website: https://github.com/google/dagger
+    License: Apache 2.0</p>
+<p>Component: AssistedInject
+    Licensor: Square
+    Website: https://github.com/square/AssistedInject
+    License: Apache 2.0</p>
 <p>Component: Conscrypt
     Licensor: Conscrypt
     Website: https://github.com/google/conscrypt
diff --git a/Corona-Warn-App/src/main/assets/technical_en.html b/Corona-Warn-App/src/main/assets/technical_en.html
index 8fef2897c5e5f5d4e4fd0e5d0f06ab0db5d5fd31..cb8322cc84be6d36cdaf9fe0731b9a6bab8317f6 100644
--- a/Corona-Warn-App/src/main/assets/technical_en.html
+++ b/Corona-Warn-App/src/main/assets/technical_en.html
@@ -62,6 +62,14 @@
     Licensor: Google
     Website: https://github.com/google/protobuf-gradle-plugin
     License: BSD 3-Clause</p>
+<p>Component: Dagger
+    Licensor: Google
+    Website: https://github.com/google/dagger
+    License: Apache 2.0</p>
+<p>Component: AssistedInject
+    Licensor: Square
+    Website: https://github.com/square/AssistedInject
+    License: Apache 2.0</p>
 <p>Component: Conscrypt
     Licensor: Conscrypt
     Website: https://github.com/google/conscrypt
diff --git a/Corona-Warn-App/src/main/assets/technical_tr.html b/Corona-Warn-App/src/main/assets/technical_tr.html
index 8fef2897c5e5f5d4e4fd0e5d0f06ab0db5d5fd31..cb8322cc84be6d36cdaf9fe0731b9a6bab8317f6 100644
--- a/Corona-Warn-App/src/main/assets/technical_tr.html
+++ b/Corona-Warn-App/src/main/assets/technical_tr.html
@@ -62,6 +62,14 @@
     Licensor: Google
     Website: https://github.com/google/protobuf-gradle-plugin
     License: BSD 3-Clause</p>
+<p>Component: Dagger
+    Licensor: Google
+    Website: https://github.com/google/dagger
+    License: Apache 2.0</p>
+<p>Component: AssistedInject
+    Licensor: Square
+    Website: https://github.com/square/AssistedInject
+    License: Apache 2.0</p>
 <p>Component: Conscrypt
     Licensor: Conscrypt
     Website: https://github.com/google/conscrypt
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
index 9369e2914894cdf8b15bc3ac7f84691c90165bbe..cd26d162082c879c4b44e3a4beba10c9c18403e8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
@@ -65,12 +65,12 @@ class CoronaWarnApplication : Application(), LifecycleObserver,
 
     @Inject
     lateinit var androidInjector: DispatchingAndroidInjector<Any>
+    override fun androidInjector(): AndroidInjector<Any> = androidInjector
 
     override fun onCreate() {
-        AppInjector.init(this)
-
-        super.onCreate()
         instance = this
+        super.onCreate()
+        AppInjector.init(this)
 
         if (CWADebug.isDebugBuildOrMode) System.setProperty("kotlinx.coroutines.debug", "on")
 
@@ -197,6 +197,4 @@ class CoronaWarnApplication : Application(), LifecycleObserver,
         LocalBroadcastManager.getInstance(this)
             .registerReceiver(errorReceiver, IntentFilter(ERROR_REPORT_LOCAL_BROADCAST_CHANNEL))
     }
-
-    override fun androidInjector(): AndroidInjector<Any> = androidInjector
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/NearbyModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/NearbyModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e35491eafaf8d414ee359c84ba80a7567db619bb
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/NearbyModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.nearby
+
+import android.content.Context
+import com.google.android.gms.nearby.Nearby
+import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient
+import dagger.Module
+import dagger.Provides
+import dagger.Reusable
+
+@Module
+class NearbyModule {
+
+    @Reusable
+    @Provides
+    fun provideENF(context: Context): ExposureNotificationClient {
+        return Nearby.getExposureNotificationClient(context)
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/timer/TimerHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/timer/TimerHelper.kt
index ac56b4c5df61a79b2d19a485ec7e876674aceb14..96c90c0f6beb744030269b9f6268694af52c425c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/timer/TimerHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/timer/TimerHelper.kt
@@ -120,7 +120,6 @@ object TimerHelper {
      * @see getManualKeyRetrievalTimeLeft
      * @see SettingsRepository.updateManualKeyRetrievalEnabled
      * @see SettingsRepository.updateManualKeyRetrievalTime
-     * @see de.rki.coronawarnapp.util.TimeAndDateExtensions.millisecondsToHMS
      */
     private fun onManualKeyRetrievalTimerTick() {
         val timeDifference = getManualKeyRetrievalTimeLeft()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt
index aaf402da012fd5a83cd513490e90745b6803ddf5..011e464a4139ce61ae3d27277f58feee268fd9ef 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt
@@ -4,12 +4,13 @@ import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.main.MainActivityModule
+import de.rki.coronawarnapp.ui.main.MainActivityTestModule
 import de.rki.coronawarnapp.ui.onboarding.OnboardingActivity
 import de.rki.coronawarnapp.ui.onboarding.OnboardingActivityModule
 
 @Module
 abstract class ActivityBinder {
-    @ContributesAndroidInjector(modules = [MainActivityModule::class])
+    @ContributesAndroidInjector(modules = [MainActivityModule::class, MainActivityTestModule::class])
     abstract fun mainActivity(): MainActivity
 
     @ContributesAndroidInjector
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt
index c5e7e7d00060b30ac1aeb7d32b24a97794e89d39..1f55312ecea5c8dd259a6445f235aca801693e86 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt
@@ -3,11 +3,11 @@ package de.rki.coronawarnapp.ui
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
 import androidx.lifecycle.lifecycleScope
-import dagger.android.AndroidInjection
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.onboarding.OnboardingActivity
 import de.rki.coronawarnapp.update.UpdateChecker
+import de.rki.coronawarnapp.util.di.AppInjector
 import kotlinx.coroutines.launch
 
 class LauncherActivity : AppCompatActivity() {
@@ -18,7 +18,7 @@ class LauncherActivity : AppCompatActivity() {
     private lateinit var updateChecker: UpdateChecker
 
     override fun onCreate(savedInstanceState: Bundle?) {
-        AndroidInjection.inject(this)
+        AppInjector.setup(this)
         super.onCreate(savedInstanceState)
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt
index ed36ea61f2a800d739395fcb5c751463386f02a2..ed1f696f784ab156db60f8c31b7db67cd5917bf2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt
@@ -1,15 +1,7 @@
 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
@@ -21,47 +13,3 @@ 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
-        }
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationAboutFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationAboutFragment.kt
index 3b98d61d742bc64da47e392a3b429f306d01833c..e0e7143283072ad7e0f69c958fd67cbe954f78e1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationAboutFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationAboutFragment.kt
@@ -3,34 +3,20 @@ package de.rki.coronawarnapp.ui.information
 import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 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
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * Basic Fragment which only displays static content.
  */
-class InformationAboutFragment : Fragment() {
-    companion object {
-        private val TAG: String? = InformationAboutFragment::class.simpleName
-    }
-
-    private var binding: FragmentInformationAboutBinding by viewLifecycle()
+class InformationAboutFragment : Fragment(R.layout.fragment_information_about) {
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentInformationAboutBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentInformationAboutBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt
index 5d42b024caaf792d54e0e7008c1f6295eda6db78..61f6ff9699e927de917d74d06715ba2ae978f1b0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt
@@ -1,35 +1,21 @@
 package de.rki.coronawarnapp.ui.information
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 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
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * Basic Fragment which only displays static content.
  */
-class InformationContactFragment : Fragment() {
-    companion object {
-        private val TAG: String? = InformationContactFragment::class.simpleName
-    }
-
-    private var binding: FragmentInformationContactBinding by viewLifecycle()
+class InformationContactFragment : Fragment(R.layout.fragment_information_contact) {
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentInformationContactBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentInformationContactBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt
index 418455b8bffdce3ed6fd8db125d7d022f5c9a866..e6b1683ce430f4073e569959a96785f79ae6eec2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt
@@ -1,9 +1,7 @@
 package de.rki.coronawarnapp.ui.information
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
 import androidx.fragment.app.Fragment
@@ -12,27 +10,15 @@ 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
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * Basic Fragment which links to static and web content.
  */
-class InformationFragment : Fragment() {
-    companion object {
-        private val TAG: String? = InformationFragment::class.simpleName
-    }
-
-    private var binding: FragmentInformationBinding by viewLifecycle()
+class InformationFragment : Fragment(R.layout.fragment_information) {
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentInformationBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentInformationBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationLegalFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationLegalFragment.kt
index 62a42f82fdbb112b6b31e517d25f0700c106dc91..560b64eb7029a5b307952a82fcac44b6d9fce725 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationLegalFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationLegalFragment.kt
@@ -2,35 +2,21 @@ package de.rki.coronawarnapp.ui.information
 
 import android.os.Bundle
 import android.text.method.LinkMovementMethod
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 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
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * Basic Fragment which only displays static content.
  */
-class InformationLegalFragment : Fragment() {
-    companion object {
-        private val TAG: String? = InformationLegalFragment::class.simpleName
-    }
-
-    private var binding: FragmentInformationLegalBinding by viewLifecycle()
+class InformationLegalFragment : Fragment(R.layout.fragment_information_legal) {
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentInformationLegalBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentInformationLegalBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationPrivacyFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationPrivacyFragment.kt
index 2aede5973617d47d3e8c77d9e15537ba52efb2c0..41c3cde335afb3b4368409540c49c09ba11a3233 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationPrivacyFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationPrivacyFragment.kt
@@ -1,33 +1,19 @@
 package de.rki.coronawarnapp.ui.information
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentInformationPrivacyBinding
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * Basic Fragment which only displays static content.
  */
-class InformationPrivacyFragment : Fragment() {
-    companion object {
-        private val TAG: String? = InformationPrivacyFragment::class.simpleName
-    }
-
-    private var binding: FragmentInformationPrivacyBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentInformationPrivacyBinding.inflate(inflater)
-        return binding.root
-    }
+class InformationPrivacyFragment : Fragment(R.layout.fragment_information_privacy) {
+    private val binding: FragmentInformationPrivacyBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationTechnicalFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationTechnicalFragment.kt
index 193fde9c2640c0ff26792d77830ae978b6deaec2..0a98ff88bfbcd7352f9b0d3260b3116e8f069b5d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationTechnicalFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationTechnicalFragment.kt
@@ -1,33 +1,23 @@
 package de.rki.coronawarnapp.ui.information
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentInformationTechnicalBinding
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * Basic Fragment which only displays static content.
  */
-class InformationTechnicalFragment : Fragment() {
+class InformationTechnicalFragment : Fragment(R.layout.fragment_information_technical) {
     companion object {
         private val TAG: String? = InformationTechnicalFragment::class.simpleName
     }
 
-    private var binding: FragmentInformationTechnicalBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentInformationTechnicalBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentInformationTechnicalBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationTermsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationTermsFragment.kt
index 592c36df7c4a5949313f1b06580507292df0c3e0..5c0046ebea1ee3ed37a93482be699babbc0955c9 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationTermsFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationTermsFragment.kt
@@ -1,33 +1,20 @@
 package de.rki.coronawarnapp.ui.information
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentInformationTermsBinding
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * Basic Fragment which only displays static content.
  */
-class InformationTermsFragment : Fragment() {
-    companion object {
-        private val TAG: String? = InformationTermsFragment::class.simpleName
-    }
-
-    private var binding: FragmentInformationTermsBinding by viewLifecycle()
+class InformationTermsFragment : Fragment(R.layout.fragment_information_terms) {
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentInformationTermsBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentInformationTermsBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt
index c4bbd54c91d8ae652a0d448a631a385145261591..63af50f06e1e574c7a09740c299981e97248b7ea 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt
@@ -10,7 +10,9 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentManager
 import androidx.lifecycle.ViewModelProviders
 import androidx.lifecycle.lifecycleScope
-import dagger.android.AndroidInjection
+import dagger.android.AndroidInjector
+import dagger.android.DispatchingAndroidInjector
+import dagger.android.HasAndroidInjector
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.http.playbook.BackgroundNoise
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
@@ -21,6 +23,7 @@ import de.rki.coronawarnapp.util.BackgroundPrioritization
 import de.rki.coronawarnapp.util.ConnectivityHelper
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.device.PowerManagement
+import de.rki.coronawarnapp.util.di.AppInjector
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
 import kotlinx.coroutines.launch
 import timber.log.Timber
@@ -34,7 +37,7 @@ import javax.inject.Inject
  * @see ConnectivityHelper
  * @see BackgroundWorkScheduler
  */
-class MainActivity : AppCompatActivity() {
+class MainActivity : AppCompatActivity(), HasAndroidInjector {
     companion object {
         private val TAG: String? = MainActivity::class.simpleName
 
@@ -43,6 +46,9 @@ class MainActivity : AppCompatActivity() {
         }
     }
 
+    @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
+    override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector
+
     private val FragmentManager.currentNavigationFragment: Fragment?
         get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first()
 
@@ -89,14 +95,15 @@ class MainActivity : AppCompatActivity() {
         }
 
         override fun onLocationUnavailable() {
-            val canIgnoreLocationEnabled = InternalExposureNotificationClient.deviceSupportsLocationlessScanning()
+            val canIgnoreLocationEnabled =
+                InternalExposureNotificationClient.deviceSupportsLocationlessScanning()
             settingsViewModel.updateLocationEnabled(canIgnoreLocationEnabled)
             Timber.d("Location unavailable but can be ignored? $canIgnoreLocationEnabled")
         }
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
-        AndroidInjection.inject(this)
+        AppInjector.setup(this)
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
         settingsViewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt
index aa437a80b60dcf6fa79874ef212652d6bb88a62d..c99a5dba307a8486871d86d3fd8929817ed8fb26 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt
@@ -1,13 +1,12 @@
 package de.rki.coronawarnapp.ui.main
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentMainOverviewBinding
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * The fragment displays static informative content to the user
@@ -16,22 +15,9 @@ import de.rki.coronawarnapp.ui.viewLifecycle
  *
  */
 
-class MainOverviewFragment : Fragment() {
+class MainOverviewFragment : Fragment(R.layout.fragment_main_overview) {
 
-    companion object {
-        private val TAG: String? = MainOverviewFragment::class.simpleName
-    }
-
-    private var binding: FragmentMainOverviewBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentMainOverviewBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentMainOverviewBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt
index 5dd447eaf6cf91b64866836419e60e930537efaf..a82e0c320d3edbd0dd6bc5dae69918206681f5c4 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt
@@ -1,45 +1,29 @@
 package de.rki.coronawarnapp.ui.main
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentMainShareBinding
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.ExternalActionHelper
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * This fragment informs the user about what he is going to share and how he is going to help everybody with this :)
  *
  * @see TracingViewModel
  */
-class MainShareFragment : Fragment() {
-
-    companion object {
-        private val TAG: String? = MainShareFragment::class.simpleName
-    }
+class MainShareFragment : Fragment(R.layout.fragment_main_share) {
 
     private val tracingViewModel: TracingViewModel by activityViewModels()
-    private var binding: FragmentMainShareBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentMainShareBinding.inflate(inflater)
-        binding.tracingViewModel = tracingViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
+    private val binding: FragmentMainShareBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        binding.tracingViewModel = tracingViewModel
         setButtonOnClickListener()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt
index 81d9648ee0bfa308e26ba266bfc02846a4680be4..0e17e71226144c4da0f24ee5e76edb633ea3384a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt
@@ -7,10 +7,10 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentManager
 import androidx.lifecycle.LifecycleObserver
-import dagger.android.AndroidInjection
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.ui.main.MainActivity
+import de.rki.coronawarnapp.util.di.AppInjector
 
 /**
  * This activity holds all the onboarding fragments and isn't used after a successful onboarding flow.
@@ -31,7 +31,7 @@ class OnboardingActivity : AppCompatActivity(), LifecycleObserver {
         get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first()
 
     override fun onCreate(savedInstanceState: Bundle?) {
-        AndroidInjection.inject(this)
+        AppInjector.setup(this)
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_onboarding)
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragment.kt
index b1daa10c00314fb9dae088f67cdecc786cc43a9c..df4b0d3f6735f76ff3e8f1f778ef88774802449a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragment.kt
@@ -3,35 +3,21 @@ package de.rki.coronawarnapp.ui.onboarding
 import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentOnboardingBinding
 import de.rki.coronawarnapp.ui.doNavigate
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * Onboarding starting point.
  */
-class OnboardingFragment : Fragment() {
-    companion object {
-        private val TAG: String? = OnboardingFragment::class.simpleName
-    }
-
-    private var binding: FragmentOnboardingBinding by viewLifecycle()
+class OnboardingFragment : Fragment(R.layout.fragment_onboarding) {
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentOnboardingBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentOnboardingBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
@@ -46,10 +32,12 @@ class OnboardingFragment : Fragment() {
     private fun setLinks() {
         binding.onboardingInclude.onboardingEasyLanguage
             .setOnClickListener {
-            val browserIntent = Intent(Intent.ACTION_VIEW,
-                Uri.parse(getString(R.string.onboarding_tracing_easy_language_explanation_url)))
-            startActivity(browserIntent)
-        }
+                val browserIntent = Intent(
+                    Intent.ACTION_VIEW,
+                    Uri.parse(getString(R.string.onboarding_tracing_easy_language_explanation_url))
+                )
+                startActivity(browserIntent)
+            }
     }
 
     override fun onResume() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt
index 5813171099375402630300b5cf0dcccaf25069c7..7ebb2b1d16e1886134c81ebae426795184b9e380 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt
@@ -1,15 +1,14 @@
 package de.rki.coronawarnapp.ui.onboarding
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.appcompat.app.AlertDialog
 import androidx.core.app.NotificationManagerCompat
 import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentOnboardingNotificationsBinding
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * This fragment ask the user if he wants to get notifications and finishes the onboarding afterwards.
@@ -17,21 +16,9 @@ import de.rki.coronawarnapp.ui.viewLifecycle
  * @see NotificationManagerCompat
  * @see AlertDialog
  */
-class OnboardingNotificationsFragment : Fragment() {
-    companion object {
-        private val TAG: String? = OnboardingNotificationsFragment::class.simpleName
-    }
-
-    private var binding: FragmentOnboardingNotificationsBinding by viewLifecycle()
+class OnboardingNotificationsFragment : Fragment(R.layout.fragment_onboarding_notifications) {
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentOnboardingNotificationsBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentOnboardingNotificationsBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingPrivacyFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingPrivacyFragment.kt
index f81ec47d1e0a1524596815713ce7e24158371454..010b629c714a490b2f1e40d4f31123a46726c6c8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingPrivacyFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingPrivacyFragment.kt
@@ -1,34 +1,21 @@
 package de.rki.coronawarnapp.ui.onboarding
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentOnboardingPrivacyBinding
 import de.rki.coronawarnapp.ui.doNavigate
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * This fragment informs the user regarding privacy.
  */
-class OnboardingPrivacyFragment : Fragment() {
-    companion object {
-        private val TAG: String? = OnboardingPrivacyFragment::class.simpleName
-    }
-
-    private var binding: FragmentOnboardingPrivacyBinding by viewLifecycle()
+class OnboardingPrivacyFragment : Fragment(R.layout.fragment_onboarding_privacy) {
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentOnboardingPrivacyBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentOnboardingPrivacyBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragment.kt
index aacc99a5e1d01e2bf193804ceccda1a63408c86a..a66ac14e56298884c776a73c3e6395f7a0dab7c0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragment.kt
@@ -1,34 +1,20 @@
 package de.rki.coronawarnapp.ui.onboarding
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentOnboardingTestBinding
 import de.rki.coronawarnapp.ui.doNavigate
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * This fragment informs the user about test results.
  */
-class OnboardingTestFragment : Fragment() {
-    companion object {
-        private val TAG: String? = OnboardingTestFragment::class.simpleName
-    }
-
-    private var binding: FragmentOnboardingTestBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentOnboardingTestBinding.inflate(inflater)
-        return binding.root
-    }
+class OnboardingTestFragment : Fragment(R.layout.fragment_onboarding_test) {
+    private val binding: FragmentOnboardingTestBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt
index 1d8e08fdffc83e8b9261f289162ba632a0d57480..de87c54f4b510af006247f8b04779729b210f72b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt
@@ -2,9 +2,7 @@ package de.rki.coronawarnapp.ui.onboarding
 
 import android.content.Intent
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.appcompat.app.AlertDialog
 import androidx.fragment.app.Fragment
@@ -18,8 +16,8 @@ import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.ui.doNavigate
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import kotlinx.coroutines.launch
 
 /**
@@ -28,7 +26,7 @@ import kotlinx.coroutines.launch
  * @see InternalExposureNotificationPermissionHelper
  * @see AlertDialog
  */
-class OnboardingTracingFragment : Fragment(),
+class OnboardingTracingFragment : Fragment(R.layout.fragment_onboarding_tracing),
     InternalExposureNotificationPermissionHelper.Callback {
 
     companion object {
@@ -36,7 +34,7 @@ class OnboardingTracingFragment : Fragment(),
     }
 
     private lateinit var internalExposureNotificationPermissionHelper: InternalExposureNotificationPermissionHelper
-    private var binding: FragmentOnboardingTracingBinding by viewLifecycle()
+    private val binding: FragmentOnboardingTracingBinding by viewBindingLazy()
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
         internalExposureNotificationPermissionHelper.onResolutionComplete(
@@ -51,15 +49,6 @@ class OnboardingTracingFragment : Fragment(),
             InternalExposureNotificationPermissionHelper(this, this)
     }
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentOnboardingTracingBinding.inflate(inflater)
-        return binding.root
-    }
-
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
         setButtonOnClickListener()
@@ -123,7 +112,7 @@ class OnboardingTracingFragment : Fragment(),
             } catch (exception: Exception) {
                 exception.report(
                     ExceptionCategory.EXPOSURENOTIFICATION,
-                    OnboardingTracingFragment.TAG,
+                    TAG,
                     null
                 )
             }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragment.kt
index db5bba738605699031475f19c236acc7ac115c1f..4a7512b80e8021f39a33b706e600b2140dfcd4e0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragment.kt
@@ -1,20 +1,19 @@
 package de.rki.coronawarnapp.ui.riskdetails
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentRiskDetailsBinding
 import de.rki.coronawarnapp.timer.TimerHelper
 import de.rki.coronawarnapp.ui.doNavigate
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * This is the detail view of the risk card if additional information for the user.
@@ -22,30 +21,16 @@ import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
  * @see TracingViewModel
  * @see SettingsViewModel
  */
-class RiskDetailsFragment : Fragment() {
-
-    companion object {
-        private val TAG: String? = RiskDetailsFragment::class.simpleName
-    }
+class RiskDetailsFragment : Fragment(R.layout.fragment_risk_details) {
 
     private val tracingViewModel: TracingViewModel by activityViewModels()
     private val settingsViewModel: SettingsViewModel by activityViewModels()
-    private var binding: FragmentRiskDetailsBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentRiskDetailsBinding.inflate(inflater)
-        binding.tracingViewModel = tracingViewModel
-        binding.settingsViewModel = settingsViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
+    private val binding: FragmentRiskDetailsBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        binding.tracingViewModel = tracingViewModel
+        binding.settingsViewModel = settingsViewModel
         setButtonOnClickListeners()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsBackgroundPriorityFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsBackgroundPriorityFragment.kt
index 7385447600b0312a69f06c32da52abd4f19491c9..57db3b076f914cd0d6c72fe88b423a9e67f1d5ae 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsBackgroundPriorityFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsBackgroundPriorityFragment.kt
@@ -1,18 +1,17 @@
 package de.rki.coronawarnapp.ui.settings
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSettingsBackgroundPriorityBinding
 import de.rki.coronawarnapp.ui.base.startActivitySafely
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * This is the setting background priority page. Here the user sees the background priority setting status.
@@ -21,27 +20,15 @@ import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
  * @see TracingViewModel
  * @see SettingsViewModel
  */
-class SettingsBackgroundPriorityFragment : Fragment() {
-    companion object {
-        private val TAG: String? = SettingsBackgroundPriorityFragment::class.simpleName
-    }
+class SettingsBackgroundPriorityFragment :
+    Fragment(R.layout.fragment_settings_background_priority) {
 
     private val settingsViewModel: SettingsViewModel by activityViewModels()
-    private var binding: FragmentSettingsBackgroundPriorityBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentSettingsBackgroundPriorityBinding.inflate(inflater)
-        binding.settingsViewModel = settingsViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
+    private val binding: FragmentSettingsBackgroundPriorityBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        binding.settingsViewModel = settingsViewModel
         setButtonOnClickListener()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsFragment.kt
index 1f6338e182d5c07b3c2c7b1012bd102aa2e47793..ab2dd1d05f46a23bf02313e24ad9cc9a59f47011 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsFragment.kt
@@ -1,19 +1,18 @@
 package de.rki.coronawarnapp.ui.settings
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSettingsBinding
 import de.rki.coronawarnapp.ui.doNavigate
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * This is the setting overview page.
@@ -21,30 +20,16 @@ import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
  * @see TracingViewModel
  * @see SettingsViewModel
  */
-class SettingsFragment : Fragment() {
-
-    companion object {
-        private val TAG: String? = SettingsFragment::class.simpleName
-    }
+class SettingsFragment : Fragment(R.layout.fragment_settings) {
 
     private val tracingViewModel: TracingViewModel by activityViewModels()
     private val settingsViewModel: SettingsViewModel by activityViewModels()
-    private var binding: FragmentSettingsBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentSettingsBinding.inflate(inflater)
-        binding.tracingViewModel = tracingViewModel
-        binding.settingsViewModel = settingsViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
+    private val binding: FragmentSettingsBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        binding.tracingViewModel = tracingViewModel
+        binding.settingsViewModel = settingsViewModel
         setButtonOnClickListener()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt
index 7cbbccbfe2ef4a63c71d85f85c3f1d6cf9c1888d..ce4ac665155a1d61dafea75d6212d1781b64a929 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt
@@ -1,19 +1,18 @@
 package de.rki.coronawarnapp.ui.settings
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSettingsNotificationsBinding
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.ExternalActionHelper
 import de.rki.coronawarnapp.util.IGNORE_CHANGE_TAG
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * This is the setting notification page. Here the user sees his os notifications settings status.
@@ -23,27 +22,14 @@ import de.rki.coronawarnapp.util.IGNORE_CHANGE_TAG
  * @see TracingViewModel
  * @see SettingsViewModel
  */
-class SettingsNotificationFragment : Fragment() {
-    companion object {
-        private val TAG: String? = SettingsNotificationFragment::class.simpleName
-    }
+class SettingsNotificationFragment : Fragment(R.layout.fragment_settings_notifications) {
 
     private val settingsViewModel: SettingsViewModel by activityViewModels()
-    private var binding: FragmentSettingsNotificationsBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentSettingsNotificationsBinding.inflate(inflater)
-        binding.settingsViewModel = settingsViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
+    private val binding: FragmentSettingsNotificationsBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        binding.settingsViewModel = settingsViewModel
         setButtonOnClickListener()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetFragment.kt
index bea672e29d7244f9a346c3f99a9af9a7ae3ba9a7..74bf4859462a9971738756c3583c3ad76a96a30c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetFragment.kt
@@ -2,9 +2,7 @@ package de.rki.coronawarnapp.ui.settings
 
 import android.app.AlertDialog
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.lifecycle.lifecycleScope
@@ -16,9 +14,9 @@ import de.rki.coronawarnapp.exception.reporting.report
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.onboarding.OnboardingActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.util.DataRetentionHelper
 import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -28,22 +26,13 @@ import kotlinx.coroutines.withContext
  * The user is informed what a reset means and he can perform it.
  *
  */
-class SettingsResetFragment : Fragment() {
+class SettingsResetFragment : Fragment(R.layout.fragment_settings_reset) {
 
     companion object {
         private val TAG: String? = SettingsResetFragment::class.simpleName
     }
 
-    private var binding: FragmentSettingsResetBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentSettingsResetBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentSettingsResetBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt
index 702eebc1705fb16a1a272293323e4d3469e621e6..fef4caab8e27b670b2590a0116ab71de215eb934 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt
@@ -2,9 +2,7 @@ package de.rki.coronawarnapp.ui.settings
 
 import android.content.Intent
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
@@ -17,13 +15,13 @@ import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.ExternalActionHelper
 import de.rki.coronawarnapp.util.IGNORE_CHANGE_TAG
 import de.rki.coronawarnapp.util.formatter.formatTracingSwitchEnabled
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
 import kotlinx.coroutines.launch
 
@@ -35,7 +33,7 @@ import kotlinx.coroutines.launch
  * @see InternalExposureNotificationClient
  * @see InternalExposureNotificationPermissionHelper
  */
-class SettingsTracingFragment : Fragment(),
+class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing),
     InternalExposureNotificationPermissionHelper.Callback {
 
     companion object {
@@ -44,24 +42,14 @@ class SettingsTracingFragment : Fragment(),
 
     private val tracingViewModel: TracingViewModel by activityViewModels()
     private val settingsViewModel: SettingsViewModel by activityViewModels()
-    private var binding: FragmentSettingsTracingBinding by viewLifecycle()
+    private val binding: FragmentSettingsTracingBinding by viewBindingLazy()
 
     private lateinit var internalExposureNotificationPermissionHelper: InternalExposureNotificationPermissionHelper
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentSettingsTracingBinding.inflate(inflater)
-        binding.tracingViewModel = tracingViewModel
-        binding.settingsViewModel = settingsViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
-
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        binding.tracingViewModel = tracingViewModel
+        binding.settingsViewModel = settingsViewModel
         setButtonOnClickListener()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionContactFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionContactFragment.kt
index 222aedad14e4c730216b13ca3c194d96a61352fd..85b74e67c5c4b6010dc4e37002fed6e21c7f1e74 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionContactFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionContactFragment.kt
@@ -1,9 +1,7 @@
 package de.rki.coronawarnapp.ui.submission.fragment
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
@@ -11,25 +9,15 @@ import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionContactBinding
 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
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * The [SubmissionContactFragment] allows requesting a teletan via phone
  */
-class SubmissionContactFragment : Fragment() {
+class SubmissionContactFragment : Fragment(R.layout.fragment_submission_contact) {
 
-    private var binding: FragmentSubmissionContactBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        // get the binding reference by inflating it with the current layout
-        binding = FragmentSubmissionContactBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentSubmissionContactBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDispatcherFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDispatcherFragment.kt
index 1a82f3fb8de31c892773a64cc5f5102aaf225ba1..128285d437cf68c4bb4fa31752120117d040fcec 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDispatcherFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDispatcherFragment.kt
@@ -1,9 +1,7 @@
 package de.rki.coronawarnapp.ui.submission.fragment
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
@@ -11,26 +9,12 @@ import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionDispatcherBinding
 import de.rki.coronawarnapp.ui.doNavigate
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
-class SubmissionDispatcherFragment : Fragment() {
+class SubmissionDispatcherFragment : Fragment(R.layout.fragment_submission_dispatcher) {
 
-    companion object {
-        private val TAG: String? = SubmissionDispatcherFragment::class.simpleName
-    }
-
-    private var binding: FragmentSubmissionDispatcherBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentSubmissionDispatcherBinding.inflate(inflater)
-        binding.lifecycleOwner = this
-        return binding.root
-    }
+    private val binding: FragmentSubmissionDispatcherBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDoneFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDoneFragment.kt
index b76808583a8c3aadf49ca3b1b17469aeb72c4198..c8ebbb8a02ebd71d66e335427000101c60fb6184 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDoneFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDoneFragment.kt
@@ -1,32 +1,22 @@
 package de.rki.coronawarnapp.ui.submission.fragment
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionDoneBinding
 import de.rki.coronawarnapp.ui.doNavigate
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * The [SubmissionDoneFragment] displays information to a user that submitted his exposure keys
  */
-class SubmissionDoneFragment : Fragment() {
+class SubmissionDoneFragment : Fragment(R.layout.fragment_submission_done) {
 
-    private var binding: FragmentSubmissionDoneBinding by viewLifecycle()
+    private val binding: FragmentSubmissionDoneBinding by viewBindingLazy()
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        // get the binding reference by inflating it with the current layout
-        binding = FragmentSubmissionDoneBinding.inflate(inflater)
-        return binding.root
-    }
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
         setButtonOnClickListener()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionIntroFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionIntroFragment.kt
index e44e543222167e1fce9c3705e3d2a6d6de731684..cf00793bd02a96a8d4449a7a8df96af0a69a040d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionIntroFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionIntroFragment.kt
@@ -1,32 +1,21 @@
 package de.rki.coronawarnapp.ui.submission.fragment
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
+import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionIntroBinding
 import de.rki.coronawarnapp.ui.doNavigate
-import de.rki.coronawarnapp.ui.viewLifecycle
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * The [SubmissionIntroFragment] displays information about how the corona warning system works
  */
-class SubmissionIntroFragment : Fragment() {
+class SubmissionIntroFragment : Fragment(R.layout.fragment_submission_intro) {
 
-    private var binding: FragmentSubmissionIntroBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        // get the binding reference by inflating it with the current layout
-        binding = FragmentSubmissionIntroBinding.inflate(inflater)
-        return binding.root
-    }
+    private val binding: FragmentSubmissionIntroBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeScanFragment.kt
index 1542ea6998e8cbc47a7511bb72fea6e97624db5c..395769997db4c0b9672fd3d83ecb0b1b684a1add 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeScanFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeScanFragment.kt
@@ -3,9 +3,7 @@ package de.rki.coronawarnapp.ui.submission.fragment
 import android.Manifest
 import android.content.pm.PackageManager
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
@@ -23,36 +21,25 @@ import de.rki.coronawarnapp.ui.doNavigate
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.ui.submission.ScanStatus
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.util.CameraPermissionHelper
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.observeEvent
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * A simple [Fragment] subclass.
  */
-class SubmissionQRCodeScanFragment : Fragment() {
+class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_code_scan) {
 
     companion object {
         private const val REQUEST_CAMERA_PERMISSION_CODE = 1
-        private val TAG: String? = SubmissionQRCodeScanFragment::class.simpleName
     }
 
     private val viewModel: SubmissionViewModel by activityViewModels()
-    private var binding: FragmentSubmissionQrCodeScanBinding by viewLifecycle()
+    private val binding: FragmentSubmissionQrCodeScanBinding by viewBindingLazy()
     private var showsPermissionDialog = false
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = FragmentSubmissionQrCodeScanBinding.inflate(inflater)
-        binding.lifecycleOwner = this
-        return binding.root
-    }
-
     private fun decodeCallback(result: BarcodeResult) {
         viewModel.validateAndStoreTestGUID(result.text)
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionResultPositiveOtherWarningFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionResultPositiveOtherWarningFragment.kt
index f62c30af37cc2ce91520fa0f2064b0ce261e8a14..5f3595ba3f003ad215c284aeb5174998ed3cbbf7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionResultPositiveOtherWarningFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionResultPositiveOtherWarningFragment.kt
@@ -2,9 +2,7 @@ package de.rki.coronawarnapp.ui.submission.fragment
 
 import android.content.Intent
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.activity.OnBackPressedCallback
 import androidx.fragment.app.Fragment
@@ -21,23 +19,20 @@ import de.rki.coronawarnapp.exception.http.ForbiddenException
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper
 import de.rki.coronawarnapp.ui.doNavigate
 import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.observeEvent
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
-class SubmissionResultPositiveOtherWarningFragment : Fragment(),
+class SubmissionResultPositiveOtherWarningFragment :
+    Fragment(R.layout.fragment_submission_positive_other_warning),
     InternalExposureNotificationPermissionHelper.Callback {
 
-    companion object {
-        private val TAG: String? = SubmissionResultPositiveOtherWarningFragment::class.simpleName
-    }
-
     private val submissionViewModel: SubmissionViewModel by activityViewModels()
     private val tracingViewModel: TracingViewModel by activityViewModels()
 
-    private var binding: FragmentSubmissionPositiveOtherWarningBinding by viewLifecycle()
+    private val binding: FragmentSubmissionPositiveOtherWarningBinding by viewBindingLazy()
     private lateinit var internalExposureNotificationPermissionHelper:
             InternalExposureNotificationPermissionHelper
 
@@ -55,20 +50,6 @@ class SubmissionResultPositiveOtherWarningFragment : Fragment(),
         tracingViewModel.refreshIsTracingEnabled()
     }
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        internalExposureNotificationPermissionHelper =
-            InternalExposureNotificationPermissionHelper(this, this)
-        binding = FragmentSubmissionPositiveOtherWarningBinding.inflate(inflater)
-        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backCallback)
-        binding.submissionViewModel = submissionViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
-
     private fun buildErrorDialog(exception: Exception): DialogHelper.DialogInstance {
         return when (exception) {
             is BadRequestException -> DialogHelper.DialogInstance(
@@ -127,6 +108,12 @@ class SubmissionResultPositiveOtherWarningFragment : Fragment(),
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        binding.submissionViewModel = submissionViewModel
+
+        internalExposureNotificationPermissionHelper =
+            InternalExposureNotificationPermissionHelper(this, this)
+        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backCallback)
+
         setButtonOnClickListener()
 
         submissionViewModel.submissionError.observeEvent(viewLifecycleOwner) {
@@ -150,21 +137,23 @@ class SubmissionResultPositiveOtherWarningFragment : Fragment(),
     }
 
     /**
-    * Opens a Dialog that warns user
-    * when they're about to cancel the submission flow
-    * @see DialogHelper
-    * @see navigateToSubmissionResultFragment
-    */
+     * Opens a Dialog that warns user
+     * when they're about to cancel the submission flow
+     * @see DialogHelper
+     * @see navigateToSubmissionResultFragment
+     */
     fun handleSubmissionCancellation() {
-        DialogHelper.showDialog(DialogHelper.DialogInstance(
-            requireActivity(),
-            R.string.submission_error_dialog_confirm_cancellation_title,
-            R.string.submission_error_dialog_confirm_cancellation_body,
-            R.string.submission_error_dialog_confirm_cancellation_button_positive,
-            R.string.submission_error_dialog_confirm_cancellation_button_negative,
-            true,
-            ::navigateToSubmissionResultFragment
-        ))
+        DialogHelper.showDialog(
+            DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_error_dialog_confirm_cancellation_title,
+                R.string.submission_error_dialog_confirm_cancellation_body,
+                R.string.submission_error_dialog_confirm_cancellation_button_positive,
+                R.string.submission_error_dialog_confirm_cancellation_button_negative,
+                true,
+                ::navigateToSubmissionResultFragment
+            )
+        )
     }
 
     private fun navigateToSubmissionResultFragment() =
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTanFragment.kt
index bd8b681d6ebde3b71fb9abcc8240582bae1d3522..b078ad1f964985625e333fdeae8b0166fe800a09 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTanFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTanFragment.kt
@@ -1,9 +1,7 @@
 package de.rki.coronawarnapp.ui.submission.fragment
 
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
@@ -20,33 +18,21 @@ import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.ui.submission.TanConstants
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionTanViewModel
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.TanHelper
 import de.rki.coronawarnapp.util.observeEvent
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import kotlinx.android.synthetic.main.include_submission_tan.*
 
 /**
  * Fragment for TAN entry
  */
-class SubmissionTanFragment : Fragment() {
+class SubmissionTanFragment : Fragment(R.layout.fragment_submission_tan) {
 
     private val viewModel: SubmissionTanViewModel by viewModels()
     private val submissionViewModel: SubmissionViewModel by activityViewModels()
-    private var binding: FragmentSubmissionTanBinding by viewLifecycle()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        // get the binding reference by inflating it with the current layout
-        binding = FragmentSubmissionTanBinding.inflate(inflater)
-        binding.viewmodel = viewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
+    private val binding: FragmentSubmissionTanBinding by viewBindingLazy()
 
     private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
         return when (exception) {
@@ -97,6 +83,7 @@ class SubmissionTanFragment : Fragment() {
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        binding.viewmodel = viewModel
 
         binding.submissionTanContent.submissionTanInput.listener = { tan ->
             resetError()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt
index 3b316fa48ef13260e537ce7d58281891815976f4..cc109856925f49d24993b308bafea5a2bbe672a8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt
@@ -2,9 +2,7 @@ package de.rki.coronawarnapp.ui.submission.fragment
 
 import android.app.AlertDialog
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import androidx.activity.OnBackPressedCallback
 import androidx.fragment.app.Fragment
@@ -17,25 +15,22 @@ import de.rki.coronawarnapp.exception.http.CwaClientError
 import de.rki.coronawarnapp.exception.http.CwaServerError
 import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.ui.doNavigate
-import de.rki.coronawarnapp.ui.viewLifecycle
 import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.DeviceUIState
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.observeEvent
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
  * A simple [Fragment] subclass.
  */
-class SubmissionTestResultFragment : Fragment() {
-    companion object {
-        private val TAG: String? = SubmissionTanFragment::class.simpleName
-    }
+class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_result) {
 
     private val submissionViewModel: SubmissionViewModel by activityViewModels()
     private val tracingViewModel: TracingViewModel by activityViewModels()
 
-    private var binding: FragmentSubmissionTestResultBinding by viewLifecycle()
+    private val binding: FragmentSubmissionTestResultBinding by viewBindingLazy()
 
     private var skipInitialTestResultRefresh = false
 
@@ -49,25 +44,6 @@ class SubmissionTestResultFragment : Fragment() {
             }
         }
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        // get the binding reference by inflating it with the current layout
-        binding = FragmentSubmissionTestResultBinding.inflate(inflater)
-        binding.submissionViewModel = submissionViewModel
-        binding.lifecycleOwner = this
-        // registers callback when the os level back is pressed
-        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backCallback)
-        // Inflate the layout for this fragment
-
-        skipInitialTestResultRefresh =
-            arguments?.getBoolean("skipInitialTestResultRefresh") ?: false
-
-        return binding.root
-    }
-
     private fun navigateToMainScreen() =
         findNavController().doNavigate(
             SubmissionTestResultFragmentDirections.actionSubmissionResultFragmentToMainFragment()
@@ -113,6 +89,13 @@ class SubmissionTestResultFragment : Fragment() {
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        binding.submissionViewModel = submissionViewModel
+        // registers callback when the os level back is pressed
+        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backCallback)
+
+        skipInitialTestResultRefresh =
+            arguments?.getBoolean("skipInitialTestResultRefresh") ?: false
+
         setButtonOnClickListener()
 
         submissionViewModel.uiStateError.observeEvent(viewLifecycleOwner) {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppInjector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppInjector.kt
index daa9dc01afa8314cedb3829fa1b60a27dfae31f1..6026460d18eb05d467d670825a6ccad8e9d531b7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppInjector.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppInjector.kt
@@ -1,6 +1,14 @@
 package de.rki.coronawarnapp.util.di
 
+import android.app.Activity
+import android.content.Context
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import dagger.android.AndroidInjection
+import dagger.android.support.AndroidSupportInjection
 import de.rki.coronawarnapp.CoronaWarnApplication
+import timber.log.Timber
 
 object AppInjector {
     lateinit var component: ApplicationComponent
@@ -9,4 +17,32 @@ object AppInjector {
         component = DaggerApplicationComponent.factory().create(app)
         component.inject(app)
     }
+
+    fun setup(activity: Activity) {
+        Timber.tag(TAG).d("Injecting %s", activity)
+
+        // Using lifecycle callbacks would be even more awesome,
+        // but Activity.onPreCreate isn't available for our minAPI
+        AndroidInjection.inject(activity)
+
+        if (activity is FragmentActivity) {
+            activity.supportFragmentManager
+                .registerFragmentLifecycleCallbacks(object :
+                    FragmentManager.FragmentLifecycleCallbacks() {
+                    override fun onFragmentPreAttached(
+                        fm: FragmentManager,
+                        f: Fragment,
+                        context: Context
+                    ) {
+                        if (f is AutoInject) {
+                            Timber.tag(TAG).d("Injecting %s", f)
+                            AndroidSupportInjection.inject(f)
+                        }
+                        super.onFragmentPreAttached(fm, f, context)
+                    }
+                }, true)
+        }
+    }
+
+    private val TAG = AppInjector::class.java.simpleName
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
index 2e6290343c50436e3cc4b809fcf3fcc6d9642725..03340dbfbb1ba4e43a4da4ed8f984fde8755b4c5 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
@@ -5,6 +5,7 @@ import dagger.Component
 import dagger.android.AndroidInjector
 import dagger.android.support.AndroidSupportInjectionModule
 import de.rki.coronawarnapp.CoronaWarnApplication
+import de.rki.coronawarnapp.nearby.NearbyModule
 import de.rki.coronawarnapp.diagnosiskeys.DiagnosisKeysModule
 import de.rki.coronawarnapp.diagnosiskeys.download.KeyFileDownloader
 import de.rki.coronawarnapp.diagnosiskeys.server.AppConfigServer
@@ -28,6 +29,7 @@ import javax.inject.Singleton
 @Component(
     modules = [
         AndroidSupportInjectionModule::class,
+        AssistedInjectModule::class,
         AndroidModule::class,
         ReceiverBinder::class,
         ServiceBinder::class,
@@ -36,7 +38,8 @@ import javax.inject.Singleton
         UtilModule::class,
         DeviceModule::class,
         HttpModule::class,
-        DiagnosisKeysModule::class
+        DiagnosisKeysModule::class,
+        NearbyModule::class
     ]
 )
 interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AssistedInjectModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AssistedInjectModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..88cab114e805f64f0e01a1ab919de97c38232a83
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AssistedInjectModule.kt
@@ -0,0 +1,8 @@
+package de.rki.coronawarnapp.util.di
+
+import com.squareup.inject.assisted.dagger2.AssistedModule
+import dagger.Module
+
+@AssistedModule
+@Module(includes = [AssistedInject_AssistedInjectModule::class])
+interface AssistedInjectModule
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AutoInject.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AutoInject.kt
new file mode 100644
index 0000000000000000000000000000000000000000..aea8f758de59fbee0f287d24bb6c27f0909e6f87
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AutoInject.kt
@@ -0,0 +1,4 @@
+package de.rki.coronawarnapp.util.di
+
+// Annotating an Activity, Fragment marks it for injection via **[AppInjector]**
+interface AutoInject
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6ba82485f07bf8de8b8f829e99e450e487dcfb7d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt
@@ -0,0 +1,14 @@
+package de.rki.coronawarnapp.util.ui
+
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+
+fun <T> LiveData<T>.observe2(fragment: Fragment, callback: (T) -> Unit) {
+    observe(fragment.viewLifecycleOwner, Observer { callback.invoke(it) })
+}
+
+fun <T> LiveData<T>.observe2(activity: AppCompatActivity, callback: (T) -> Unit) {
+    observe(activity, Observer { callback.invoke(it) })
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SingleLiveEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SingleLiveEvent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..44809a1831ffe97fb8615ace28f1d692c173a421
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SingleLiveEvent.kt
@@ -0,0 +1,71 @@
+package de.rki.coronawarnapp.util.ui
+
+/*
+ *  Copyright 2017 Google Inc.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+import androidx.annotation.MainThread
+import androidx.annotation.Nullable
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import timber.log.Timber
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * A lifecycle-aware observable that sends only new updates after subscription, used for events like
+ * navigation and Snackbar messages.
+ *
+ *
+ * This avoids a common problem with events: on configuration change (like rotation) an update
+ * can be emitted if the observer is active. This LiveData only calls the observable if there's an
+ * explicit call to setValue() or call().
+ *
+ *
+ * Note that only one observer is going to be notified of changes.
+ * https://github.com/android/architecture-samples/blob/166ca3a93ad14c6e224a3ea9bfcbd773eb048fb0/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java
+ */
+class SingleLiveEvent<T> : MutableLiveData<T>() {
+
+    private val pending = AtomicBoolean(false)
+
+    @MainThread
+    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
+        if (hasActiveObservers()) {
+            Timber.w("Multiple observers registered but only one will be notified of changes.")
+        }
+
+        // Observe the internal MutableLiveData
+        super.observe(owner, Observer<T> { t ->
+            if (pending.compareAndSet(true, false)) {
+                observer.onChanged(t)
+            }
+        })
+    }
+
+    @MainThread
+    override fun setValue(@Nullable t: T?) {
+        pending.set(true)
+        super.setValue(t)
+    }
+
+    /**
+     * Used for cases where T is Void, to make calls cleaner.
+     */
+    @MainThread
+    fun call() {
+        value = null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewBindingExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewBindingExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0459511819f2e3bc7fc295e7c00ba8f1ac9230d0
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewBindingExtensions.kt
@@ -0,0 +1,62 @@
+package de.rki.coronawarnapp.util.ui
+
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.annotation.MainThread
+import androidx.databinding.ViewDataBinding
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.viewbinding.ViewBinding
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+inline fun <FragmentT : Fragment, reified BindingT : ViewBinding> FragmentT.viewBindingLazy() =
+    viewBindingLazy {
+        val bindingMethod = BindingT::class.java.getMethod("bind", View::class.java)
+        val binding = bindingMethod(null, requireView()) as BindingT
+        if (binding is ViewDataBinding) {
+            binding.lifecycleOwner = this
+        }
+        binding
+    }
+
+@Suppress("unused")
+fun <FragmentT : Fragment, BindingT : ViewBinding> FragmentT.viewBindingLazy(
+    bindingProvider: FragmentT.() -> BindingT
+) = ViewBindingProperty(bindingProvider)
+
+class ViewBindingProperty<ComponentT : LifecycleOwner, BindingT : ViewBinding>(
+    private val bindingProvider: (ComponentT) -> BindingT
+) : ReadOnlyProperty<ComponentT, BindingT> {
+
+    private val uiHandler = Handler(Looper.getMainLooper())
+    private var localRef: ComponentT? = null
+    private var viewBinding: BindingT? = null
+
+    private val onDestroyObserver = object : DefaultLifecycleObserver {
+        // Called right before Fragment.onDestroyView
+        override fun onDestroy(owner: LifecycleOwner) {
+            val ref = localRef ?: return
+            ref.lifecycle.removeObserver(this)
+            localRef = null
+            // Otherwise the binding is null before Fragment.onDestroyView
+            uiHandler.post { viewBinding = null }
+        }
+    }
+
+    @MainThread
+    override fun getValue(thisRef: ComponentT, property: KProperty<*>): BindingT {
+        viewBinding?.let {
+            // Only accessible from within the same component
+            require(localRef === thisRef)
+            return@getValue it
+        }
+
+        localRef = thisRef
+        thisRef.lifecycle.addObserver(onDestroyObserver)
+
+        return bindingProvider(thisRef).also { viewBinding = it }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..065a6bc2bf629d194c66dbb9976fd70776ba10b7
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.util.viewmodel
+
+import androidx.annotation.CallSuper
+import androidx.lifecycle.ViewModel
+import timber.log.Timber
+
+abstract class CWAViewModel : ViewModel() {
+
+    init {
+        Timber.v("Initialized")
+    }
+
+    @CallSuper
+    override fun onCleared() {
+        Timber.v("onCleared()")
+        super.onCleared()
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4ff5fa783d48b544b7b8974ca766e113e78c4495
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelExtensions.kt
@@ -0,0 +1,58 @@
+package de.rki.coronawarnapp.util.viewmodel
+
+import androidx.activity.ComponentActivity
+import androidx.annotation.MainThread
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.SavedStateHandle
+
+@MainThread
+inline fun <reified VM : CWAViewModel> Fragment.cwaViewModels(
+    noinline factoryProducer: (() -> CWAViewModelFactoryProvider.Factory)
+) = this.cwaViewModels<VM>(null, factoryProducer)
+
+@MainThread
+inline fun <reified VM : CWAViewModel> Fragment.cwaViewModels(
+    noinline keyProducer: (() -> String)? = null,
+    noinline factoryProducer: (() -> CWAViewModelFactoryProvider.Factory)
+) = viewModelsKeyed<VM>(keyProducer) { factoryProducer.invoke().create(this, arguments) }
+
+@MainThread
+inline fun <reified VM : CWAViewModel> Fragment.cwaViewModelsAssisted(
+    noinline factoryProducer: (() -> CWAViewModelFactoryProvider.Factory),
+    noinline constructorCall: ((CWAViewModelFactory<out CWAViewModel>, SavedStateHandle) -> CWAViewModel)
+) = this.cwaViewModelsAssisted<VM>(null, factoryProducer, constructorCall)
+
+@MainThread
+inline fun <reified VM : CWAViewModel> Fragment.cwaViewModelsAssisted(
+    noinline keyProducer: (() -> String)? = null,
+    noinline factoryProducer: (() -> CWAViewModelFactoryProvider.Factory),
+    noinline constructorCall: ((CWAViewModelFactory<out CWAViewModel>, SavedStateHandle) -> CWAViewModel)
+) = viewModelsKeyed<VM>(keyProducer) {
+    factoryProducer.invoke().create(this, arguments, constructorCall)
+}
+
+@MainThread
+inline fun <reified VM : CWAViewModel> ComponentActivity.cwaViewModels(
+    noinline factoryProducer: (() -> CWAViewModelFactoryProvider.Factory)
+) = this.cwaViewModels<VM>(null, factoryProducer)
+
+@MainThread
+inline fun <reified VM : CWAViewModel> ComponentActivity.cwaViewModels(
+    noinline keyProducer: (() -> String)? = null,
+    noinline factoryProducer: (() -> CWAViewModelFactoryProvider.Factory)
+) = viewModelsKeyed<VM>(keyProducer) { factoryProducer.invoke().create(this, intent.extras) }
+
+@MainThread
+inline fun <reified VM : CWAViewModel> ComponentActivity.cwaViewModelsAssisted(
+    noinline factoryProducer: (() -> CWAViewModelFactoryProvider.Factory),
+    noinline constructorCall: ((CWAViewModelFactory<out CWAViewModel>, SavedStateHandle) -> CWAViewModel)
+) = this.cwaViewModelsAssisted<VM>(null, factoryProducer, constructorCall)
+
+@MainThread
+inline fun <reified VM : CWAViewModel> ComponentActivity.cwaViewModelsAssisted(
+    noinline keyProducer: (() -> String)? = null,
+    noinline factoryProducer: (() -> CWAViewModelFactoryProvider.Factory),
+    noinline constructorCall: ((CWAViewModelFactory<out CWAViewModel>, SavedStateHandle) -> CWAViewModel)
+) = viewModelsKeyed<VM>(keyProducer) {
+    factoryProducer.invoke().create(this, intent.extras, constructorCall)
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bc2241b0f0f24e060a6b5dbed45255c566cf345f
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelFactory.kt
@@ -0,0 +1,3 @@
+package de.rki.coronawarnapp.util.viewmodel
+
+interface CWAViewModelFactory<T : CWAViewModel>
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelFactoryProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelFactoryProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..836fe2688145ef18cd97a9a24894d686e1ce0a86
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelFactoryProvider.kt
@@ -0,0 +1,80 @@
+package de.rki.coronawarnapp.util.viewmodel
+
+import android.os.Bundle
+import androidx.lifecycle.AbstractSavedStateViewModelFactory
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.savedstate.SavedStateRegistryOwner
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+
+/**
+ * Using fragments as example:
+ *
+ * Step 1: Inject CWAViewModelSource.Factory into Fragment
+ *
+ * Step 2: Use assisted inject `CWAViewModelSource.Factory.create(...)` to create an instance of
+ * CWAViewModelSource that has access to the fragments `savedStateOwner`
+ *
+ * Step 3: On first access (like "by lazy") to the viewModel the extension function,
+ * i.e. `ourViewModel.onActionClicked`,
+ * `cwaViewModelsAssisted` (or one of it's variants) checks the fragments viewmodel store
+ *
+ * Step 4: If no viewmodel was available, this factory is tasked with creating a new viewmodel
+ *
+ * Step 5: `CWAViewModelSource.create` is called as part of that flow and returns a new viewmodel
+ *
+ * Step 6: The new viewmodel is cached and returned
+ *
+ * Step 7: Finally whatever called `ourViewModel.onActionClicked` can execute.
+ */
+class CWAViewModelFactoryProvider @AssistedInject constructor(
+    private val creators: @JvmSuppressWildcards Map<Class<out CWAViewModel>, CWAViewModelFactory<out CWAViewModel>>,
+    @Assisted savedStateOwner: SavedStateRegistryOwner,
+    @Assisted defaultSavedState: Bundle?,
+    @Assisted private val assistAction: ((CWAViewModelFactory<out CWAViewModel>, SavedStateHandle) -> CWAViewModel)?
+) : AbstractSavedStateViewModelFactory(savedStateOwner, defaultSavedState) {
+
+    /**
+     * Called indirectly (lazy) when the viewModel is accessed in a Fragment/Activity
+     **/
+    override fun <T : ViewModel?> create(
+        key: String,
+        modelClass: Class<T>,
+        handle: SavedStateHandle
+    ): T {
+        // Find the right factory for our ViewModel
+        val factory = creators.entries.find { modelClass.isAssignableFrom(it.key) }?.value
+        if (factory == null) throw IllegalStateException("Unknown ViewModel factory: $modelClass")
+
+        // If an `assistAction` was passed from `cwaViewModels` use that to create the ViewModel
+        @Suppress("UNCHECKED_CAST")
+        var vm: T? = assistAction?.invoke(factory, handle) as? T
+
+        // If no `assistAction` was passed, try one of the defaults
+        // The Fragment or Activity may have used one of them to reduce boilerplate code.
+        @Suppress("UNCHECKED_CAST")
+        if (vm == null) {
+            vm = when (factory) {
+                is SavedStateCWAViewModelFactory<*> -> factory.create(handle) as? T
+                is SimpleCWAViewModelFactory<*> -> factory.create() as? T
+                else -> throw IllegalStateException("Unknown factory: $factory")
+            }
+        }
+
+        return vm ?: throw IllegalStateException("$factory didn't return a ViewModel")
+    }
+
+    /**
+     * Injected into fragments/activities
+     * Uses assisted injection to get access to the fragments/activities SavedStateRegistryOwner
+     */
+    @AssistedInject.Factory
+    interface Factory {
+        fun create(
+            savedStateOwner: SavedStateRegistryOwner,
+            defaultSavedState: Bundle?,
+            assistAction: ((CWAViewModelFactory<out CWAViewModel>, SavedStateHandle) -> CWAViewModel)? = null
+        ): CWAViewModelFactoryProvider
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelKey.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelKey.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f50cc6ccbcb44c789e5dd55d9f486c57603460b3
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModelKey.kt
@@ -0,0 +1,9 @@
+package de.rki.coronawarnapp.util.viewmodel
+
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+@Target(AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.RUNTIME)
+@MapKey
+internal annotation class CWAViewModelKey(val value: KClass<out CWAViewModel>)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/SavedStateCWAViewModelFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/SavedStateCWAViewModelFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..add9ff8d2412d2d54fa37b00a6ded86c6ce9372f
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/SavedStateCWAViewModelFactory.kt
@@ -0,0 +1,7 @@
+package de.rki.coronawarnapp.util.viewmodel
+
+import androidx.lifecycle.SavedStateHandle
+
+interface SavedStateCWAViewModelFactory<T : CWAViewModel> : CWAViewModelFactory<T> {
+    fun create(handle: SavedStateHandle): T
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/SimpleCWAViewModelFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/SimpleCWAViewModelFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2998e5edd04a08fb06946934e3e12e3992e428ea
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/SimpleCWAViewModelFactory.kt
@@ -0,0 +1,5 @@
+package de.rki.coronawarnapp.util.viewmodel
+
+interface SimpleCWAViewModelFactory<T : CWAViewModel> : CWAViewModelFactory<T> {
+    fun create(): T
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/ViewModelLazyKeyed.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/ViewModelLazyKeyed.kt
new file mode 100644
index 0000000000000000000000000000000000000000..11ea914bd437aa39d411728545bdc2e9000e825b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/ViewModelLazyKeyed.kt
@@ -0,0 +1,184 @@
+package de.rki.coronawarnapp.util.viewmodel
+
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.activity.ComponentActivity
+import androidx.annotation.MainThread
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelLazy
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.ViewModelStoreOwner
+import kotlin.reflect.KClass
+
+/**
+ * This is a fork of the standard ViewModel code that allows to optionally provide
+ * alternative keys for ViewModels, to allow for more freedom when using multiple viewModels.
+ * Note the `keyProducer` argument.
+ */
+
+/**
+ * An implementation of [Lazy] used by [androidx.fragment.app.Fragment.viewModels] and
+ * [androidx.activity.ComponentActivity.viewmodels].
+ *
+ * [storeProducer] is a lambda that will be called during initialization, [VM] will be created
+ * in the scope of returned [ViewModelStore].
+ *
+ * [factoryProducer] is a lambda that will be called during initialization,
+ * returned [ViewModelProvider.Factory] will be used for creation of [VM]
+ */
+class ViewModelLazyKeyed<VM : ViewModel>(
+    private val viewModelClass: KClass<VM>,
+    private val keyProducer: (() -> String)? = null,
+    private val storeProducer: () -> ViewModelStore,
+    private val factoryProducer: () -> ViewModelProvider.Factory
+) : Lazy<VM> {
+    private var cached: VM? = null
+
+    override val value: VM
+        get() {
+            val viewModel = cached
+            return if (viewModel == null) {
+                val factory = factoryProducer()
+                val store = storeProducer()
+                val key = keyProducer?.invoke() ?: "androidx.lifecycle.ViewModelProvider.DefaultKey"
+                ViewModelProvider(store, factory).get(
+                    key + ":" + viewModelClass.java.canonicalName,
+                    viewModelClass.java
+                ).also {
+                    cached = it
+                }
+            } else {
+                viewModel
+            }
+        }
+
+    override fun isInitialized() = cached != null
+}
+
+/**
+ * Returns a property delegate to access [ViewModel] by **default** scoped to this [Fragment]:
+ * ```
+ * class MyFragment : Fragment() {
+ *     val viewmodel: NYViewModel by viewmodels()
+ * }
+ * ```
+ *
+ * Custom [ViewModelProvider.Factory] can be defined via [factoryProducer] parameter,
+ * factory returned by it will be used to create [ViewModel]:
+ * ```
+ * class MyFragment : Fragment() {
+ *     val viewmodel: MYViewModel by viewmodels { myFactory }
+ * }
+ * ```
+ *
+ * Default scope may be overridden with parameter [ownerProducer]:
+ * ```
+ * class MyFragment : Fragment() {
+ *     val viewmodel: MYViewModel by viewmodels ({requireParentFragment()})
+ * }
+ * ```
+ *
+ * This property can be accessed only after this Fragment is attached i.e., after
+ * [Fragment.onAttach()], and access prior to that will result in IllegalArgumentException.
+ */
+@MainThread
+inline fun <reified VM : ViewModel> Fragment.viewModelsKeyed(
+    noinline keyProducer: (() -> String)? = null,
+    noinline ownerProducer: () -> ViewModelStoreOwner = { this },
+    noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
+) = createViewModelLazyKeyed(
+    VM::class,
+    keyProducer,
+    { ownerProducer().viewModelStore },
+    factoryProducer
+)
+
+/**
+ * Returns a property delegate to access parent activity's [ViewModel],
+ * if [factoryProducer] is specified then [ViewModelProvider.Factory]
+ * returned by it will be used to create [ViewModel] first time.
+ *
+ * ```
+ * class MyFragment : Fragment() {
+ *     val viewmodel: MyViewModel by activityViewModels()
+ * }
+ * ```
+ *
+ * This property can be accessed only after this Fragment is attached i.e., after
+ * [Fragment.onAttach()], and access prior to that will result in IllegalArgumentException.
+ */
+@MainThread
+inline fun <reified VM : ViewModel> Fragment.activityViewModelsKeyed(
+    noinline keyProducer: (() -> String)? = null,
+    noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
+) = createViewModelLazyKeyed(
+    VM::class,
+    keyProducer,
+    { requireActivity().viewModelStore },
+    factoryProducer
+)
+
+/**
+ * Helper method for creation of [ViewModelLazy], that resolves `null` passed as [factoryProducer]
+ * to default factory.
+ */
+@MainThread
+fun <VM : ViewModel> Fragment.createViewModelLazyKeyed(
+    viewModelClass: KClass<VM>,
+    keyProducer: (() -> String)? = null,
+    storeProducer: () -> ViewModelStore,
+    factoryProducer: (() -> ViewModelProvider.Factory)? = null
+): Lazy<VM> {
+    val factoryPromise = factoryProducer ?: {
+        val application = activity?.application ?: throw IllegalStateException(
+            "ViewModel can be accessed only when Fragment is attached"
+        )
+        ViewModelProvider.AndroidViewModelFactory.getInstance(application)
+    }
+    return ViewModelLazyKeyed(viewModelClass, keyProducer, storeProducer, factoryPromise)
+}
+
+/**
+ * Returns a [Lazy] delegate to access the ComponentActivity's ViewModel, if [factoryProducer]
+ * is specified then [ViewModelProvider.Factory] returned by it will be used
+ * to create [ViewModel] first time.
+ *
+ * ```
+ * class MyComponentActivity : ComponentActivity() {
+ *     val viewmodel: MyViewModel by viewmodels()
+ * }
+ * ```
+ *
+ * This property can be accessed only after the Activity is attached to the Application,
+ * and access prior to that will result in IllegalArgumentException.
+ */
+@MainThread
+inline fun <reified VM : ViewModel> ComponentActivity.viewModelsKeyed(
+    noinline keyProducer: (() -> String)? = null,
+    noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
+): Lazy<VM> {
+    val factoryPromise = factoryProducer ?: {
+        val application = application ?: throw IllegalArgumentException(
+            "ViewModel can be accessed only when Activity is attached"
+        )
+        ViewModelProvider.AndroidViewModelFactory.getInstance(application)
+    }
+
+    return ViewModelLazyKeyed(VM::class, keyProducer, { viewModelStore }, factoryPromise)
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt
index d9df9734b50b60a323366f41c3cb9de7ef39ea2c..4557883674743c15d6c6545486aefce57a5d90b4 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt
@@ -62,6 +62,8 @@ class KeyFileDownloaderTest : BaseIOTest() {
         testDir.exists() shouldBe true
 
         mockkObject(CWADebug)
+        every { CWADebug.isDebugBuildOrMode } returns false
+        every { settings.isLast3HourModeEnabled } returns false
 
         coEvery { diagnosisKeyServer.getCountryIndex() } returns listOf(
             LocationCode("DE"),
@@ -71,9 +73,6 @@ class KeyFileDownloaderTest : BaseIOTest() {
             every { isSpaceAvailable } returns true
         }
 
-        coEvery { settings.isLast3HourModeEnabled } returns false
-
-
         coEvery { diagnosisKeyServer.getCountryIndex() } returns listOf(
             LocationCode("DE"), LocationCode("NL")
         )
diff --git a/Corona-Warn-App/src/test/java/testhelpers/extensions/CoroutineTestExtension.kt b/Corona-Warn-App/src/test/java/testhelpers/extensions/CoroutineTestExtension.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b2180a1c80b9363e296fbaefb538d1aca762a008
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/testhelpers/extensions/CoroutineTestExtension.kt
@@ -0,0 +1,26 @@
+package testhelpers.extensions
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.jupiter.api.extension.AfterEachCallback
+import org.junit.jupiter.api.extension.BeforeEachCallback
+import org.junit.jupiter.api.extension.ExtensionContext
+
+@ExperimentalCoroutinesApi
+class CoroutinesTestExtension(
+    private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
+) : BeforeEachCallback, AfterEachCallback, TestCoroutineScope by TestCoroutineScope(dispatcher) {
+
+    override fun beforeEach(context: ExtensionContext?) {
+        Dispatchers.setMain(dispatcher)
+    }
+
+    override fun afterEach(context: ExtensionContext?) {
+        cleanupTestCoroutines()
+        Dispatchers.resetMain()
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/testhelpers/extensions/InstantExecutorExtension.kt b/Corona-Warn-App/src/test/java/testhelpers/extensions/InstantExecutorExtension.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5f9ab64108ff01f43cb7363786a094454ac1f02f
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/testhelpers/extensions/InstantExecutorExtension.kt
@@ -0,0 +1,25 @@
+package testhelpers.extensions
+
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import org.junit.jupiter.api.extension.AfterEachCallback
+import org.junit.jupiter.api.extension.BeforeEachCallback
+import org.junit.jupiter.api.extension.ExtensionContext
+
+class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
+
+    override fun beforeEach(context: ExtensionContext?) {
+        ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
+            override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
+
+            override fun postToMainThread(runnable: Runnable) = runnable.run()
+
+            override fun isMainThread(): Boolean = true
+        })
+    }
+
+    override fun afterEach(context: ExtensionContext?) {
+        ArchTaskExecutor.getInstance().setDelegate(null)
+    }
+
+}
diff --git a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5f04e64c371d04d92ade5ef94b9dbcab8ca59c4a
--- /dev/null
+++ b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt
@@ -0,0 +1,103 @@
+package de.rki.coronawarnapp.test.risklevel.ui
+
+import android.content.Context
+import androidx.lifecycle.SavedStateHandle
+import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient
+import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository
+import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction
+import de.rki.coronawarnapp.transaction.RiskLevelTransaction
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.clearAllMocks
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.coVerifyOrder
+import io.mockk.impl.annotations.MockK
+import io.mockk.mockkObject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import testhelpers.BaseTest
+import testhelpers.extensions.CoroutinesTestExtension
+import testhelpers.extensions.InstantExecutorExtension
+
+@ExperimentalCoroutinesApi
+@ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class)
+class TestRiskLevelCalculationFragmentCWAViewModelTest : BaseTest() {
+
+    @MockK lateinit var context: Context
+    @MockK lateinit var savedStateHandle: SavedStateHandle
+    @MockK lateinit var exposureNotificationClient: ExposureNotificationClient
+    @MockK lateinit var keyCacheRepository: KeyCacheRepository
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        mockkObject(RetrieveDiagnosisKeysTransaction)
+        coEvery { RetrieveDiagnosisKeysTransaction.start() } returns Unit
+        mockkObject(RiskLevelTransaction)
+        coEvery { RiskLevelTransaction.start() } returns Unit
+
+        coEvery { keyCacheRepository.clear() } returns Unit
+    }
+
+    @AfterEach
+    fun teardown() {
+        clearAllMocks()
+    }
+
+    private fun createViewModel(exampleArgs: String? = null): TestRiskLevelCalculationFragmentCWAViewModel =
+        TestRiskLevelCalculationFragmentCWAViewModel(
+            handle = savedStateHandle,
+            exampleArg = exampleArgs,
+            context = context,
+            exposureNotificationClient = exposureNotificationClient,
+            keyCacheRepository = keyCacheRepository
+        )
+
+    @Test
+    fun `action retrieveDiagnosisKeys, retieves diagnosis keys and calls risklevel calculation`() {
+        val vm = createViewModel()
+
+        vm.retrieveDiagnosisKeys()
+
+        coVerifyOrder {
+            RetrieveDiagnosisKeysTransaction.start()
+            RiskLevelTransaction.start()
+        }
+    }
+
+    @Test
+    fun `action calculateRiskLevel, calls risklevel calculation`() {
+        val vm = createViewModel()
+
+        vm.calculateRiskLevel()
+
+        coVerify(exactly = 1) { RiskLevelTransaction.start() }
+        coVerify(exactly = 0) { RetrieveDiagnosisKeysTransaction.start() }
+    }
+
+    @Test
+    fun `action clearDiagnosisKeys calls the keyCacheRepo`() {
+        val vm = createViewModel()
+
+        vm.clearKeyCache()
+
+        coVerify(exactly = 1) { keyCacheRepository.clear() }
+    }
+
+    @Test
+    fun `action scanLocalQRCodeAndProvide, triggers event`() {
+        val vm = createViewModel()
+
+        vm.startLocalQRCodeScanEvent.value shouldBe null
+
+        vm.scanLocalQRCodeAndProvide()
+
+        vm.startLocalQRCodeScanEvent.value shouldBe Unit
+    }
+
+}
diff --git a/build.gradle b/build.gradle
index 9204a9f1ce135bb448852f56d08cf2eec84a33c5..3758485c638c3420912fe1f8c8a0afefebdc3773 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
-    ext.kotlin_version = '1.3.72'
+    ext.kotlin_version = '1.4.0'
     ext.protobufVersion = '0.8.12'
     ext.navVersion = "2.2.2"