refactor ViewBinding lazy initialization

This commit is contained in:
Evgeny Dubravin 2024-04-12 17:02:07 +07:00
parent 9b85bcf562
commit 60d2b9d766
10 changed files with 81 additions and 52 deletions

View File

@ -1,13 +1,13 @@
package ru.touchin.template.base.fragment
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import ru.touchin.template.base.viewmodel.BaseController
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 : ViewModel> : Fragment(), OnBackPressedListener {
abstract class BaseFragment<T : BaseViewModel>(@LayoutRes layoutId: Int) : Fragment(layoutId), OnBackPressedListener {
protected val viewModel: T by lazy { createViewModelLazy().value }
@ -18,6 +18,6 @@ abstract class BaseFragment<T : ViewModel> : Fragment(), OnBackPressedListener {
protected fun getSharedComponent(): SharedComponent = getSharedModule()
override fun onBackPressed(): Boolean {
return (viewModel as BaseController).onBackClicked()
return viewModel.onBackClicked()
}
}

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Router
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
import ru.touchin.template.di.AppScope
@Module
class NavigationModule {
@ -13,13 +13,13 @@ class NavigationModule {
private val cicerone: Cicerone<Router> = Cicerone.create()
@Provides
@Singleton
@AppScope
fun provideRouter(): Router {
return cicerone.router
}
@Provides
@Singleton
@AppScope
fun provideNavigatorHolder(): NavigatorHolder {
return cicerone.getNavigatorHolder()
}

View File

@ -1,25 +1,19 @@
package ru.touchin.template.feature.first
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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>() {
class FirstFragment : BaseFragment<FirstViewModel>(R.layout.fragment_first) {
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
private val binding by viewBinding { FragmentFirstBinding.bind(it) }
override fun createViewModelLazy() = viewModels<FirstViewModel> { viewModelFactory }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentFirstBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -32,10 +26,4 @@ class FirstFragment : BaseFragment<FirstViewModel>() {
textView.text = "First Fragment"
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -1,15 +1,16 @@
package ru.touchin.template.feature.first
import androidx.lifecycle.ViewModel
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
) : ViewModel() {
) : BaseViewModel() {
fun onNextButtonClicked(from: String) {
router.navigateTo(Screens.Second(from))
}
}

View File

@ -1,19 +1,19 @@
package ru.touchin.template.feature.second
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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>() {
class SecondFragment : BaseFragment<SecondViewModel>(R.layout.fragment_second) {
companion object {
private const val FROM_KEY = "FROM"
@ -31,8 +31,7 @@ class SecondFragment : BaseFragment<SecondViewModel>() {
}
}
private var _binding: FragmentSecondBinding? = null
private val binding get() = _binding!!
private val binding by viewBinding { FragmentSecondBinding.bind(it) }
private val secondViewModelFactory by lazy { getSharedComponent().secondScreenViewModelFactory() }
@ -43,11 +42,6 @@ class SecondFragment : BaseFragment<SecondViewModel>() {
)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentSecondBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -67,9 +61,4 @@ class SecondFragment : BaseFragment<SecondViewModel>() {
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -1,6 +1,5 @@
package ru.touchin.template.feature.second
import androidx.lifecycle.ViewModel
import com.github.terrakok.cicerone.Router
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -8,13 +7,13 @@ 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.BaseController
import ru.touchin.template.base.viewmodel.BaseViewModel
class SecondViewModel @AssistedInject constructor(
@Assisted("from") from: String,
@Assisted("screenName") screenName: String,
private val rootRouter: Router
) : ViewModel(), BaseController {
) : BaseViewModel() {
private val _state = MutableStateFlow("$from to $screenName")
val state: StateFlow<String> = _state.asStateFlow()

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

@ -4,7 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/biometric_error_color">
android:background="@color/colorPrimaryDark">
<FrameLayout
android:id="@+id/activityScreensContainer"