diff --git a/app/src/main/java/com/otus/dihomework/ProductsApplication.kt b/app/src/main/java/com/otus/dihomework/ProductsApplication.kt index 2e3799d..f9d2edb 100644 --- a/app/src/main/java/com/otus/dihomework/ProductsApplication.kt +++ b/app/src/main/java/com/otus/dihomework/ProductsApplication.kt @@ -1,10 +1,22 @@ package com.otus.dihomework import android.app.Application +import com.otus.dihomework.common.di.Dependencies +import com.otus.dihomework.common.di.DependenciesProvider +import com.otus.dihomework.di.AppComponent +import com.otus.dihomework.di.DaggerAppComponent + +class ProductsApplication : Application(), DependenciesProvider { + + lateinit var appComponent: AppComponent + private set -class ProductsApplication : Application() { override fun onCreate() { super.onCreate() - ServiceLocator.init(this) + appComponent = DaggerAppComponent.factory().create(applicationContext) + } + + override fun getDependencies(): Dependencies { + return appComponent } } diff --git a/app/src/main/java/com/otus/dihomework/ServiceLocator.kt b/app/src/main/java/com/otus/dihomework/ServiceLocator.kt deleted file mode 100644 index 1a56c01..0000000 --- a/app/src/main/java/com/otus/dihomework/ServiceLocator.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.otus.dihomework - -import android.content.Context -import com.google.gson.GsonBuilder -import com.otus.dihomework.common.data.FavoritesRepositoryImpl -import com.otus.dihomework.common.data.ProductApiService -import com.otus.dihomework.common.data.ProductDomainMapper -import com.otus.dihomework.common.data.ProductRemoteDataSource -import com.otus.dihomework.common.data.ProductRepositoryImpl -import com.otus.dihomework.common.domain_api.ConsumeFavoritesUseCase -import com.otus.dihomework.common.domain_api.ConsumeProductsUseCase -import com.otus.dihomework.common.domain_api.ToggleFavoriteUseCase -import com.otus.dihomework.common.domain_impl.ConsumeFavoritesUseCaseImpl -import com.otus.dihomework.common.domain_impl.ConsumeProductsUseCaseImpl -import com.otus.dihomework.common.domain_impl.FavoritesRepository -import com.otus.dihomework.common.domain_impl.ProductRepository -import com.otus.dihomework.common.domain_impl.ToggleFavoriteUseCaseImpl -import com.otus.dihomework.common.util.PriceFormatter -import com.otus.dihomework.features.favorites.FavoritesStateFactory -import com.otus.dihomework.features.products.ProductsStateFactory -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import java.util.concurrent.TimeUnit - -object ServiceLocator { - - private var applicationContext: Context? = null - - fun init(context: Context) { - applicationContext = context.applicationContext - } - - private val httpLoggingInterceptor: HttpLoggingInterceptor by lazy { - HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY - } - } - - private val okHttpClient: OkHttpClient by lazy { - OkHttpClient.Builder() - .addInterceptor(httpLoggingInterceptor) - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build() - } - - private val retrofit: Retrofit by lazy { - Retrofit.Builder() - .baseUrl("https://otus-android.github.io/") - .client(okHttpClient) - .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create())) - .build() - } - - private val _productApiService: ProductApiService by lazy { - retrofit.create(ProductApiService::class.java) - } - - fun getProductApiService(): ProductApiService { - return _productApiService - } - - fun getProductDomainMapper(): ProductDomainMapper { - return ProductDomainMapper() - } - - fun getProductRemoteDataSource(): ProductRemoteDataSource { - return ProductRemoteDataSource() - } - - fun getFavoritesRepository(): FavoritesRepository { - val context = applicationContext - checkNotNull(context) { "ServiceLocator not initialized! Call init() first." } - return FavoritesRepositoryImpl(context) - } - - fun getProductRepository(): ProductRepository { - return ProductRepositoryImpl() - } - - fun getConsumeProductsUseCase(): ConsumeProductsUseCase { - return ConsumeProductsUseCaseImpl() - } - - fun getConsumeFavoritesUseCase(): ConsumeFavoritesUseCase { - return ConsumeFavoritesUseCaseImpl() - } - - fun getToggleFavoriteUseCase(): ToggleFavoriteUseCase { - return ToggleFavoriteUseCaseImpl() - } - - fun getPriceFormatter(): PriceFormatter { - return PriceFormatter() - } - - fun getProductsStateFactory(): ProductsStateFactory { - return ProductsStateFactory() - } - - fun getFavoritesStateFactory(): FavoritesStateFactory { - return FavoritesStateFactory() - } -} diff --git a/app/src/main/java/com/otus/dihomework/common/data/FavoritesRepositoryImpl.kt b/app/src/main/java/com/otus/dihomework/common/data/FavoritesRepositoryImpl.kt index ad70211..79c9b35 100644 --- a/app/src/main/java/com/otus/dihomework/common/data/FavoritesRepositoryImpl.kt +++ b/app/src/main/java/com/otus/dihomework/common/data/FavoritesRepositoryImpl.kt @@ -9,10 +9,13 @@ import androidx.datastore.preferences.preferencesDataStore import com.otus.dihomework.common.domain_impl.FavoritesRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton private val Context.dataStore: DataStore by preferencesDataStore(name = "favorites") -class FavoritesRepositoryImpl( +@Singleton +class FavoritesRepositoryImpl @Inject constructor( private val context: Context ) : FavoritesRepository { diff --git a/app/src/main/java/com/otus/dihomework/common/data/ProductDomainMapper.kt b/app/src/main/java/com/otus/dihomework/common/data/ProductDomainMapper.kt index 762959d..0ba54f1 100644 --- a/app/src/main/java/com/otus/dihomework/common/data/ProductDomainMapper.kt +++ b/app/src/main/java/com/otus/dihomework/common/data/ProductDomainMapper.kt @@ -1,8 +1,9 @@ package com.otus.dihomework.common.data import com.otus.dihomework.common.domain_api.Product +import javax.inject.Inject -class ProductDomainMapper() { +class ProductDomainMapper @Inject constructor() { fun fromDto(dto: ProductDto): Product { return Product( diff --git a/app/src/main/java/com/otus/dihomework/common/data/ProductRemoteDataSource.kt b/app/src/main/java/com/otus/dihomework/common/data/ProductRemoteDataSource.kt index ae9935f..ba9670e 100644 --- a/app/src/main/java/com/otus/dihomework/common/data/ProductRemoteDataSource.kt +++ b/app/src/main/java/com/otus/dihomework/common/data/ProductRemoteDataSource.kt @@ -1,11 +1,12 @@ package com.otus.dihomework.common.data -import com.otus.dihomework.ServiceLocator import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import javax.inject.Inject -class ProductRemoteDataSource() { - private val apiService = ServiceLocator.getProductApiService() +class ProductRemoteDataSource @Inject constructor( + private val apiService: ProductApiService +) { fun consumeProducts(): Flow> = flow { emit(apiService.getProducts()) diff --git a/app/src/main/java/com/otus/dihomework/common/data/ProductRepositoryImpl.kt b/app/src/main/java/com/otus/dihomework/common/data/ProductRepositoryImpl.kt index 7027dca..22585ad 100644 --- a/app/src/main/java/com/otus/dihomework/common/data/ProductRepositoryImpl.kt +++ b/app/src/main/java/com/otus/dihomework/common/data/ProductRepositoryImpl.kt @@ -1,15 +1,17 @@ package com.otus.dihomework.common.data -import com.otus.dihomework.ServiceLocator import com.otus.dihomework.common.domain_impl.ProductRepository import com.otus.dihomework.common.domain_api.Product import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton -class ProductRepositoryImpl() : ProductRepository { - - private val remoteDataSource = ServiceLocator.getProductRemoteDataSource() - private val mapper = ServiceLocator.getProductDomainMapper() +@Singleton +class ProductRepositoryImpl @Inject constructor( + private val remoteDataSource: ProductRemoteDataSource, + private val mapper: ProductDomainMapper +) : ProductRepository { override fun consumeProducts(): Flow> { return remoteDataSource.consumeProducts() diff --git a/app/src/main/java/com/otus/dihomework/common/domain_impl/ConsumeFavoritesUseCaseImpl.kt b/app/src/main/java/com/otus/dihomework/common/domain_impl/ConsumeFavoritesUseCaseImpl.kt index bc9b71f..7094d2f 100644 --- a/app/src/main/java/com/otus/dihomework/common/domain_impl/ConsumeFavoritesUseCaseImpl.kt +++ b/app/src/main/java/com/otus/dihomework/common/domain_impl/ConsumeFavoritesUseCaseImpl.kt @@ -1,15 +1,15 @@ package com.otus.dihomework.common.domain_impl -import com.otus.dihomework.ServiceLocator import com.otus.dihomework.common.domain_api.ConsumeFavoritesUseCase import com.otus.dihomework.common.domain_api.ProductWithFavorite import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import javax.inject.Inject -class ConsumeFavoritesUseCaseImpl() : ConsumeFavoritesUseCase { - - private val productRepository = ServiceLocator.getProductRepository() - private val favoritesRepository = ServiceLocator.getFavoritesRepository() +class ConsumeFavoritesUseCaseImpl @Inject constructor( + private val productRepository: ProductRepository, + private val favoritesRepository: FavoritesRepository +) : ConsumeFavoritesUseCase { override fun invoke(): Flow> { return combine( diff --git a/app/src/main/java/com/otus/dihomework/common/domain_impl/ConsumeProductsUseCaseImpl.kt b/app/src/main/java/com/otus/dihomework/common/domain_impl/ConsumeProductsUseCaseImpl.kt index 6314f50..06e8660 100644 --- a/app/src/main/java/com/otus/dihomework/common/domain_impl/ConsumeProductsUseCaseImpl.kt +++ b/app/src/main/java/com/otus/dihomework/common/domain_impl/ConsumeProductsUseCaseImpl.kt @@ -1,16 +1,16 @@ package com.otus.dihomework.common.domain_impl -import com.otus.dihomework.ServiceLocator import com.otus.dihomework.common.domain_api.ConsumeProductsUseCase import com.otus.dihomework.common.domain_api.ProductWithFavorite import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map +import javax.inject.Inject -class ConsumeProductsUseCaseImpl() : ConsumeProductsUseCase { - - private val productRepository = ServiceLocator.getProductRepository() - private val favoritesRepository = ServiceLocator.getFavoritesRepository() +class ConsumeProductsUseCaseImpl @Inject constructor( + private val productRepository: ProductRepository, + private val favoritesRepository: FavoritesRepository +) : ConsumeProductsUseCase { override fun invoke(): Flow> { return combine( diff --git a/app/src/main/java/com/otus/dihomework/common/domain_impl/ToggleFavoriteUseCaseImpl.kt b/app/src/main/java/com/otus/dihomework/common/domain_impl/ToggleFavoriteUseCaseImpl.kt index 4b5c8b7..99b52ae 100644 --- a/app/src/main/java/com/otus/dihomework/common/domain_impl/ToggleFavoriteUseCaseImpl.kt +++ b/app/src/main/java/com/otus/dihomework/common/domain_impl/ToggleFavoriteUseCaseImpl.kt @@ -1,11 +1,11 @@ package com.otus.dihomework.common.domain_impl -import com.otus.dihomework.ServiceLocator import com.otus.dihomework.common.domain_api.ToggleFavoriteUseCase +import javax.inject.Inject -class ToggleFavoriteUseCaseImpl() : ToggleFavoriteUseCase { - - private val favoritesRepository = ServiceLocator.getFavoritesRepository() +class ToggleFavoriteUseCaseImpl @Inject constructor( + private val favoritesRepository: FavoritesRepository +) : ToggleFavoriteUseCase { override suspend fun invoke(productId: String, isFavorite: Boolean) { if (isFavorite) { diff --git a/app/src/main/java/com/otus/dihomework/common/util/PriceFormatter.kt b/app/src/main/java/com/otus/dihomework/common/util/PriceFormatter.kt index d13a12f..446678a 100644 --- a/app/src/main/java/com/otus/dihomework/common/util/PriceFormatter.kt +++ b/app/src/main/java/com/otus/dihomework/common/util/PriceFormatter.kt @@ -1,6 +1,8 @@ package com.otus.dihomework.common.util -class PriceFormatter() { +import javax.inject.Inject + +class PriceFormatter @Inject constructor() { fun format(price: Double): String { return "${price.toInt()} ₽" diff --git a/app/src/main/java/com/otus/dihomework/di/AppComponent.kt b/app/src/main/java/com/otus/dihomework/di/AppComponent.kt new file mode 100644 index 0000000..806da15 --- /dev/null +++ b/app/src/main/java/com/otus/dihomework/di/AppComponent.kt @@ -0,0 +1,25 @@ +package com.otus.dihomework.di + +import android.content.Context +import dagger.BindsInstance +import dagger.Component +import javax.inject.Singleton + +@Singleton +@Component( + modules = [ + NetworkModule::class, + DataModule::class, + DomainModule::class, + SubcomponentsModule::class, + ] +) +interface AppComponent : ProductsDependencies { + + fun favoritesComponent(): FavoritesComponent.Factory + + @Component.Factory + interface Factory { + fun create(@BindsInstance context: Context): AppComponent + } +} diff --git a/app/src/main/java/com/otus/dihomework/di/DataModule.kt b/app/src/main/java/com/otus/dihomework/di/DataModule.kt new file mode 100644 index 0000000..b492724 --- /dev/null +++ b/app/src/main/java/com/otus/dihomework/di/DataModule.kt @@ -0,0 +1,21 @@ +package com.otus.dihomework.di + +import com.otus.dihomework.common.data.FavoritesRepositoryImpl +import com.otus.dihomework.common.data.ProductRepositoryImpl +import com.otus.dihomework.common.domain_impl.FavoritesRepository +import com.otus.dihomework.common.domain_impl.ProductRepository +import dagger.Binds +import dagger.Module +import javax.inject.Singleton + +@Module +abstract class DataModule { + + @Binds + @Singleton + abstract fun bindProductRepository(impl: ProductRepositoryImpl): ProductRepository + + @Binds + @Singleton + abstract fun bindFavoritesRepository(impl: FavoritesRepositoryImpl): FavoritesRepository +} diff --git a/app/src/main/java/com/otus/dihomework/di/DomainModule.kt b/app/src/main/java/com/otus/dihomework/di/DomainModule.kt new file mode 100644 index 0000000..3e797d8 --- /dev/null +++ b/app/src/main/java/com/otus/dihomework/di/DomainModule.kt @@ -0,0 +1,23 @@ +package com.otus.dihomework.di + +import com.otus.dihomework.common.domain_api.ConsumeFavoritesUseCase +import com.otus.dihomework.common.domain_api.ConsumeProductsUseCase +import com.otus.dihomework.common.domain_api.ToggleFavoriteUseCase +import com.otus.dihomework.common.domain_impl.ConsumeFavoritesUseCaseImpl +import com.otus.dihomework.common.domain_impl.ConsumeProductsUseCaseImpl +import com.otus.dihomework.common.domain_impl.ToggleFavoriteUseCaseImpl +import dagger.Binds +import dagger.Module + +@Module +abstract class DomainModule { + + @Binds + abstract fun bindConsumeProducts(impl: ConsumeProductsUseCaseImpl): ConsumeProductsUseCase + + @Binds + abstract fun bindConsumeFavorites(impl: ConsumeFavoritesUseCaseImpl): ConsumeFavoritesUseCase + + @Binds + abstract fun bindToggleFavorite(impl: ToggleFavoriteUseCaseImpl): ToggleFavoriteUseCase +} diff --git a/app/src/main/java/com/otus/dihomework/di/FavoritesComponent.kt b/app/src/main/java/com/otus/dihomework/di/FavoritesComponent.kt new file mode 100644 index 0000000..ebd98a9 --- /dev/null +++ b/app/src/main/java/com/otus/dihomework/di/FavoritesComponent.kt @@ -0,0 +1,17 @@ +package com.otus.dihomework.di + +import com.otus.dihomework.common.di.FeatureScope +import com.otus.dihomework.features.favorites.FavoritesViewModelFactory +import dagger.Subcomponent + +@FeatureScope +@Subcomponent +interface FavoritesComponent { + + fun viewModelFactory(): FavoritesViewModelFactory + + @Subcomponent.Factory + interface Factory { + fun create(): FavoritesComponent + } +} diff --git a/app/src/main/java/com/otus/dihomework/di/NetworkModule.kt b/app/src/main/java/com/otus/dihomework/di/NetworkModule.kt new file mode 100644 index 0000000..afddfe4 --- /dev/null +++ b/app/src/main/java/com/otus/dihomework/di/NetworkModule.kt @@ -0,0 +1,47 @@ +package com.otus.dihomework.di + +import com.google.gson.GsonBuilder +import com.otus.dihomework.common.data.ProductApiService +import dagger.Module +import dagger.Provides +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit +import javax.inject.Singleton + +@Module +object NetworkModule { + + private const val BASE_URL = "https://otus-android.github.io/" + + @Provides + @Singleton + fun provideOkHttpClient(): OkHttpClient { + val logging = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + return OkHttpClient.Builder() + .addInterceptor(logging) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() + } + + @Provides + @Singleton + fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create())) + .build() + } + + @Provides + @Singleton + fun provideProductApiService(retrofit: Retrofit): ProductApiService { + return retrofit.create(ProductApiService::class.java) + } +} diff --git a/app/src/main/java/com/otus/dihomework/di/ProductsComponent.kt b/app/src/main/java/com/otus/dihomework/di/ProductsComponent.kt new file mode 100644 index 0000000..d586178 --- /dev/null +++ b/app/src/main/java/com/otus/dihomework/di/ProductsComponent.kt @@ -0,0 +1,17 @@ +package com.otus.dihomework.di + +import com.otus.dihomework.common.di.FeatureScope +import com.otus.dihomework.features.products.ProductsViewModelFactory +import dagger.Component + +@FeatureScope +@Component(dependencies = [ProductsDependencies::class]) +interface ProductsComponent { + + fun viewModelFactory(): ProductsViewModelFactory + + @Component.Factory + interface Factory { + fun create(dependencies: ProductsDependencies): ProductsComponent + } +} diff --git a/app/src/main/java/com/otus/dihomework/di/ProductsDependencies.kt b/app/src/main/java/com/otus/dihomework/di/ProductsDependencies.kt new file mode 100644 index 0000000..049eb78 --- /dev/null +++ b/app/src/main/java/com/otus/dihomework/di/ProductsDependencies.kt @@ -0,0 +1,12 @@ +package com.otus.dihomework.di + +import com.otus.dihomework.common.di.Dependencies +import com.otus.dihomework.common.domain_api.ConsumeProductsUseCase +import com.otus.dihomework.common.domain_api.ToggleFavoriteUseCase +import com.otus.dihomework.features.products.ProductsStateFactory + +interface ProductsDependencies : Dependencies { + fun consumeProductsUseCase(): ConsumeProductsUseCase + fun toggleFavoriteUseCase(): ToggleFavoriteUseCase + fun productsStateFactory(): ProductsStateFactory +} diff --git a/app/src/main/java/com/otus/dihomework/di/SubcomponentsModule.kt b/app/src/main/java/com/otus/dihomework/di/SubcomponentsModule.kt new file mode 100644 index 0000000..ff6f390 --- /dev/null +++ b/app/src/main/java/com/otus/dihomework/di/SubcomponentsModule.kt @@ -0,0 +1,6 @@ +package com.otus.dihomework.di + +import dagger.Module + +@Module(subcomponents = [FavoritesComponent::class]) +interface SubcomponentsModule diff --git a/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesStateFactory.kt b/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesStateFactory.kt index d2569a4..56c6c82 100644 --- a/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesStateFactory.kt +++ b/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesStateFactory.kt @@ -1,10 +1,12 @@ package com.otus.dihomework.features.favorites -import com.otus.dihomework.ServiceLocator import com.otus.dihomework.common.domain_api.ProductWithFavorite +import com.otus.dihomework.common.util.PriceFormatter +import javax.inject.Inject -class FavoritesStateFactory() { - private val priceFormatter = ServiceLocator.getPriceFormatter() +class FavoritesStateFactory @Inject constructor( + private val priceFormatter: PriceFormatter +) { fun create(favorites: List): List { return favorites.map { createFavoriteState(it) } diff --git a/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesViewModel.kt b/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesViewModel.kt index 9d41c4b..0f96b04 100644 --- a/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesViewModel.kt +++ b/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesViewModel.kt @@ -2,7 +2,8 @@ package com.otus.dihomework.features.favorites import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.otus.dihomework.ServiceLocator +import com.otus.dihomework.common.domain_api.ConsumeFavoritesUseCase +import com.otus.dihomework.common.domain_api.ToggleFavoriteUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -12,12 +13,13 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import javax.inject.Inject -class FavoritesViewModel() : ViewModel() { - - private val consumeFavoritesUseCase = ServiceLocator.getConsumeFavoritesUseCase() - private val toggleFavoriteUseCase = ServiceLocator.getToggleFavoriteUseCase() - private val favoritesStateFactory = ServiceLocator.getFavoritesStateFactory() +class FavoritesViewModel @Inject constructor( + private val consumeFavoritesUseCase: ConsumeFavoritesUseCase, + private val toggleFavoriteUseCase: ToggleFavoriteUseCase, + private val favoritesStateFactory: FavoritesStateFactory +) : ViewModel() { private val _state = MutableStateFlow(FavoritesScreenState()) val state: StateFlow = _state.asStateFlow() diff --git a/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesViewModelFactory.kt b/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesViewModelFactory.kt index a30f8fc..75ffbce 100644 --- a/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesViewModelFactory.kt +++ b/app/src/main/java/com/otus/dihomework/features/favorites/FavoritesViewModelFactory.kt @@ -2,12 +2,18 @@ package com.otus.dihomework.features.favorites import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import com.otus.dihomework.common.di.FeatureScope +import javax.inject.Inject +import javax.inject.Provider -class FavoritesViewModelFactory() : ViewModelProvider.Factory { +@FeatureScope +class FavoritesViewModelFactory @Inject constructor( + private val viewModelProvider: Provider +) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { require(modelClass == FavoritesViewModel::class.java) - return FavoritesViewModel() as T + return viewModelProvider.get() as T } } diff --git a/app/src/main/java/com/otus/dihomework/features/favorites/ui/FavoritesScreen.kt b/app/src/main/java/com/otus/dihomework/features/favorites/ui/FavoritesScreen.kt index 6c9c79b..828e9bd 100644 --- a/app/src/main/java/com/otus/dihomework/features/favorites/ui/FavoritesScreen.kt +++ b/app/src/main/java/com/otus/dihomework/features/favorites/ui/FavoritesScreen.kt @@ -12,11 +12,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.otus.dihomework.ProductsApplication import com.otus.dihomework.R import com.otus.dihomework.features.favorites.FavoritesScreenState import com.otus.dihomework.features.favorites.FavoritesViewModel @@ -26,8 +29,15 @@ import com.otus.dihomework.features.favorites.FavoritesViewModelFactory fun FavoritesScreenContent( modifier: Modifier = Modifier ) { + val context = LocalContext.current + val factory = remember(context.applicationContext) { + (context.applicationContext as ProductsApplication).appComponent + .favoritesComponent() + .create() + .viewModelFactory() + } val viewModel: FavoritesViewModel = viewModel( - factory = FavoritesViewModelFactory() + factory = factory ) val state by viewModel.state.collectAsState() diff --git a/app/src/main/java/com/otus/dihomework/features/products/ProductsStateFactory.kt b/app/src/main/java/com/otus/dihomework/features/products/ProductsStateFactory.kt index d78f6e7..cea5b8d 100644 --- a/app/src/main/java/com/otus/dihomework/features/products/ProductsStateFactory.kt +++ b/app/src/main/java/com/otus/dihomework/features/products/ProductsStateFactory.kt @@ -1,10 +1,12 @@ package com.otus.dihomework.features.products -import com.otus.dihomework.ServiceLocator import com.otus.dihomework.common.domain_api.ProductWithFavorite +import com.otus.dihomework.common.util.PriceFormatter +import javax.inject.Inject -class ProductsStateFactory() { - private val priceFormatter = ServiceLocator.getPriceFormatter() +class ProductsStateFactory @Inject constructor( + private val priceFormatter: PriceFormatter +) { fun create(products: List): List { return products diff --git a/app/src/main/java/com/otus/dihomework/features/products/ProductsViewModel.kt b/app/src/main/java/com/otus/dihomework/features/products/ProductsViewModel.kt index ad2ce7d..63a1670 100644 --- a/app/src/main/java/com/otus/dihomework/features/products/ProductsViewModel.kt +++ b/app/src/main/java/com/otus/dihomework/features/products/ProductsViewModel.kt @@ -2,7 +2,8 @@ package com.otus.dihomework.features.products import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.otus.dihomework.ServiceLocator +import com.otus.dihomework.common.domain_api.ConsumeProductsUseCase +import com.otus.dihomework.common.domain_api.ToggleFavoriteUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -12,12 +13,13 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import javax.inject.Inject -class ProductsViewModel() : ViewModel() { - - private val consumeProductsUseCase = ServiceLocator.getConsumeProductsUseCase() - private val toggleFavoriteUseCase = ServiceLocator.getToggleFavoriteUseCase() - private val productsStateFactory = ServiceLocator.getProductsStateFactory() +class ProductsViewModel @Inject constructor( + private val consumeProductsUseCase: ConsumeProductsUseCase, + private val toggleFavoriteUseCase: ToggleFavoriteUseCase, + private val productsStateFactory: ProductsStateFactory +) : ViewModel() { private val _state = MutableStateFlow(ProductsScreenState()) val state: StateFlow = _state.asStateFlow() diff --git a/app/src/main/java/com/otus/dihomework/features/products/ProductsViewModelFactory.kt b/app/src/main/java/com/otus/dihomework/features/products/ProductsViewModelFactory.kt index 82defc9..5391512 100644 --- a/app/src/main/java/com/otus/dihomework/features/products/ProductsViewModelFactory.kt +++ b/app/src/main/java/com/otus/dihomework/features/products/ProductsViewModelFactory.kt @@ -2,12 +2,18 @@ package com.otus.dihomework.features.products import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import com.otus.dihomework.common.di.FeatureScope +import javax.inject.Inject +import javax.inject.Provider -class ProductsViewModelFactory() : ViewModelProvider.Factory { +@FeatureScope +class ProductsViewModelFactory @Inject constructor( + private val viewModelProvider: Provider +) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { require(modelClass == ProductsViewModel::class.java) - return ProductsViewModel() as T + return viewModelProvider.get() as T } } diff --git a/app/src/main/java/com/otus/dihomework/features/products/ui/ProductsScreen.kt b/app/src/main/java/com/otus/dihomework/features/products/ui/ProductsScreen.kt index 0072b10..1165cf4 100644 --- a/app/src/main/java/com/otus/dihomework/features/products/ui/ProductsScreen.kt +++ b/app/src/main/java/com/otus/dihomework/features/products/ui/ProductsScreen.kt @@ -13,20 +13,30 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.otus.dihomework.common.di.findDependencies +import com.otus.dihomework.di.DaggerProductsComponent +import com.otus.dihomework.di.ProductsDependencies import com.otus.dihomework.features.products.ProductsScreenState import com.otus.dihomework.features.products.ProductsViewModel -import com.otus.dihomework.features.products.ProductsViewModelFactory @Composable fun ProductsScreenContent( modifier: Modifier = Modifier ) { + val context = LocalContext.current + val factory = remember(context.applicationContext) { + DaggerProductsComponent.factory() + .create(context.findDependencies()) + .viewModelFactory() + } val viewModel: ProductsViewModel = viewModel( - factory = ProductsViewModelFactory() + factory = factory ) val state by viewModel.state.collectAsState() diff --git a/common/di/src/main/java/com/otus/dihomework/common/di/Dependencies.kt b/common/di/src/main/java/com/otus/dihomework/common/di/Dependencies.kt new file mode 100644 index 0000000..e8f269a --- /dev/null +++ b/common/di/src/main/java/com/otus/dihomework/common/di/Dependencies.kt @@ -0,0 +1,13 @@ +package com.otus.dihomework.common.di + +import android.content.Context + +interface Dependencies + +interface DependenciesProvider { + fun getDependencies(): Dependencies +} + +inline fun Context.findDependencies(): T { + return (applicationContext as DependenciesProvider).getDependencies() as T +}