2018年6月20日水曜日

LiveData を UnitTest でテストする

デザートの文字列を保持して、追加・削除されたタイミングで保持数を LiveData で通知する DessertsHolder を UnitTest でテストしてみましょう。
  1. class DessertsHolder {  
  2.   
  3.     private val counter = MutableLiveData<Int>()  
  4.     private val list = mutableListOf<String>()  
  5.   
  6.     init {  
  7.         counter.value = 0  
  8.     }  
  9.   
  10.     fun getCounter(): LiveData<Int> = counter  
  11.   
  12.     fun add(item: String) {  
  13.         list.add(item)  
  14.         counter.value = list.size  
  15.     }  
  16.   
  17.     fun remove(item: String) {  
  18.         list.remove(item)  
  19.         counter.value = list.size  
  20.     }  
  21.   
  22.     fun clear() {  
  23.         list.clear()  
  24.         counter.value = 0  
  25.     }  
  26. }  
特に何もせず次のような普通の UnitTest を書くと、Looper が mock されていないというエラー(RuntimeException: Method getMainLooper in android.os.Looper not mocked. )がでます。
  1. class DessertsHolderTest {  
  2.   
  3.     @Test  
  4.     fun test() {  
  5.         val holder = DessertsHolder()  
  6.   
  7.         holder.add("Donuts")  
  8.   
  9.         assertThat(holder.getCounter().value).isEqualTo(1)  
  10.     }  
  11. }  


そこで、まず AAC の core-testing ライブラリを追加します。
  1. dependencies {  
  2.     def lifecycle_version = "1.1.1"  
  3.     testImplementation "android.arch.core:core-testing:$lifecycle_version"  
  4. }  
AndroidX
  1. dependencies {  
  2.     def lifecycle_version = "2.0.0"  
  3.     testImplementation "androidx.arch.core:core-testing:$lifecycle_version"  
  4. }  

そして @get:Rule で rule に InstantTaskExecutorRule を指定します。このとき get: をつけないと rule が public ではないという ValidationError (ValidationError: The @Rule 'rule' must be public.)が起こるので注意しましょう。
  1. class DessertsHolderTest {  
  2.   
  3.     @get:Rule  
  4.     val rule: TestRule = InstantTaskExecutorRule()  
  5.   
  6.     @Test  
  7.     fun test() {  
  8.         ...  
  9.     }  
  10. }  



2018年6月9日土曜日

ViewOutlineProvider を使う

API Level 21 に追加された ViewOutlineProvider では shadow casting と outline clipping に利用する Outline を指定できます。

ViewOutlineProvider の getOutline() の引数で渡される Outline に Rect, RoundRect, Oval または ConvexPath を指定します。

View が持つ Drawable が invalidate されたり、View のサイズが変わったり、View の invalidateOutline() が呼ばれると getOutline() が呼ばれます。
  1. private val clipOutlineProvider = object : ViewOutlineProvider() {  
  2.   
  3.     override fun getOutline(view: View, outline: Outline) {  
  4.         val margin = min(view.width, view.height) / 10  
  5.         outline.setOval(  
  6.                 0,  
  7.                 0,  
  8.                 view.width,  
  9.                 view.height  
  10.         )  
  11.     }  
  12. }  
View の setOutlineProvider() で ViewOutlineProvider を指定します。outline clipping を有効にするには setClipToOutline() で true を指定します。

outline clipping は現状 RoundRect か Circle(Oval で縦横のサイズが同じ)のときだけ効きます。
clip しても View のサイズには影響しないためクリック領域などはそのままです。
  1. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
  2.     super.onViewCreated(view, savedInstanceState)  
  3.   
  4.     clippedView.outlineProvider = clipOutlineProvider  
  5.     clippedView.clipToOutline = true  
  6. }  
左 : clipToOutline = false, 右 : clipToOutline = true





2018年6月7日木曜日

ConstraintLayout で float で指定する属性

ConstraintLayout 1.1.0 の属性のうち、TypedArray から getFloat() で取得されている属性は
  • app:layout_constraintCircleAngle
  • app:layout_constraintGuide_percent
  • app:layout_constraintHorizontal_bias
  • app:layout_constraintVertical_bias
  • app:layout_constraintHeight_percent
  • app:layout_constraintWidth_percent
  • app:layout_constraintHorizontal_weight
  • app:layout_constraintVertical_weight
です。

これらの属性は
  1. app:layout_constraintGuide_percent="0.3"  
のように値を直接指定するときは float 形式で書きます。

float 値を resource で定義するには、
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <item name="guideline_percent" type="dimen" format="float">0.3</item>  
  4. </resources>  
のように item タグ を使って float format、dimen type で定義します。
  1. app:layout_constraintGuide_percent="@dimen/guideline_percent"