# Unit Test - 使用hilt測試3 - setUp Architectural Skeleton --- ## 這篇紀錄建立app的主要架構 * layout * fragment * viewModel ## 開始吧 ![](https://i.imgur.com/AGhTseO.png) ### 創建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`