diff --git a/app/src/main/java/ru/touchin/template/base/fragment/BaseFragment.kt b/app/src/main/java/ru/touchin/template/base/fragment/BaseFragment.kt index 71d59d2..39384c7 100644 --- a/app/src/main/java/ru/touchin/template/base/fragment/BaseFragment.kt +++ b/app/src/main/java/ru/touchin/template/base/fragment/BaseFragment.kt @@ -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 : Fragment(), OnBackPressedListener { +abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId), OnBackPressedListener { protected val viewModel: T by lazy { createViewModelLazy().value } @@ -18,6 +18,6 @@ abstract class BaseFragment : Fragment(), OnBackPressedListener { protected fun getSharedComponent(): SharedComponent = getSharedModule() override fun onBackPressed(): Boolean { - return (viewModel as BaseController).onBackClicked() + return viewModel.onBackClicked() } } \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/base/viewmodel/BaseViewModel.kt b/app/src/main/java/ru/touchin/template/base/viewmodel/BaseViewModel.kt new file mode 100644 index 0000000..cb7386f --- /dev/null +++ b/app/src/main/java/ru/touchin/template/base/viewmodel/BaseViewModel.kt @@ -0,0 +1,5 @@ +package ru.touchin.template.base.viewmodel + +import androidx.lifecycle.ViewModel + +abstract class BaseViewModel : ViewModel(), BaseController \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/auth/AuthModule.kt b/app/src/main/java/ru/touchin/template/di/auth/AuthModule.kt index be8abd5..e2e325a 100644 --- a/app/src/main/java/ru/touchin/template/di/auth/AuthModule.kt +++ b/app/src/main/java/ru/touchin/template/di/auth/AuthModule.kt @@ -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() +// } } \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/NavigationModule.kt b/app/src/main/java/ru/touchin/template/di/modules/NavigationModule.kt index 7c584b3..4838b23 100644 --- a/app/src/main/java/ru/touchin/template/di/modules/NavigationModule.kt +++ b/app/src/main/java/ru/touchin/template/di/modules/NavigationModule.kt @@ -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 = Cicerone.create() @Provides - @Singleton + @AppScope fun provideRouter(): Router { return cicerone.router } @Provides - @Singleton + @AppScope fun provideNavigatorHolder(): NavigatorHolder { return cicerone.getNavigatorHolder() } diff --git a/app/src/main/java/ru/touchin/template/feature/first/FirstFragment.kt b/app/src/main/java/ru/touchin/template/feature/first/FirstFragment.kt index ddcd6c8..128df58 100644 --- a/app/src/main/java/ru/touchin/template/feature/first/FirstFragment.kt +++ b/app/src/main/java/ru/touchin/template/feature/first/FirstFragment.kt @@ -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() { +class FirstFragment : BaseFragment(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 { 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() { textView.text = "First Fragment" } } - - override fun onDestroyView() { - super.onDestroyView() - - _binding = null - } } \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/feature/first/FirstViewModel.kt b/app/src/main/java/ru/touchin/template/feature/first/FirstViewModel.kt index 0b13b41..02941c6 100644 --- a/app/src/main/java/ru/touchin/template/feature/first/FirstViewModel.kt +++ b/app/src/main/java/ru/touchin/template/feature/first/FirstViewModel.kt @@ -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)) } + } \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/feature/second/SecondFragment.kt b/app/src/main/java/ru/touchin/template/feature/second/SecondFragment.kt index c4da57f..7e17c92 100644 --- a/app/src/main/java/ru/touchin/template/feature/second/SecondFragment.kt +++ b/app/src/main/java/ru/touchin/template/feature/second/SecondFragment.kt @@ -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() { +class SecondFragment : BaseFragment(R.layout.fragment_second) { companion object { private const val FROM_KEY = "FROM" @@ -31,8 +31,7 @@ class SecondFragment : BaseFragment() { } } - 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() { ) } - 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() { } } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } } \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/feature/second/SecondViewModel.kt b/app/src/main/java/ru/touchin/template/feature/second/SecondViewModel.kt index d07cc14..cc9fd9c 100644 --- a/app/src/main/java/ru/touchin/template/feature/second/SecondViewModel.kt +++ b/app/src/main/java/ru/touchin/template/feature/second/SecondViewModel.kt @@ -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 = _state.asStateFlow() diff --git a/app/src/main/java/ru/touchin/template/utils/binding/FragmentViewBindingDelegate.kt b/app/src/main/java/ru/touchin/template/utils/binding/FragmentViewBindingDelegate.kt new file mode 100644 index 0000000..f5e15b1 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/utils/binding/FragmentViewBindingDelegate.kt @@ -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 = (View) -> T + +class FragmentViewBindingDelegate( + val fragment: Fragment, + val viewBindingFactory: ViewBindingFactory, +) : ReadOnlyProperty { + 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 Fragment.viewBinding(viewBindingFactory: ViewBindingFactory) = + FragmentViewBindingDelegate(this, viewBindingFactory) \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9de21c1..305c3c3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -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">