diff --git a/README.md b/README.md index 791b21e5d..fd352531e 100644 --- a/README.md +++ b/README.md @@ -51,4 +51,7 @@ fun bind(product: ...) { ### 선택 요구 사항 - [] `DateFormatter`가 Configuration Changes에도 살아남을 수 있도록 구현한다. -- [] Activity, ViewModel 외에도 다양한 컴포넌트(Fragment, Service 등)별 유지될 의존성을 관리한다. \ No newline at end of file +- [] Activity, ViewModel 외에도 다양한 컴포넌트(Fragment, Service 등)별 유지될 의존성을 관리한다. + +## 5단계 기능 요구 사항 +- [] 지금까지 만든 쇼핑 장바구니 앱에 적용된 DI 라이브러리를 Hilt 코드로 교체한다. 기존에 만들어 둔 모듈과 테스트 코드를 삭제하진 않아도 된다. 이전 요구 사항을 동일하게 만족해야 한다. \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f5e9a3f78..38d877ed8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.kapt) + alias(libs.plugins.hilt.android) } android { @@ -76,6 +77,11 @@ dependencies { androidTestImplementation(libs.assertj.core) androidTestImplementation(libs.truth) + // Hilt + kapt(libs.hilt.compiler) + kapt(libs.androidx.hilt.compiler) + implementation(libs.hilt.android) + // Reflection implementation(libs.kotlin.reflect) // Coroutines diff --git a/app/src/main/java/woowacourse/shopping/App.kt b/app/src/main/java/woowacourse/shopping/App.kt index 6e236674c..807a59ada 100644 --- a/app/src/main/java/woowacourse/shopping/App.kt +++ b/app/src/main/java/woowacourse/shopping/App.kt @@ -2,9 +2,11 @@ package woowacourse.shopping import android.app.Application import android.content.Context +import dagger.hilt.android.HiltAndroidApp import woowacourse.shopping.di.DatabaseModule import woowacourse.shopping.di.RepositoryModule +@HiltAndroidApp class App : Application() { val container: Container by lazy { Container() } diff --git a/app/src/main/java/woowacourse/shopping/data/repository/DefaultCartRepository.kt b/app/src/main/java/woowacourse/shopping/data/repository/DefaultCartRepository.kt index 49b97470d..fb5576e20 100644 --- a/app/src/main/java/woowacourse/shopping/data/repository/DefaultCartRepository.kt +++ b/app/src/main/java/woowacourse/shopping/data/repository/DefaultCartRepository.kt @@ -1,10 +1,10 @@ package woowacourse.shopping.data.repository -import woowacourse.shopping.annotation.Inject import woowacourse.shopping.data.CartProductDao import woowacourse.shopping.data.CartProductEntity import woowacourse.shopping.domain.model.CartProduct import woowacourse.shopping.domain.repository.CartRepository +import javax.inject.Inject class DefaultCartRepository @Inject diff --git a/app/src/main/java/woowacourse/shopping/data/repository/InMemoryCartRepository.kt b/app/src/main/java/woowacourse/shopping/data/repository/InMemoryCartRepository.kt index b92491105..b14db9abd 100644 --- a/app/src/main/java/woowacourse/shopping/data/repository/InMemoryCartRepository.kt +++ b/app/src/main/java/woowacourse/shopping/data/repository/InMemoryCartRepository.kt @@ -1,8 +1,8 @@ package woowacourse.shopping.data.repository -import woowacourse.shopping.annotation.Inject import woowacourse.shopping.domain.model.CartProduct import woowacourse.shopping.domain.repository.CartRepository +import javax.inject.Inject class InMemoryCartRepository @Inject diff --git a/app/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.kt b/app/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.kt index e1c84ca21..fed74fdd7 100644 --- a/app/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.kt +++ b/app/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.kt @@ -1,8 +1,8 @@ package woowacourse.shopping.data.repository -import woowacourse.shopping.annotation.Inject import woowacourse.shopping.domain.model.Product import woowacourse.shopping.domain.repository.ProductRepository +import javax.inject.Inject class ProductRepositoryImpl @Inject diff --git a/app/src/main/java/woowacourse/shopping/hilt/Qualifiers.kt b/app/src/main/java/woowacourse/shopping/hilt/Qualifiers.kt new file mode 100644 index 000000000..3956dc168 --- /dev/null +++ b/app/src/main/java/woowacourse/shopping/hilt/Qualifiers.kt @@ -0,0 +1,11 @@ +package woowacourse.shopping.hilt + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class HiltInMemory + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class HiltRoomDB diff --git a/app/src/main/java/woowacourse/shopping/hilt/module/CartRepositoryModule.kt b/app/src/main/java/woowacourse/shopping/hilt/module/CartRepositoryModule.kt new file mode 100644 index 000000000..3f289d10c --- /dev/null +++ b/app/src/main/java/woowacourse/shopping/hilt/module/CartRepositoryModule.kt @@ -0,0 +1,26 @@ +package woowacourse.shopping.hilt.module + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import woowacourse.shopping.data.repository.DefaultCartRepository +import woowacourse.shopping.data.repository.InMemoryCartRepository +import woowacourse.shopping.domain.repository.CartRepository +import woowacourse.shopping.hilt.HiltInMemory +import woowacourse.shopping.hilt.HiltRoomDB +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class CartRepositoryModule { + @Binds + @Singleton + @HiltRoomDB + abstract fun bindsDefaultCartRepository(impl: DefaultCartRepository): CartRepository + + @Binds + @Singleton + @HiltInMemory + abstract fun inMemoryCartRepository(impl: InMemoryCartRepository): CartRepository +} diff --git a/app/src/main/java/woowacourse/shopping/hilt/module/DatabaseModule.kt b/app/src/main/java/woowacourse/shopping/hilt/module/DatabaseModule.kt new file mode 100644 index 000000000..a3a9ef4f7 --- /dev/null +++ b/app/src/main/java/woowacourse/shopping/hilt/module/DatabaseModule.kt @@ -0,0 +1,22 @@ +package woowacourse.shopping.hilt.module + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import woowacourse.shopping.data.CartProductDao +import woowacourse.shopping.data.ShoppingDatabase + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + @Provides + fun provideShoppingDatabase( + @ApplicationContext context: Context, + ): ShoppingDatabase = ShoppingDatabase.getInstance(context) + + @Provides + fun provideCartProductDao(database: ShoppingDatabase): CartProductDao = database.cartProductDao() +} diff --git a/app/src/main/java/woowacourse/shopping/hilt/module/ProductRepositoryModule.kt b/app/src/main/java/woowacourse/shopping/hilt/module/ProductRepositoryModule.kt new file mode 100644 index 000000000..530398b94 --- /dev/null +++ b/app/src/main/java/woowacourse/shopping/hilt/module/ProductRepositoryModule.kt @@ -0,0 +1,17 @@ +package woowacourse.shopping.hilt.module + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped +import woowacourse.shopping.data.repository.ProductRepositoryImpl +import woowacourse.shopping.domain.repository.ProductRepository + +@Module +@InstallIn(ViewModelComponent::class) +abstract class ProductRepositoryModule { + @Binds + @ViewModelScoped + abstract fun bindProductRepository(impl: ProductRepositoryImpl): ProductRepository +} diff --git a/app/src/main/java/woowacourse/shopping/ui/MainActivity.kt b/app/src/main/java/woowacourse/shopping/ui/MainActivity.kt index c0a90a840..2e80b9de0 100644 --- a/app/src/main/java/woowacourse/shopping/ui/MainActivity.kt +++ b/app/src/main/java/woowacourse/shopping/ui/MainActivity.kt @@ -5,17 +5,19 @@ import android.os.Bundle import android.view.Menu import android.widget.Toast import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import dagger.hilt.android.AndroidEntryPoint import woowacourse.shopping.R import woowacourse.shopping.databinding.ActivityMainBinding -import woowacourse.shopping.di.injectViewModel import woowacourse.shopping.ui.cart.CartActivity +@AndroidEntryPoint class MainActivity : AppCompatActivity() { private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } - private val viewModel by injectViewModel() + private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/woowacourse/shopping/ui/MainViewModel.kt b/app/src/main/java/woowacourse/shopping/ui/MainViewModel.kt index a87e70e4b..803522a4c 100644 --- a/app/src/main/java/woowacourse/shopping/ui/MainViewModel.kt +++ b/app/src/main/java/woowacourse/shopping/ui/MainViewModel.kt @@ -4,43 +4,43 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import woowacourse.shopping.annotation.Inject -import woowacourse.shopping.di.annotation.RoomDB import woowacourse.shopping.domain.model.CartProduct import woowacourse.shopping.domain.model.Product import woowacourse.shopping.domain.repository.CartRepository import woowacourse.shopping.domain.repository.ProductRepository +import woowacourse.shopping.hilt.HiltRoomDB +import javax.inject.Inject -class MainViewModel : ViewModel() { +@HiltViewModel +class MainViewModel @Inject - private lateinit var productRepository: ProductRepository - - @Inject - @RoomDB - private lateinit var cartRepository: CartRepository - - private val _products: MutableLiveData> = MutableLiveData(emptyList()) - val products: LiveData> get() = _products - - private val _onProductAdded: MutableLiveData = MutableLiveData(false) - val onProductAdded: LiveData get() = _onProductAdded + constructor( + private val productRepository: ProductRepository, + @HiltRoomDB private val cartRepository: CartRepository, + ) : ViewModel() { + private val _products: MutableLiveData> = MutableLiveData(emptyList()) + val products: LiveData> get() = _products + + private val _onProductAdded: MutableLiveData = MutableLiveData(false) + val onProductAdded: LiveData get() = _onProductAdded + + fun addCartProduct(product: Product) { + viewModelScope.launch { + cartRepository.addCartProduct(product.toCartProduct()) + _onProductAdded.value = true + } + } - fun addCartProduct(product: Product) { - viewModelScope.launch { - cartRepository.addCartProduct(product.toCartProduct()) - _onProductAdded.value = true + fun getAllProducts() { + _products.value = productRepository.getAllProducts() } - } - fun getAllProducts() { - _products.value = productRepository.getAllProducts() + private fun Product.toCartProduct(): CartProduct = + CartProduct( + name = name, + price = price, + imageUrl = imageUrl, + ) } - - private fun Product.toCartProduct(): CartProduct = - CartProduct( - name = name, - price = price, - imageUrl = imageUrl, - ) -} diff --git a/app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt b/app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt index aec40b6ae..613fc73bc 100644 --- a/app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt +++ b/app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt @@ -3,20 +3,22 @@ package woowacourse.shopping.ui.cart import android.os.Bundle import android.widget.Toast import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import dagger.hilt.android.AndroidEntryPoint import woowacourse.shopping.R -import woowacourse.shopping.annotation.Inject import woowacourse.shopping.databinding.ActivityCartBinding -import woowacourse.shopping.di.DIActivity -import woowacourse.shopping.di.injectViewModel +import javax.inject.Inject -class CartActivity : DIActivity() { +@AndroidEntryPoint +class CartActivity : AppCompatActivity() { private val binding by lazy { ActivityCartBinding.inflate(layoutInflater) } - private val viewModel by injectViewModel() + private val viewModel: CartViewModel by viewModels() @Inject - private lateinit var dateFormatter: DateFormatter + lateinit var dateFormatter: DateFormatter private val adapter by lazy { CartProductAdapter( onClickDelete = viewModel::deleteCartProduct, diff --git a/app/src/main/java/woowacourse/shopping/ui/cart/CartViewModel.kt b/app/src/main/java/woowacourse/shopping/ui/cart/CartViewModel.kt index a3068150c..30e68a04f 100644 --- a/app/src/main/java/woowacourse/shopping/ui/cart/CartViewModel.kt +++ b/app/src/main/java/woowacourse/shopping/ui/cart/CartViewModel.kt @@ -4,32 +4,37 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import woowacourse.shopping.di.annotation.RoomDB import woowacourse.shopping.domain.model.CartProduct import woowacourse.shopping.domain.repository.CartRepository +import woowacourse.shopping.hilt.HiltRoomDB +import javax.inject.Inject -class CartViewModel( - @RoomDB private val cartRepository: CartRepository, -) : ViewModel() { - private val _cartProducts: MutableLiveData> = - MutableLiveData(emptyList()) - val cartProducts: LiveData> get() = _cartProducts +@HiltViewModel +class CartViewModel + @Inject + constructor( + @HiltRoomDB private val cartRepository: CartRepository, + ) : ViewModel() { + private val _cartProducts: MutableLiveData> = + MutableLiveData(emptyList()) + val cartProducts: LiveData> get() = _cartProducts - private val _onCartProductDeleted: MutableLiveData = MutableLiveData(false) - val onCartProductDeleted: LiveData get() = _onCartProductDeleted + private val _onCartProductDeleted: MutableLiveData = MutableLiveData(false) + val onCartProductDeleted: LiveData get() = _onCartProductDeleted - fun getAllCartProducts() { - viewModelScope.launch { - _cartProducts.value = cartRepository.getAllCartProducts() + fun getAllCartProducts() { + viewModelScope.launch { + _cartProducts.value = cartRepository.getAllCartProducts() + } } - } - fun deleteCartProduct(id: Long) { - viewModelScope.launch { - cartRepository.deleteCartProduct(id) - _onCartProductDeleted.value = true - _cartProducts.value = cartRepository.getAllCartProducts() + fun deleteCartProduct(id: Long) { + viewModelScope.launch { + cartRepository.deleteCartProduct(id) + _onCartProductDeleted.value = true + _cartProducts.value = cartRepository.getAllCartProducts() + } } } -} diff --git a/app/src/main/java/woowacourse/shopping/ui/cart/DateFormatter.kt b/app/src/main/java/woowacourse/shopping/ui/cart/DateFormatter.kt index 5117e8adf..c30ae04a1 100644 --- a/app/src/main/java/woowacourse/shopping/ui/cart/DateFormatter.kt +++ b/app/src/main/java/woowacourse/shopping/ui/cart/DateFormatter.kt @@ -1,19 +1,25 @@ package woowacourse.shopping.ui.cart import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.scopes.ActivityRetainedScoped import woowacourse.shopping.R import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import javax.inject.Inject -class DateFormatter(context: Context) { - private val formatter = - SimpleDateFormat( - context.getString(R.string.date_format), - Locale.KOREA, - ) +@ActivityRetainedScoped +class DateFormatter + @Inject + constructor( + @ApplicationContext context: Context, + ) { + private val formatter = + SimpleDateFormat( + context.getString(R.string.date_format), + Locale.KOREA, + ) - fun formatDate(timestamp: Long): String { - return formatter.format(Date(timestamp)) + fun formatDate(timestamp: Long): String = formatter.format(Date(timestamp)) } -} diff --git a/build.gradle.kts b/build.gradle.kts index 5830a401e..b933caff0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,8 +4,14 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.ktlint) apply false alias(libs.plugins.jetbrains.kotlin.jvm) apply false + alias(libs.plugins.hilt.android) apply false } allprojects { - apply(plugin = rootProject.libs.plugins.ktlint.get().pluginId) + apply( + plugin = + rootProject.libs.plugins.ktlint + .get() + .pluginId, + ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c427a78f1..e50e78e4d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,8 @@ junit-vintage-engine = "5.9.3" kotest = "5.8.1" mockk = "1.13.11" jetbrains-kotlin-jvm = "1.9.25" +hilt = "2.48" +androidx-hilt = "1.0.0" [libraries] androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "androidx-activity-ktx" } @@ -51,6 +53,9 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines-test" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } truth = { module = "com.google.truth:truth", version.ref = "truth" } +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } +hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } +androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "androidx-hilt" } [plugins] android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } @@ -58,3 +63,4 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } kotlin-kapt = { id = "kotlin-kapt" } jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrains-kotlin-jvm" } +hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }