From 417547d44e25e1e33ffcf0f0bb06ad3c9f0c0caa Mon Sep 17 00:00:00 2001 From: Evgeny Dubravin Date: Thu, 28 Mar 2024 17:10:25 +0700 Subject: [PATCH 1/5] =?UTF-8?q?feature=20TI-193:=20[Android]=20=D0=A0?= =?UTF-8?q?=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?ViewModel=20Factory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 3 -- app/src/main/java/ru/touchin/template/App.kt | 15 +++++++++- .../ru/touchin/template/SingleViewModel.kt | 6 ++++ .../template/base/activity/BaseActivity.kt | 12 ++++++++ .../template/base/fragment/BaseFragment.kt | 12 ++++++++ .../ru/touchin/template/di/AppComponent.kt | 30 +++++++++++++++++++ .../main/java/ru/touchin/template/di/DI.kt | 16 ++++++++++ .../touchin/template/di/SharedComponentExt.kt | 17 +++++++++++ .../template/di/SharedComponentProvider.kt | 15 ++++++++++ .../touchin/template/di/modules/AppModule.kt | 11 +++++++ .../template/di/modules/NetworkModule.kt | 7 +++++ .../template/di/modules/RepositoryModule.kt | 7 +++++ .../template/di/modules/ViewModelModule.kt | 22 ++++++++++++++ .../template/di/viewmodel/ViewModelFactory.kt | 17 +++++++++++ .../template/di/viewmodel/ViewModelKey.kt | 9 ++++++ common-template | 1 + 16 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/ru/touchin/template/SingleViewModel.kt create mode 100644 app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt create mode 100644 app/src/main/java/ru/touchin/template/base/fragment/BaseFragment.kt create mode 100644 app/src/main/java/ru/touchin/template/di/AppComponent.kt create mode 100644 app/src/main/java/ru/touchin/template/di/DI.kt create mode 100644 app/src/main/java/ru/touchin/template/di/SharedComponentExt.kt create mode 100644 app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt create mode 100644 app/src/main/java/ru/touchin/template/di/modules/AppModule.kt create mode 100644 app/src/main/java/ru/touchin/template/di/modules/NetworkModule.kt create mode 100644 app/src/main/java/ru/touchin/template/di/modules/RepositoryModule.kt create mode 100644 app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt create mode 100644 app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt create mode 100644 app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt create mode 160000 common-template diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5103e8c..a7d316a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/src/main/java/ru/touchin/template/App.kt b/app/src/main/java/ru/touchin/template/App.kt index c6ffdc1..0048de1 100644 --- a/app/src/main/java/ru/touchin/template/App.kt +++ b/app/src/main/java/ru/touchin/template/App.kt @@ -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() +} diff --git a/app/src/main/java/ru/touchin/template/SingleViewModel.kt b/app/src/main/java/ru/touchin/template/SingleViewModel.kt new file mode 100644 index 0000000..15b24f0 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/SingleViewModel.kt @@ -0,0 +1,6 @@ +package ru.touchin.template + +import androidx.lifecycle.ViewModel +import javax.inject.Inject + +class SingleViewModel @Inject constructor() : ViewModel() \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt b/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt new file mode 100644 index 0000000..df11275 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt @@ -0,0 +1,12 @@ +package ru.touchin.template.base.activity + +import androidx.appcompat.app.AppCompatActivity +import ru.touchin.template.di.SharedComponent +import ru.touchin.template.di.getSharedModule + +class BaseActivity : AppCompatActivity() { + + protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() } + + protected fun getSharedComponent(): SharedComponent = getSharedModule() +} \ No newline at end of file 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 new file mode 100644 index 0000000..c9c3c8e --- /dev/null +++ b/app/src/main/java/ru/touchin/template/base/fragment/BaseFragment.kt @@ -0,0 +1,12 @@ +package ru.touchin.template.base.fragment + +import androidx.fragment.app.Fragment +import ru.touchin.template.di.SharedComponent +import ru.touchin.template.di.getSharedModule + +class BaseFragment : Fragment() { + + protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() } + + protected fun getSharedComponent(): SharedComponent = getSharedModule() +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/AppComponent.kt b/app/src/main/java/ru/touchin/template/di/AppComponent.kt new file mode 100644 index 0000000..f4b7320 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/AppComponent.kt @@ -0,0 +1,30 @@ +package ru.touchin.template.di + +import android.content.Context +import dagger.BindsInstance +import dagger.Component +import javax.inject.Singleton +import ru.touchin.template.App +import ru.touchin.template.di.modules.AppModule +import ru.touchin.template.di.modules.ViewModelModule + +@Component( + modules = [ + AppModule::class, + ViewModelModule::class + ] +) +@Singleton +interface AppComponent : SharedComponent { + + @Component.Builder + interface Builder { + + @BindsInstance + fun appContext(appContext: Context): Builder + + fun build(): AppComponent + } + + fun inject(entry: App) +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/DI.kt b/app/src/main/java/ru/touchin/template/di/DI.kt new file mode 100644 index 0000000..626c007 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/DI.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/SharedComponentExt.kt b/app/src/main/java/ru/touchin/template/di/SharedComponentExt.kt new file mode 100644 index 0000000..36993ee --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/SharedComponentExt.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt b/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt new file mode 100644 index 0000000..0ee19ab --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt @@ -0,0 +1,15 @@ +package ru.touchin.template.di + +import dagger.Module +import ru.touchin.template.di.viewmodel.ViewModelFactory + +interface SharedComponent { + fun viewModelFactory(): ViewModelFactory +} + +@Module +class SharedModule + +interface SharedComponentProvider { + fun getModule(): SharedComponent +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/AppModule.kt b/app/src/main/java/ru/touchin/template/di/modules/AppModule.kt new file mode 100644 index 0000000..0943510 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/modules/AppModule.kt @@ -0,0 +1,11 @@ +package ru.touchin.template.di.modules + +import dagger.Module + +@Module( + includes = [ + RepositoryModule::class, + NetworkModule::class + ] +) +class AppModule \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/NetworkModule.kt b/app/src/main/java/ru/touchin/template/di/modules/NetworkModule.kt new file mode 100644 index 0000000..8c14718 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/modules/NetworkModule.kt @@ -0,0 +1,7 @@ +package ru.touchin.template.di.modules + +import dagger.Module + +@Module +class NetworkModule { +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/RepositoryModule.kt b/app/src/main/java/ru/touchin/template/di/modules/RepositoryModule.kt new file mode 100644 index 0000000..f995563 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/modules/RepositoryModule.kt @@ -0,0 +1,7 @@ +package ru.touchin.template.di.modules + +import dagger.Module + +@Module +class RepositoryModule { +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt b/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt new file mode 100644 index 0000000..9331a01 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt @@ -0,0 +1,22 @@ +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.SingleViewModel +import ru.touchin.template.di.viewmodel.ViewModelFactory +import ru.touchin.template.di.viewmodel.ViewModelKey + +@Module +interface ViewModelModule { + + @Binds + fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory + + @Binds + @IntoMap + @ViewModelKey(SingleViewModel::class) + fun bindsSingleViewModel(viewModel: SingleViewModel): ViewModel +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt new file mode 100644 index 0000000..297984b --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt @@ -0,0 +1,17 @@ +package ru.touchin.template.di.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import javax.inject.Inject +import javax.inject.Provider + +class ViewModelFactory @Inject constructor( + private val viewModels: MutableMap, Provider> +) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + val viewModel = viewModels[modelClass]?.get() + return viewModel as T + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt new file mode 100644 index 0000000..1f76f60 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt @@ -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) \ No newline at end of file diff --git a/common-template b/common-template new file mode 160000 index 0000000..d6f303b --- /dev/null +++ b/common-template @@ -0,0 +1 @@ +Subproject commit d6f303bf879a2da1706cfdacaf2bbe0c326044bd -- 2.40.1 From c4b740f629d11839c35c02253633b5988e49914c Mon Sep 17 00:00:00 2001 From: Evgeny Dubravin Date: Fri, 29 Mar 2024 22:36:19 +0700 Subject: [PATCH 2/5] feature TI-194: [Android] DI --- app/src/main/AndroidManifest.xml | 6 ++--- .../ru/touchin/template/SingleActivity.kt | 5 ---- .../template/base/activity/BaseActivity.kt | 7 ++++- .../template/base/fragment/BaseFragment.kt | 5 +++- .../template/di/SharedComponentProvider.kt | 2 ++ .../template/di/modules/ViewModelModule.kt | 9 ++++++- .../template/di/viewmodel/ViewModelFactory.kt | 27 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 24 ++++++++++++++--- app/src/main/res/layout/fragment_first.xml | 27 +++++++++++++++++++ app/src/main/res/layout/fragment_second.xml | 27 +++++++++++++++++++ 10 files changed, 124 insertions(+), 15 deletions(-) delete mode 100644 app/src/main/java/ru/touchin/template/SingleActivity.kt create mode 100644 app/src/main/res/layout/fragment_first.xml create mode 100644 app/src/main/res/layout/fragment_second.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 498225a..0f6717f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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"> diff --git a/app/src/main/java/ru/touchin/template/SingleActivity.kt b/app/src/main/java/ru/touchin/template/SingleActivity.kt deleted file mode 100644 index e71c1db..0000000 --- a/app/src/main/java/ru/touchin/template/SingleActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package ru.touchin.template - -import androidx.appcompat.app.AppCompatActivity - -class SingleActivity : AppCompatActivity() diff --git a/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt b/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt index df11275..0100cf3 100644 --- a/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt +++ b/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt @@ -1,12 +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 -class BaseActivity : AppCompatActivity() { +abstract class BaseActivity : AppCompatActivity() { + + protected val viewModel: T by lazy { createViewModelLazy().value } protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() } + abstract fun createViewModelLazy(): Lazy + protected fun getSharedComponent(): SharedComponent = getSharedModule() } \ No newline at end of file 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 c9c3c8e..7125d9e 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,12 +1,15 @@ package ru.touchin.template.base.fragment import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel import ru.touchin.template.di.SharedComponent import ru.touchin.template.di.getSharedModule -class BaseFragment : Fragment() { +abstract class BaseFragment : Fragment() { protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() } + protected abstract fun createViewModelLazy(): Lazy + protected fun getSharedComponent(): SharedComponent = getSharedModule() } \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt b/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt index 0ee19ab..771a420 100644 --- a/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt +++ b/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt @@ -2,9 +2,11 @@ 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 diff --git a/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt b/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt index 9331a01..781d5ad 100644 --- a/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt +++ b/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt @@ -5,9 +5,10 @@ import androidx.lifecycle.ViewModelProvider import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap -import ru.touchin.template.SingleViewModel 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 { @@ -19,4 +20,10 @@ interface ViewModelModule { @IntoMap @ViewModelKey(SingleViewModel::class) fun bindsSingleViewModel(viewModel: SingleViewModel): ViewModel + + @Binds + @IntoMap + @ViewModelKey(FirstViewModel::class) + fun bindsFirstViewModel(viewModel: FirstViewModel): ViewModel + } \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt index 297984b..699e68c 100644 --- a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt +++ b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt @@ -1,7 +1,13 @@ 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 @@ -14,4 +20,25 @@ class ViewModelFactory @Inject constructor( val viewModel = viewModels[modelClass]?.get() return viewModel as T } +} + +inline fun Fragment.assistedViewModel( + crossinline creator: (SavedStateHandle) -> VM, +): Lazy = viewModels { createAbstractSavedStateViewModelFactory(arguments, creator) } + +inline fun SavedStateRegistryOwner.createAbstractSavedStateViewModelFactory( + arguments: Bundle? = Bundle(), + crossinline creator: (SavedStateHandle) -> T, +): ViewModelProvider.Factory { + return object : AbstractSavedStateViewModelFactory( + owner = this@createAbstractSavedStateViewModelFactory, + defaultArgs = arguments, + ) { + @Suppress("UNCHECKED_CAST") + override fun create( + key: String, + modelClass: Class, + handle: SavedStateHandle, + ): T = creator(handle) as T + } } \ 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 a066a0d..9de21c1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,22 @@ - - + android:layout_height="match_parent" + android:background="@color/biometric_error_color"> + + + + + + diff --git a/app/src/main/res/layout/fragment_first.xml b/app/src/main/res/layout/fragment_first.xml new file mode 100644 index 0000000..a36f0ea --- /dev/null +++ b/app/src/main/res/layout/fragment_first.xml @@ -0,0 +1,27 @@ + + + + + +