Skip to content

Commit

Permalink
[부나] 1단계 자동 DI 미션 제출합니다 (#2)
Browse files Browse the repository at this point in the history
* refactor: 각 Repository 구현체마다 인터페이스를 구현하도록 변경

* refactor: MainViewModel 팩토리 구현

* refactor: Main 관련 클래스 패키지 분리

* refactor: 코드 포맷팅

* refactor: CartViewModel 팩토리 구현

* chore: mockk 1.13.5 라이브러리 추가

* test: MainViewModel 테스트 코드 작성

* feat: 자동 DI 구현

* feat: @Inject Annotation 포함을 검증하지 않도록 변경

* chore: 불필요한 라이브러리 제거

* refactor: ViewModelFactory 유틸 클래스를 di 패키지로 이동

* refactor: @Inject 관련 코드 제거

* refactor: 각 ViewModel에 constructor 키워드 제거

* refactor: @mainthread 애노테이션 제거
  • Loading branch information
tmdgh1592 authored Sep 4, 2023
1 parent 4e25784 commit e4c9c8d
Show file tree
Hide file tree
Showing 30 changed files with 322 additions and 78 deletions.
14 changes: 12 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
kotlin("kapt")
}

android {
Expand All @@ -23,7 +23,7 @@ android {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
"proguard-rules.pro",
)
}
}
Expand Down Expand Up @@ -54,6 +54,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
testImplementation("com.google.truth:truth:1.1.3")
testImplementation("androidx.arch.core:core-testing:2.2.0")
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// Coroutines
Expand All @@ -68,4 +69,13 @@ dependencies {
implementation("com.github.bumptech.glide:glide:4.15.1")
// Robolectric
testImplementation("org.robolectric:robolectric:4.9")
// Mockk
testImplementation("io.mockk:mockk:1.13.5")
androidTestImplementation("io.mockk:mockk-android:1.13.5")
// Reflection
implementation("org.jetbrains.kotlin", "kotlin-reflect", "1.8.21")
}

kapt {
correctErrorTypes = true
}
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".ShoppingApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -17,7 +18,7 @@
android:name=".ui.cart.CartActivity"
android:exported="false" />
<activity
android:name=".ui.MainActivity"
android:name=".ui.main.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/woowacourse/shopping/ShoppingApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package woowacourse.shopping

import android.app.Application
import woowacourse.shopping.data.repository.DefaultCartRepository
import woowacourse.shopping.data.repository.DefaultProductRepository
import woowacourse.shopping.di.injector.modules
import woowacourse.shopping.repository.CartRepository
import woowacourse.shopping.repository.ProductRepository

class ShoppingApplication : Application() {
override fun onCreate() {
super.onCreate()

modules {
inject<ProductRepository>(DefaultProductRepository())
inject<CartRepository>(DefaultCartRepository())
}
}
}
20 changes: 0 additions & 20 deletions app/src/main/java/woowacourse/shopping/data/CartRepository.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package woowacourse.shopping.data
import androidx.room.Database
import androidx.room.RoomDatabase


@Database(entities = [CartProductEntity::class], version = 1, exportSchema = false)
abstract class ShoppingDatabase : RoomDatabase() {
abstract fun cartProductDao(): CartProductDao
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package woowacourse.shopping.data.repository

import woowacourse.shopping.model.Product
import woowacourse.shopping.repository.CartRepository

class DefaultCartRepository : CartRepository {
private val cartProducts: MutableList<Product> = mutableListOf()

override fun addCartProduct(product: Product) {
cartProducts.add(product)
}

override fun getAllCartProducts(): List<Product> = cartProducts.toList()

override fun deleteCartProduct(id: Int) {
cartProducts.removeAt(id)
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
package woowacourse.shopping.data
package woowacourse.shopping.data.repository

import woowacourse.shopping.model.Product
import woowacourse.shopping.repository.ProductRepository

class ProductRepository {

class DefaultProductRepository : ProductRepository {
private val products: List<Product> = listOf(
Product(
name = "우테코 과자",
price = 10_000,
imageUrl = "https://cdn-mart.baemin.com/sellergoods/api/main/df6d76fb-925b-40f8-9d1c-f0920c3c697a.jpg?h=700&w=700"
imageUrl = "https://cdn-mart.baemin.com/sellergoods/api/main/df6d76fb-925b-40f8-9d1c-f0920c3c697a.jpg?h=700&w=700",
),
Product(
name = "우테코 쥬스",
price = 8_000,
imageUrl = "https://cdn-mart.baemin.com/sellergoods/main/52dca718-31c5-4f80-bafa-7e300d8c876a.jpg?h=700&w=700"
imageUrl = "https://cdn-mart.baemin.com/sellergoods/main/52dca718-31c5-4f80-bafa-7e300d8c876a.jpg?h=700&w=700",
),
Product(
name = "우테코 아이스크림",
price = 20_000,
imageUrl = "https://cdn-mart.baemin.com/sellergoods/main/e703c53e-5d01-4b20-bd33-85b5e778e73f.jpg?h=700&w=700"
imageUrl = "https://cdn-mart.baemin.com/sellergoods/main/e703c53e-5d01-4b20-bd33-85b5e778e73f.jpg?h=700&w=700",
),
)

fun getAllProducts(): List<Product> {
return products
}
override fun getAllProducts(): List<Product> = products
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package woowacourse.shopping.di.injector

import woowacourse.shopping.di.util.validateHasPrimaryConstructor
import kotlin.reflect.KParameter
import kotlin.reflect.javaType

object ClassInjector {
val dependencies = mutableMapOf<Class<*>, Any>()

inline fun <reified T : Any> inject(instance: T) {
dependencies[T::class.java] = instance
}

inline fun <reified T : Any> inject(): T {
val primaryConstructor = validateHasPrimaryConstructor<T>()
val parameterValues = getParameterValues(primaryConstructor.parameters)

return primaryConstructor.call(*parameterValues.toTypedArray())
}

@OptIn(ExperimentalStdlibApi::class)
fun getParameterValues(parameters: List<KParameter>): MutableList<Any> {
val parameterTypes = parameters.map { it.type }
val parameterValues = mutableListOf<Any>()

parameterTypes.forEach { paramType ->
val parameterType = paramType.javaType
val parameterValue = dependencies[parameterType]

requireNotNull(parameterValue) { "[ERROR] 주입할 의존성이 존재하지 않습니다." }
parameterValues.add(parameterValue)
}

return parameterValues
}
}

class ClassInjectorDsl {
inline fun <reified T : Any> inject(instance: T) {
ClassInjector.inject(instance)
}
}

fun modules(block: ClassInjectorDsl.() -> Unit) {
ClassInjectorDsl().apply(block)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package woowacourse.shopping.di.lazy

import androidx.activity.ComponentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelLazy
import woowacourse.shopping.di.injector.ClassInjector
import woowacourse.shopping.di.util.viewModelFactory

inline fun <reified VM : ViewModel> ComponentActivity.viewModel(): Lazy<VM> {
return ViewModelLazy(
VM::class,
{ viewModelStore },
{ viewModelFactory { createViewModel<VM>() } },
)
}

inline fun <reified VM : ViewModel> createViewModel(): VM {
return ClassInjector.inject()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package woowacourse.shopping.di.util

import kotlin.reflect.KFunction
import kotlin.reflect.full.primaryConstructor

inline fun <reified T : Any> validateHasPrimaryConstructor(): KFunction<T> {
val primaryConstructor = T::class.primaryConstructor
return requireNotNull(primaryConstructor) { "[ERROR] 주생성자가 존재하지 않습니다." }
}
13 changes: 13 additions & 0 deletions app/src/main/java/woowacourse/shopping/di/util/ViewModelFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package woowacourse.shopping.di.util

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory

inline fun <reified VM : ViewModel> viewModelFactory(crossinline create: () -> VM): ViewModelProvider.Factory =
viewModelFactory {
initializer {
create()
}
}
12 changes: 4 additions & 8 deletions app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@ package woowacourse.shopping.ui.cart
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import woowacourse.shopping.R
import woowacourse.shopping.databinding.ActivityCartBinding
import woowacourse.shopping.di.lazy.viewModel

class CartActivity : AppCompatActivity() {

private val binding by lazy { ActivityCartBinding.inflate(layoutInflater) }

private val viewModel by lazy {
ViewModelProvider(this)[CartViewModel::class.java]
}
private val viewModel: CartViewModel by viewModel()

private lateinit var dateFormatter: DateFormatter

Expand Down Expand Up @@ -52,15 +48,15 @@ class CartActivity : AppCompatActivity() {
}

private fun setupCartProductData() {
viewModel.getAllCartProducts()
viewModel.fetchAllCartProducts()
}

private fun setupCartProductList() {
viewModel.cartProducts.observe(this) {
val adapter = CartProductAdapter(
items = it,
dateFormatter = dateFormatter,
onClickDelete = viewModel::deleteCartProduct
onClickDelete = viewModel::deleteCartProduct,
)
binding.rvCartProducts.adapter = adapter
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package woowacourse.shopping.ui.cart
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import woowacourse.shopping.data.CartRepository
import woowacourse.shopping.model.Product
import woowacourse.shopping.repository.CartRepository

class CartViewModel(
private val cartRepository: CartRepository,
Expand All @@ -17,7 +17,7 @@ class CartViewModel(
private val _onCartProductDeleted: MutableLiveData<Boolean> = MutableLiveData(false)
val onCartProductDeleted: LiveData<Boolean> get() = _onCartProductDeleted

fun getAllCartProducts() {
fun fetchAllCartProducts() {
_cartProducts.value = cartRepository.getAllCartProducts()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import java.util.Locale
class DateFormatter(context: Context) {

private val formatter = SimpleDateFormat(
context.getString(R.string.date_format), Locale.KOREA
context.getString(R.string.date_format),
Locale.KOREA,
)

fun formatDate(timestamp: Long): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package woowacourse.shopping.ui.bindingadapter
package woowacourse.shopping.ui.common.bindingadapter

import android.widget.ImageView
import androidx.databinding.BindingAdapter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package woowacourse.shopping.ui
package woowacourse.shopping.ui.main

import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import woowacourse.shopping.R
import woowacourse.shopping.databinding.ActivityMainBinding
import woowacourse.shopping.di.lazy.viewModel
import woowacourse.shopping.ui.cart.CartActivity

class MainActivity : AppCompatActivity() {

private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

private val viewModel by lazy {
ViewModelProvider(this)[MainViewModel::class.java]
}
private val viewModel: MainViewModel by viewModel()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -27,7 +23,6 @@ class MainActivity : AppCompatActivity() {
setupView()
}


override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.cart_menu, menu)
menu?.findItem(R.id.cart)?.actionView?.let { view ->
Expand All @@ -51,14 +46,14 @@ class MainActivity : AppCompatActivity() {
}

private fun setupProductData() {
viewModel.getAllProducts()
viewModel.fetchAllProducts()
}

private fun setupProductList() {
viewModel.products.observe(this) {
val adapter = ProductAdapter(
items = it,
onClickProduct = viewModel::addCartProduct
onClickProduct = viewModel::addCartProduct,
)
binding.rvProducts.adapter = adapter
}
Expand Down
Loading

0 comments on commit e4c9c8d

Please sign in to comment.