-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[둘리] 2, 3단계 자동 DI 미션 제출합니다 #28
Changes from 45 commits
168589f
87d3c2a
657518e
1bdceaf
5e94a5e
7ab7e4e
04ac319
c75db5c
f483bbb
d2744a7
c6393e6
8b64b40
ffa5c7b
2254b53
8d57300
e2b9366
62465b1
559b93f
f6c41a8
c0ee682
38725ee
55308d0
a80f744
15b4fe6
37d31ed
7f5c22d
368e9c9
83f960e
6f0dcf2
d66108d
5abddb2
b9ebfdf
0b4f3a1
2498638
9eecad2
8a46ecd
8fca93c
a636e8c
7184501
7443924
353d48b
bbb76a5
a89cc44
acaa5ef
e6ee91f
1f27660
d53b175
885ab4d
fa81116
77bc755
ad360bb
55bc13c
cc6cc2e
6c59beb
a7baa40
47600c6
a91d38b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,37 @@ | ||
# android-di | ||
# 0.5단계 | ||
# 2단계 | ||
## 기능 요구 사항 | ||
### 생성자 주입 - 수동 | ||
### 필드 주입 | ||
- [x] ViewModel 내 필드 주입을 구현한다. | ||
|
||
### Annotation | ||
다음 문제점을 해결한다. | ||
- [x] 의존성 주입이 필요한 필드와 그렇지 않은 필드를 구분할 수 없다. | ||
- [x] Annotation을 붙여서 필요한 요소에만 의존성을 주입한다. | ||
- [x] 내가 만든 의존성 라이브러리가 제대로 작동하는지 테스트 코드를 작성한다. | ||
### Recursive DI | ||
- [x] CartRepository가 다음과 같이 DAO 객체를 참조하도록 변경한다. | ||
CartProductEntity에는 createdAt 프로퍼티가 있어서 언제 장바구니에 상품이 담겼는지를 알 수 있다. | ||
- [x] CartProductViewHolder의 bind 함수에 다음 구문을 추가하여 뷰에서도 날짜 정보를 확인할 수 있도록 한다. | ||
|
||
## 선택 요구 사항 | ||
- [x] 현재는 장바구니 아이템 삭제 버튼을 누르면 RecyclerView의 position에 해당하는 상품이 지워진다. | ||
- [x] 상품의 position과 CartRepository::deleteCartProduct의 id가 동일한 값임을 보장할 수 없다는 문제를 해결한다. | ||
- [x] 뷰에서 CartProductEntity를 직접 참조하지 않는다. | ||
|
||
- [x] DB 없이 테스트하기 어렵다. | ||
- [x] DB 객체를 교체하기 위해 또다른 객체를 만들어 바꿔줘야 한다. 즉, ViewModel에 직접적인 변경사항이 발생한다. | ||
## 프로그래밍 요구 사항 | ||
사전에 주어진 테스트 코드가 모두 성공해야 한다. | ||
|
||
# 1단계 | ||
# 3단계 | ||
## 기능 요구 사항 | ||
### 생성자 주입 - 자동 | ||
### Qualifier | ||
다음 문제점을 해결한다. | ||
- [x] 하나의 인터페이스의 여러 구현체가 DI 컨테이너에 등록된 경우, 어떤 의존성을 가져와야 할지 알 수 없다. | ||
- [x] 상황에 따라 개발자가 Room DB 의존성을 주입받을지, In-Memory 의존성을 주입받을지 선택할 수 있다. | ||
|
||
- [x] ViewModel에서 참조하는 Repository가 정상적으로 주입되지 않는다. | ||
- [x] Repository를 참조하는 다른 객체가 생기면 주입 코드를 매번 만들어줘야 한다. | ||
- [x] ViewModel에 수동으로 주입되고 있는 의존성들을 자동으로 주입되도록 바꿔본다. | ||
- [x] 특정 ViewModel에서만이 아닌, 범용적으로 활용될 수 있는 자동 주입 로직을 작성한다. (MainViewModel, CartViewModel 모두 하나의 로직만 참조한다) | ||
- [x] 100개의 ViewModel이 생긴다고 가정했을 때, 자동 주입 로직 100개가 생기는 것이 아니다. 하나의 자동 주입 로직을 재사용할 수 있어야 한다. | ||
- [x] 장바구니에 접근할 때마다 매번 CartRepository 인스턴스를 새로 만들고 있다. | ||
- [x] 여러 번 인스턴스화할 필요 없는 객체는 최초 한 번만 인스턴스화한다. (이 단계에서는 너무 깊게 생각하지 말고 싱글 오브젝트로 구현해도 된다.) | ||
### 모듈 분리 | ||
- [x] 내가 만든 DI 라이브러리를 모듈로 분리한다. | ||
|
||
## 선택 요구 사항 | ||
- [ ] TDD로 DI 구현 | ||
- [x] Robolectric으로 기능 테스트 | ||
- [ ] ViewModel 테스트 | ||
- [ ] 모든 도메인 로직, Repository 단위 테스트 | ||
|
||
## 프로그래밍 요구 사항 | ||
사전에 주어진 테스트 코드가 모두 성공해야 한다. | ||
Annotation은 이 단계에서 활용하지 않는다. | ||
- [ ] DSL을 활용한다. | ||
- [ ] 내가 만든 DI 라이브러리를 배포하고 적용한다. |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package woowacourse.shopping.data | ||
|
||
import woowacourse.shopping.data.mapper.toCartProduct | ||
import woowacourse.shopping.data.mapper.toEntity | ||
import woowacourse.shopping.model.CartProduct | ||
import woowacourse.shopping.model.Product | ||
|
||
// TODO: Step2 - CartProductDao를 참조하도록 변경 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 필요없는 주석은 제거해주세요! |
||
class InDiskCartRepository( | ||
private val dao: CartProductDao, | ||
) : CartRepository { | ||
override suspend fun addCartProduct(product: Product) { | ||
dao.insert(product.toEntity()) | ||
} | ||
|
||
override suspend fun getAllCartProducts(): List<CartProduct> { | ||
return dao.getAll().map { it.toCartProduct() } | ||
} | ||
|
||
override suspend fun deleteCartProduct(id: Long) { | ||
dao.delete(id) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package woowacourse.shopping.data | ||
|
||
import woowacourse.shopping.model.CartProduct | ||
import woowacourse.shopping.model.Product | ||
|
||
// TODO: Step2 - CartProductDao를 참조하도록 변경 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 필요없는 주석이 InMemory 에도 따라왔네요! |
||
class InMemoryCartRepository : CartRepository { | ||
private val products = mutableListOf<CartProduct>() | ||
override suspend fun addCartProduct(product: Product) { | ||
products.add( | ||
CartProduct( | ||
products.size.toLong(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. size 가 장바구니에 담긴 아이템 id 의 고유함을 보장할 수 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 갖고 있는 products 중에 최대 id + 1, products가 빈 리스트라면 DEFAULT_ID를 넣도록 계산법을 수정했습니다! |
||
product.name, | ||
product.price, | ||
product.imageUrl, | ||
System.currentTimeMillis(), | ||
), | ||
) | ||
} | ||
|
||
override suspend fun getAllCartProducts(): List<CartProduct> { | ||
return products.toList() | ||
} | ||
|
||
override suspend fun deleteCartProduct(id: Long) { | ||
products.removeIf { it.id == id } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package woowacourse.shopping.data | ||
|
||
import io.hyemdooly.di.annotation.Inject | ||
import woowacourse.shopping.model.Product | ||
|
||
class InMemoryProductRepository : ProductRepository { | ||
@Inject | ||
private val products: List<Product> = emptyList() | ||
|
||
override fun getAllProducts(): List<Product> { | ||
return products | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package woowacourse.shopping.data.mapper | ||
|
||
import woowacourse.shopping.data.CartProductEntity | ||
import woowacourse.shopping.model.CartProduct | ||
|
||
fun CartProductEntity.toCartProduct(): CartProduct { | ||
return CartProduct(id, name, price, imageUrl, createdAt) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수에 인자가 많을 때는 파라미터 명을 나타내는 것이 바람직하다고 생각합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추가했습니다~ |
||
} |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,14 @@ | ||
package woowacourse.shopping.ui | ||
|
||
import android.app.Application | ||
import woowacourse.shopping.data.CartRepository | ||
import woowacourse.shopping.data.CartRepositoryImpl | ||
import woowacourse.shopping.data.ProductRepository | ||
import woowacourse.shopping.data.ProductRepositoryImpl | ||
import woowacourse.shopping.di.Container | ||
import androidx.room.Room | ||
import io.hyemdooly.di.Container | ||
import io.hyemdooly.di.Injector | ||
import woowacourse.shopping.data.InDiskCartRepository | ||
import woowacourse.shopping.data.InMemoryCartRepository | ||
import woowacourse.shopping.data.InMemoryProductRepository | ||
import woowacourse.shopping.data.ShoppingDatabase | ||
import woowacourse.shopping.model.Product | ||
|
||
class ShoppingApplication : Application() { | ||
override fun onCreate() { | ||
|
@@ -14,7 +17,35 @@ class ShoppingApplication : Application() { | |
} | ||
|
||
private fun inject() { | ||
Container.addInstance(CartRepository::class, CartRepositoryImpl()) | ||
Container.addInstance(ProductRepository::class, ProductRepositoryImpl()) | ||
val inDiskDb = Room.databaseBuilder( | ||
applicationContext, | ||
ShoppingDatabase::class.java, | ||
ShoppingDatabase.name, | ||
).build() | ||
|
||
val products = 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", | ||
), | ||
Product( | ||
name = "우테코 쥬스", | ||
price = 8_000, | ||
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", | ||
), | ||
) | ||
|
||
Container.addInstance(products) | ||
Container.addInstance(inDiskDb.cartProductDao()) | ||
|
||
Container.addInstance(Injector.inject(InMemoryProductRepository::class)) | ||
Container.addInstance(Injector.inject(InDiskCartRepository::class)) | ||
Container.addInstance(Injector.inject(InMemoryCartRepository::class)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 구조에서는 주입해야하는 것들이 생길 때마다 Application 을 수정해야하네요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Module을 생성하여 묶어서 instances에 추가할 수 있도록 수정했습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 주입 순서가 바뀌면 주입에 실패하네요! recursive DI 가 안되고 있습니다ㅜ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 삭제해야하는 코드였는데 삭제하지 않았네요... 삭제했습니다 ㅎㅎ |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package woowacourse.shopping.ui | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.ViewModelProvider | ||
import androidx.lifecycle.viewmodel.CreationExtras | ||
import io.hyemdooly.di.Injector | ||
|
||
val viewModelFactory = object : ViewModelProvider.Factory { | ||
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T = | ||
Injector.inject(modelClass.kotlin) | ||
} | ||
Comment on lines
+8
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 라이브러리 사용자가 ViewModelFactory 의 주입을 정의하고 사용해야하네요. 자동으로 할 수 있지 않을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 고민을 해봤는데 DI는 안드로이드에서만 사용되는 개념이 아니라고 생각합니다. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
주석을 해제해야 하지 않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
둘리가 배포한 라이브러리 버전에는 원하는 기능이 포함되어 있지 않네요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
배포한걸 implementation 했으니 주석처리해도 된다고 생각했습니다!
배포한 라이브러리에 원하는 기능이 포함되어있지 않다니 확인해보겠습니다.ㅠㅠ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
세상에 도메인 모듈에 퍼블리싱이 되어있었네요.................. 감사합니다 제이슨!!