# Unit Test - 使用hilt測試3 - setUp Architectural Skeleton
---
## 這篇紀錄建立app的主要架構
* layout
* fragment
* viewModel
## 開始吧

### 創建fragment對應到各自的layout
```kotlin=
class ShoppingFragment: Fragment(R.layout.fragment_shopping) {}
class AddShoppingItemFragment: Fragment(R.layout.fragment_add_shopping_item) {}
class ImagePickFragment: Fragment(R.layout.fragment_image_pick) {}
```
### 建立ViewModel
* 這邊只用一個viewModel,其他fragment共享
* 用DI inject,注意這邊注入的是一個interface,
```kotlin=
@HiltViewModel
class ShoppingViewModel @Inject constructor(
private val repository: ShoppingRepository,
) : ViewModel() {
}
```
* 在DI Module提供方法
* 注入介面[官方寫法](https://developer.android.com/training/dependency-injection/hilt-android)
* 作者在這邊使用as做型態轉換
```kotlin=
@Singleton
@Provides
fun provideDefaultShoppingRepository(
dao: ShoppingDao,
api: PixabayAPI
) = DefaultShoppingRepository(dao,api) as ShoppingRepository //轉成介面
```
### event管理的類別
* 作者提供了一個類別,避免當liveData處理事件之後,若發生旋轉銀幕...等等的configration change event造成liveData會再次處理一次
* 透過一個布林值來控制
```kotlin=
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
```
### 補完viewModel
```kotlin=
@HiltViewModel
class ShoppingViewModel @Inject constructor(
private val repository: ShoppingRepository,
) : ViewModel() {
val shoppingItems = repository.observeAllShoppingItems()
val totalPrice = repository.observeTotalPrice()
private val _images = MutableLiveData<Event<Resource<ImageResponse>>>()
val image: LiveData<Event<Resource<ImageResponse>>> = _images
private val _curImageUrl = MutableLiveData<String>()
val curImageUrl: LiveData<String> = _curImageUrl
private val _insertShoppingItemStatus = MutableLiveData<Event<Resource<ShoppingItem>>>()
val insertShoppingItemStatus: LiveData<Event<Resource<ShoppingItem>>> =
_insertShoppingItemStatus
fun setCurImageUrl(url: String) {
_curImageUrl.postValue(url)
}
fun deleteShoppingItem(shoppingItem: ShoppingItem) = viewModelScope.launch {
repository.deleteShoppingItem(shoppingItem)
}
fun insertShoppingItemIntoDb(shoppingItem: ShoppingItem) = viewModelScope.launch {
repository.insertShoppingItem(shoppingItem)
}
fun insertShoppingItem(name: String, amountString: String, priceString: String) {
//要先寫測試,這邊暫不實作
}
fun searchForImage(imageQuery: String){
//要先寫測試,這邊暫不實作
}
}
```
### 把3個fragment 帶入相同的ViewModel
```kotlin=
class ShoppingFragment: Fragment(R.layout.fragment_shopping) {
lateinit var viewModel: ShoppingViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(ShoppingViewModel::class.java)
}
}
class AddShoppingItemFragment: Fragment(R.layout.fragment_add_shopping_item) {
lateinit var viewModel: ShoppingViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(ShoppingViewModel::class.java)
}
}
class ImagePickFragment: Fragment(R.layout.fragment_image_pick) {
lateinit var viewModel: ShoppingViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(ShoppingViewModel::class.java)
}
}
```
## 前置作業完成,下一篇就開始來做viewModel測試了
參考資料
[Philipp Lackner's channel](https://www.youtube.com/watch?v=x2WahC3N_Yw)
###### tags: `test` `Unit Test` `hilt` `kotlin`