feature TI-193: [Android] Реализоваь viewModel factory #4

Merged
evgeny.dubravin merged 11 commits from feature/TI-193 into release/mvvm 2024-05-20 22:06:23 +03:00
42 changed files with 698 additions and 79 deletions

View File

@ -87,6 +87,9 @@ dependencies {
// Groupie
implementation(libs.groupie)
implementation(libs.groupie.viewbinding)
// Cicecrone
implementation(libs.cicerone)
}
apply(from = "${rootProject.ext["buildScriptsDir"]}/gradle/scripts/stringGenerator.gradle")

View File

@ -11,12 +11,12 @@
android:label="@string/common_app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false"
tools:ignore="GoogleAppIndexingWarning">
tools:ignore="GoogleAppIndexingWarning"
android:theme="@style/AppTheme">
<activity
android:name="ru.touchin.template.SingleActivity"
android:name=".feature.SingleActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>

View File

@ -1,5 +1,18 @@
package ru.touchin.template
import android.app.Application
import ru.touchin.template.di.DI
import ru.touchin.template.di.SharedComponent
import ru.touchin.template.di.SharedComponentProvider
class App : Application()
class App : Application(), SharedComponentProvider {
override fun onCreate() {
super.onCreate()
DI.init(applicationContext)
DI.getComponent().inject(this)
}
override fun getModule(): SharedComponent = DI.getComponent()
}

View File

@ -1,5 +0,0 @@
package ru.touchin.template
import androidx.appcompat.app.AppCompatActivity
class SingleActivity : AppCompatActivity()

View File

@ -0,0 +1,17 @@
package ru.touchin.template.base.activity
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import ru.touchin.template.di.SharedComponent
import ru.touchin.template.di.getSharedModule
abstract class BaseActivity<T : ViewModel> : AppCompatActivity() {
protected val viewModel: T by lazy { createViewModelLazy().value }
protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() }
abstract fun createViewModelLazy(): Lazy<T>
protected fun getSharedComponent(): SharedComponent = getSharedModule()
}

View File

@ -0,0 +1,23 @@
package ru.touchin.template.base.fragment
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import ru.touchin.template.base.viewmodel.BaseViewModel
import ru.touchin.template.di.SharedComponent
import ru.touchin.template.di.getSharedModule
import ru.touchin.template.navigation.backpress.OnBackPressedListener
abstract class BaseFragment<T : BaseViewModel>(@LayoutRes layoutId: Int) : Fragment(layoutId), OnBackPressedListener {
protected val viewModel: T by lazy { createViewModelLazy().value }
protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() }
protected abstract fun createViewModelLazy(): Lazy<T>
protected fun getSharedComponent(): SharedComponent = getSharedModule()
override fun onBackPressed(): Boolean {
return viewModel.onBackClicked()
}
}

View File

@ -0,0 +1,6 @@
package ru.touchin.template.base.viewmodel
interface BaseController {
fun onBackClicked(): Boolean = true
}

View File

@ -0,0 +1,5 @@
package ru.touchin.template.base.viewmodel
import androidx.lifecycle.ViewModel
abstract class BaseViewModel : ViewModel(), BaseController

View File

@ -0,0 +1,37 @@
package ru.touchin.template.di
import android.content.Context
import dagger.BindsInstance
import dagger.Component
import ru.touchin.template.App
import ru.touchin.template.di.auth.AuthComponent
import ru.touchin.template.di.modules.AppModule
import ru.touchin.template.di.modules.NavigationModule
import ru.touchin.template.di.modules.ViewModelModule
import ru.touchin.template.feature.SingleActivity
@Component(
modules = [
AppModule::class,
ViewModelModule::class,
NavigationModule::class
]
)
@AppScope
interface AppComponent : SharedComponent {
fun authComponent(): AuthComponent.Builder
@Component.Builder
interface Builder {
@BindsInstance
fun appContext(appContext: Context): Builder
fun build(): AppComponent
}
fun inject(entry: App)
fun inject(entry: SingleActivity)
}

View File

@ -0,0 +1,6 @@
package ru.touchin.template.di
import javax.inject.Scope
@Scope
annotation class AppScope

View File

@ -0,0 +1,16 @@
package ru.touchin.template.di
import android.content.Context
object DI {
private lateinit var appComponent: AppComponent
fun init(context: Context) {
appComponent = DaggerAppComponent.builder()
.appContext(context)
.build()
}
fun getComponent(): AppComponent = appComponent
}

View File

@ -0,0 +1,17 @@
package ru.touchin.template.di
import android.app.Activity
import android.content.Context
import androidx.fragment.app.Fragment
fun Context.getSharedModule(): SharedComponent {
return (applicationContext as SharedComponentProvider).getModule()
}
fun Activity.getSharedModule(): SharedComponent {
return (applicationContext as SharedComponentProvider).getModule()
}
fun Fragment.getSharedModule(): SharedComponent {
return requireContext().getSharedModule()
}

View File

@ -0,0 +1,17 @@
package ru.touchin.template.di
import dagger.Module
import ru.touchin.template.di.viewmodel.ViewModelFactory
import ru.touchin.template.feature.second.SecondViewModel
interface SharedComponent {
fun viewModelFactory(): ViewModelFactory
fun secondScreenViewModelFactory(): SecondViewModel.Factory
}
@Module
class SharedModule
interface SharedComponentProvider {
fun getModule(): SharedComponent
}

View File

@ -0,0 +1,17 @@
package ru.touchin.template.di.auth
import dagger.Subcomponent
import ru.touchin.template.feature.first.FirstFragment
@Subcomponent(modules = [AuthModule::class])
@AuthScope
interface AuthComponent {
@Subcomponent.Builder
interface Builder {
fun build(): AuthComponent
}
fun inject(entry: FirstFragment)
}

View File

@ -0,0 +1,13 @@
package ru.touchin.template.di.auth
import dagger.Module
@Module
class AuthModule {
// @Provides
// @AuthScope
// internal fun providesSecondRepository(): SecondRepository {
// return SecondRepositoryImpl()
// }
}

View File

@ -0,0 +1,6 @@
package ru.touchin.template.di.auth
import javax.inject.Scope
@Scope
annotation class AuthScope

View File

@ -0,0 +1,13 @@
package ru.touchin.template.di.modules
import dagger.Module
import ru.touchin.template.di.auth.AuthComponent
@Module(
includes = [
RepositoryModule::class,
NetworkModule::class
],
subcomponents = [AuthComponent::class]
)
class AppModule

View File

@ -0,0 +1,26 @@
package ru.touchin.template.di.modules
import com.github.terrakok.cicerone.Cicerone
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Router
import dagger.Module
import dagger.Provides
import ru.touchin.template.di.AppScope
@Module
class NavigationModule {
private val cicerone: Cicerone<Router> = Cicerone.create()
@Provides
@AppScope
fun provideRouter(): Router {
return cicerone.router
}
@Provides
@AppScope
fun provideNavigatorHolder(): NavigatorHolder {
return cicerone.getNavigatorHolder()
}
}

View File

@ -0,0 +1,7 @@
package ru.touchin.template.di.modules
import dagger.Module
@Module
class NetworkModule {
}

View File

@ -0,0 +1,7 @@
package ru.touchin.template.di.modules
import dagger.Module
@Module
class RepositoryModule {
}

View File

@ -0,0 +1,29 @@
package ru.touchin.template.di.modules
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import ru.touchin.template.di.viewmodel.ViewModelFactory
import ru.touchin.template.di.viewmodel.ViewModelKey
import ru.touchin.template.feature.SingleViewModel
import ru.touchin.template.feature.first.FirstViewModel
@Module
interface ViewModelModule {
@Binds
fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
@Binds
@IntoMap
@ViewModelKey(SingleViewModel::class)
fun bindsSingleViewModel(viewModel: SingleViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(FirstViewModel::class)
fun bindsFirstViewModel(viewModel: FirstViewModel): ViewModel
}

View File

@ -0,0 +1,44 @@
package ru.touchin.template.di.viewmodel
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.AbstractSavedStateViewModelFactory
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.savedstate.SavedStateRegistryOwner
import javax.inject.Inject
import javax.inject.Provider
class ViewModelFactory @Inject constructor(
private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val viewModel = viewModels[modelClass]?.get()
return viewModel as T
}
}
inline fun <reified VM : ViewModel> Fragment.assistedViewModel(
crossinline creator: (SavedStateHandle) -> VM,
): Lazy<VM> = viewModels { createAbstractSavedStateViewModelFactory(arguments, creator) }
inline fun <reified T : ViewModel> SavedStateRegistryOwner.createAbstractSavedStateViewModelFactory(
arguments: Bundle? = Bundle(),
crossinline creator: (SavedStateHandle) -> T,
): ViewModelProvider.Factory {
return object : AbstractSavedStateViewModelFactory(
owner = this@createAbstractSavedStateViewModelFactory,
defaultArgs = arguments,
) {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle,
): T = creator(handle) as T
}
}

View File

@ -0,0 +1,9 @@
package ru.touchin.template.di.viewmodel
import androidx.lifecycle.ViewModel
import dagger.MapKey
import kotlin.reflect.KClass
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

View File

@ -0,0 +1,47 @@
package ru.touchin.template.feature
import android.os.Bundle
import androidx.activity.viewModels
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.androidx.AppNavigator
import javax.inject.Inject
import ru.touchin.template.R
import ru.touchin.template.base.activity.BaseActivity
import ru.touchin.template.databinding.ActivityMainBinding
import ru.touchin.template.di.DI
class SingleActivity : BaseActivity<SingleViewModel>() {
@Inject
lateinit var navigatorHolder: NavigatorHolder
private val rootNavigator by lazy {
AppNavigator(this, R.id.activityScreensContainer)
}
private lateinit var binding: ActivityMainBinding
override fun createViewModelLazy(): Lazy<SingleViewModel> = viewModels { viewModelFactory }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
DI.getComponent().inject(this)
viewModel.navigate()
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(rootNavigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
}

View File

@ -0,0 +1,14 @@
package ru.touchin.template.feature
import androidx.lifecycle.ViewModel
import com.github.terrakok.cicerone.Router
import javax.inject.Inject
import ru.touchin.template.navigation.Screens
class SingleViewModel @Inject constructor(
private val router: Router,
) : ViewModel() {
fun navigate() {
router.newRootScreen(Screens.First())
}
}

View File

@ -0,0 +1,29 @@
package ru.touchin.template.feature.first
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import ru.touchin.template.R
import ru.touchin.template.base.fragment.BaseFragment
import ru.touchin.template.databinding.FragmentFirstBinding
import ru.touchin.template.utils.binding.viewBinding
class FirstFragment : BaseFragment<FirstViewModel>(R.layout.fragment_first) {
private val binding by viewBinding { FragmentFirstBinding.bind(it) }
override fun createViewModelLazy() = viewModels<FirstViewModel> { viewModelFactory }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding) {
button.apply {
text = "Next"
setOnClickListener { viewModel.onNextButtonClicked(this@FirstFragment::class.toString()) }
}
textView.text = "First Fragment"
}
}
}

View File

@ -0,0 +1,16 @@
package ru.touchin.template.feature.first
import com.github.terrakok.cicerone.Router
import javax.inject.Inject
import ru.touchin.template.base.viewmodel.BaseViewModel
import ru.touchin.template.navigation.Screens
class FirstViewModel @Inject constructor(
private val router: Router
) : BaseViewModel() {
fun onNextButtonClicked(from: String) {
router.navigateTo(Screens.Second(from))
}
}

View File

@ -0,0 +1,64 @@
package ru.touchin.template.feature.second
import android.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import ru.touchin.template.R
import ru.touchin.template.base.fragment.BaseFragment
import ru.touchin.template.databinding.FragmentSecondBinding
import ru.touchin.template.di.viewmodel.assistedViewModel
import ru.touchin.template.utils.binding.viewBinding
class SecondFragment : BaseFragment<SecondViewModel>(R.layout.fragment_second) {
companion object {
private const val FROM_KEY = "FROM"
private const val SCREEN_NAME_KEY = "SCREEN_NAME"
private const val FRAGMENT_NAME = "Second Fragment"
fun newInstance(from: String): SecondFragment {
val args = bundleOf(FROM_KEY to from, SCREEN_NAME_KEY to FRAGMENT_NAME)
val fragment = SecondFragment().apply {
arguments = args
}
return fragment
}
}
private val binding by viewBinding { FragmentSecondBinding.bind(it) }
private val secondViewModelFactory by lazy { getSharedComponent().secondScreenViewModelFactory() }
override fun createViewModelLazy() = assistedViewModel {
secondViewModelFactory.create(
arguments?.getString(FROM_KEY) ?: "Unkown Fragment",
arguments?.getString(SCREEN_NAME_KEY) ?: "Unknown"
)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding) {
button.apply {
text = "Back"
setOnClickListener { onBackPressed() }
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.state
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect {
textView.text = it
}
}
}
}
}

View File

@ -0,0 +1,33 @@
package ru.touchin.template.feature.second
import com.github.terrakok.cicerone.Router
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import ru.touchin.template.base.viewmodel.BaseViewModel
class SecondViewModel @AssistedInject constructor(
@Assisted("from") from: String,
@Assisted("screenName") screenName: String,
private val rootRouter: Router
) : BaseViewModel() {
private val _state = MutableStateFlow("$from to $screenName")
val state: StateFlow<String> = _state.asStateFlow()
@AssistedFactory
interface Factory {
fun create(
@Assisted("from") from: String,
@Assisted("screenName") screenName: String
): SecondViewModel
}
override fun onBackClicked(): Boolean {
rootRouter.exit()
return super.onBackClicked()
}
}

View File

@ -0,0 +1,16 @@
package ru.touchin.template.navigation
import com.github.terrakok.cicerone.androidx.FragmentScreen
import ru.touchin.template.feature.first.FirstFragment
import ru.touchin.template.feature.second.SecondFragment
object Screens {
fun First(): FragmentScreen = FragmentScreen {
FirstFragment()
}
fun Second(from: String): FragmentScreen = FragmentScreen {
SecondFragment.newInstance(from)
}
}

View File

@ -0,0 +1,6 @@
package ru.touchin.template.navigation.backpress
interface OnBackPressedListener {
fun onBackPressed(): Boolean
}

View File

@ -0,0 +1,7 @@
package ru.touchin.template.navigation.router
import com.github.terrakok.cicerone.Router
interface RouterProvider {
val router: Router
}

View File

@ -0,0 +1,50 @@
package ru.touchin.template.utils.binding
import android.view.View
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
typealias ViewBindingFactory<T> = (View) -> T
class FragmentViewBindingDelegate<T : ViewBinding>(
val fragment: Fragment,
val viewBindingFactory: ViewBindingFactory<T>,
) : ReadOnlyProperty<Fragment, T> {
private var binding: T? = null
init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.observe(fragment) { lifecylelOwner ->
lifecylelOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
})
}
}
})
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
val binding = this.binding
if (binding != null) return binding
val lifecycle = fragment.viewLifecycleOwner.lifecycle
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
}
return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
}
}
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: ViewBindingFactory<T>) =
FragmentViewBindingDelegate(this, viewBindingFactory)

View File

@ -1,6 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark">
<FrameLayout
android:id="@+id/activityScreensContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="40dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,6 +2,9 @@ package plugins
import versioncatalog.androidApplicationPlugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.getByType
class AndroidAppPlugin : BaseAndroidPlugin() {

1
common-template Submodule

@ -0,0 +1 @@
Subproject commit 8f595d1a472afe437a56b8ce70ec6ac8e375c3ed

View File

@ -1,3 +1,5 @@
package ru.template.data.network
import ru.template.data.network.sslpinning.ServerInfo
import ru.template.data.network.sslpinning.UrlInfo

View File

@ -1,16 +0,0 @@
package com.redmadrobot.data.network.sslpinning
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
object TrustManagerUnsafe: X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) = Unit
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) = Unit
override fun getAcceptedIssuers() = emptyArray<X509Certificate>()
}

View File

@ -1,49 +0,0 @@
package com.redmadrobot.data.network.sslpinning
import android.annotation.SuppressLint
import com.redmadrobot.data.network.NetworkConfig
import com.redmadrobot.domain.extension.toHex
import com.redmadrobot.domain.repository.ssl.SslPublicKeyRepository
import java.security.MessageDigest
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.Locale
import javax.net.ssl.X509TrustManager
@SuppressLint("CustomX509TrustManager")
class TrustManagerWithoutTls(
private val networkConfig: NetworkConfig,
private val sslPublicKeyRepository: SslPublicKeyRepository
) : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) = Unit
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
if (networkConfig.isSslPinningEnabled()) {
chain?.let { checkCertificateFingerprint(it) }
}
}
override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
private fun checkCertificateFingerprint(chain: Array<X509Certificate?>) {
val pinFromServer = chain[0]?.let { getSha256FingerprintFormatted(it) }
networkConfig.getCurrentServer().getPins().also {
if (it.contains(pinFromServer)) {
sslPublicKeyRepository.setPublicKey(isValid = true)
return
}
}
sslPublicKeyRepository.setPublicKey(isValid = false)
throw CertificateException("Cannot validate server certificate")
}
private fun getSha256FingerprintFormatted(certificate: X509Certificate): String {
return MessageDigest
.getInstance("SHA-256")
.digest(certificate.encoded)
.toHex(separator = ":").toUpperCase(Locale.getDefault())
}
}

View File

@ -58,7 +58,7 @@ javapoet = "1.13.0"
googleServices = "4.4.1"
googleLicenses = "17.0.1"
googleLicensesPlugin = "0.10.6"
firebaseBom = "32.7.4"
firebaseBom = "32.8.1"
firebaseCrashlytics = "2.9.9"
firebasePerf = "1.4.2"