feature/TI-193: [Android] Реализоваь viewModel factory #2
|
|
@ -1,8 +1,5 @@
|
|||
plugins {
|
||||
id(Plugins.ANDROID_APP_PLUGIN_WITH_DEFAULT_CONFIG)
|
||||
id(libs.plugins.android.application.get().pluginId)
|
||||
id(libs.plugins.kotlin.android.get().pluginId)
|
||||
id(libs.plugins.kotlin.kapt.get().pluginId)
|
||||
alias(libs.plugins.google.services)
|
||||
alias(libs.plugins.firebase.crashlytics)
|
||||
alias(libs.plugins.firebase.perf)
|
||||
|
|
@ -91,6 +88,9 @@ dependencies {
|
|||
// Groupie
|
||||
implementation(libs.groupie)
|
||||
implementation(libs.groupie.viewbinding)
|
||||
|
||||
// Cicecrone
|
||||
implementation(libs.cicerone)
|
||||
}
|
||||
|
||||
apply(from = "${rootProject.ext["buildScriptsDir"]}/gradle/scripts/stringGenerator.gradle")
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
package ru.touchin.template
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class SingleActivity : AppCompatActivity()
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
bogdan.terehov
commented
Исходя их описания класса Выход -- делаешь BaseViewModel и в ней имплементишь BaseController А тут тогда T : BaseViewModel И тогда не нужно будет вызывать не безопасный Исходя их описания класса
T : ViewModel, а вью модел у тебя не обязательно имплеменишь BaseController
Выход -- делаешь BaseViewModel и в ней имплементишь BaseController
А тут тогда T : BaseViewModel
И тогда не нужно будет вызывать не безопасный `as` (`as BaseController`)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.touchin.template.base.viewmodel
|
||||
|
||||
interface BaseController {
|
||||
|
||||
fun onBackClicked(): Boolean = true
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.touchin.template.base.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
abstract class BaseViewModel : ViewModel(), BaseController
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.touchin.template.di
|
||||
|
||||
import javax.inject.Scope
|
||||
|
||||
@Scope
|
||||
annotation class AppScope
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.template.di.auth
|
||||
|
||||
import dagger.Module
|
||||
|
||||
@Module
|
||||
class AuthModule {
|
||||
|
||||
// @Provides
|
||||
// @AuthScope
|
||||
// internal fun providesSecondRepository(): SecondRepository {
|
||||
// return SecondRepositoryImpl()
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.touchin.template.di.auth
|
||||
|
||||
import javax.inject.Scope
|
||||
|
||||
@Scope
|
||||
annotation class AuthScope
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.template.di.modules
|
||||
|
||||
import dagger.Module
|
||||
|
||||
@Module
|
||||
class NetworkModule {
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.template.di.modules
|
||||
|
||||
import dagger.Module
|
||||
|
||||
@Module
|
||||
class RepositoryModule {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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>)
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
bogdan.terehov
commented
почему с большой? так было в доке, понял почему с большой?
так было в доке, понял
|
||||
FirstFragment()
|
||||
}
|
||||
|
||||
fun Second(from: String): FragmentScreen = FragmentScreen {
|
||||
SecondFragment.newInstance(from)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.touchin.template.navigation.backpress
|
||||
|
||||
interface OnBackPressedListener {
|
||||
|
||||
fun onBackPressed(): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.template.navigation.router
|
||||
|
||||
import com.github.terrakok.cicerone.Router
|
||||
|
||||
interface RouterProvider {
|
||||
val router: Router
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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">
|
||||
|
bogdan.terehov
commented
такого ресурса почему то нет, хотя он поставляется из сторонних библиотек java.lang.RuntimeException: Unable to start activity ComponentInfo{ru.touchin.template.debug/ru.touchin.template.feature.SingleActivity}: android.view.InflateException: Binary XML file line #7 in ru.touchin.template.debug:layout/activity_main: Binary XML file line #7 in ru.touchin.template.debug:layout/activity_main: Error inflating class androidx.constraintlayout.widget.ConstraintLayout такого ресурса почему то нет, хотя он поставляется из сторонних библиотек
Как следствие на моем 29 эмуле прила падает
java.lang.RuntimeException: Unable to start activity ComponentInfo{ru.touchin.template.debug/ru.touchin.template.feature.SingleActivity}: android.view.InflateException: Binary XML file line #7 in ru.touchin.template.debug:layout/activity_main: Binary XML file line #7 in ru.touchin.template.debug:layout/activity_main: Error inflating class androidx.constraintlayout.widget.ConstraintLayout
...
Caused by: android.content.res.Resources$NotFoundException: File res/color-v26/biometric_error_color.xml from drawable resource ID #0x7f060021
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit d6f303bf879a2da1706cfdacaf2bbe0c326044bd
|
||||
А если у нас появилась BaseViewModel
То лучше и тут ее использовать