介紹完ViewModel & LiveData 以及databinding以後
接下來可以開始構築專案了
今天的進度會從branch - databinding-2接續往下改
頁面的功能是輸入兩個數字 加總後顯示
xml布局
布局只貼重點 全貼太長了
activity_stage01.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
...
<data>
<variable
name="viewModel"
type="com.ithome11.jetpackmvvmdemo.main.s01.Stage01ViewModel" />
<import type="android.view.View" />
</data>
...
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_left"
android:text="@={viewModel.left}" />
...
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_right"
android:text="@={viewModel.right}"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/bt_clear"
android:onClick="@{_ -> viewModel.clear()}"
/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/bt_plus"
android:onClick="@{_ -> viewModel.plus()}"
/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_result"
android:text="@{@string/stage01_result_format(viewModel.left,
viewModel.right, viewModel.result)}"
android:visibility="@{viewModel.visibleResult ? View.VISIBLE : View.INVISIBLE}"
/>
...
</layout>
strings.xml
<resources>
...
<string name="stage01_result_format">%s + %s = %s</string>
</resources>
其中 <import type="android.view.View" / > 為 android:visibility用的
如果不想用這種寫法的話也可以換成下敘寫法
<data>
<variable
name="viewModel"
type="...."/>
</data>
<View
android:layout_width="10dp"
android:layout_height="10dp"
android:visibility="@{viewModel.saleVisibility, default=gone}"/>
viewModel
@BindingAdapter("android:visibility")
fun setVisibility(view: View, value : Boolean) {
view.visibility = if (value) View.VISIBLE else View.GONE
}
ViewModel代碼
Stage01ViewModel.kt
class Stage01ViewModel : ViewModel() {
val left = MutableLiveData<String>("")
val right = MutableLiveData<String>("")
val result = MutableLiveData<String>("")
//tv_result根據visibleResult決定是否顯示
val visibleResult = MutableLiveData<Boolean>().apply { value = false }
fun plus() {
//如果輸入的數字不能轉為int或null 則tv_result隱藏 並且不繼續往下執行
val leftNum = left.value.toString().toIntOrNull() ?: run { visibleResult.value = false; return }
val rightNum = right.value.toString().toIntOrNull() ?: run { visibleResult.value = false; return }
result.value = (leftNum + rightNum).toString()
visibleResult.value = true
}
//重設資料
fun clear() {
left.value = ""
right.value = ""
result.value = ""
visibleResult.value = false
}
}
如果只是要給預設值 那
MutableLiveData("") 與 MutableLiveData().apply { value = false }
兩種寫法都可以
接著去更新activity
Stage01Activity.kt
class Stage01Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_stage01)
val viewModel = ViewModelProviders.of(this).get(Stage01ViewModel::class.java)
DataBindingUtil.setContentView<ActivityStage01Binding>(this,
R.layout.activity_stage01).let {
it.setLifecycleOwner(this)
it.viewModel = viewModel
}
val setInvisibleResult = Observer<String?> { viewModel.visibleResult.value = false }
viewModel.left.observe(this, setInvisibleResult)
viewModel.right.observe(this, setInvisibleResult)
}
這裡針對left 跟right做觀察
當數據變化時 隱藏result
僅有點選plus時會更新與顯示數據
接著今天專案的基本功能就完成了
可以運行來試看看
接著要來新增測試案例 可以手動新增 也可以反白fun名稱後點快捷建立
Stage01ViewModelTest.kt (androidTest)
import androidx.test.ext.junit.runners.AndroidJUnit4
@RunWith(AndroidJUnit4::class)
class Stage01ViewModelTest {
@get:Rule
val rule = ActivityTestRule<Stage01Activity>(Stage01Activity::class.java)
private fun plus() {
Espresso.onView(ViewMatchers.withId(R.id.et_left)).perform(ViewActions.typeText("1"))
Espresso.onView(ViewMatchers.withId(R.id.et_right)).perform(ViewActions.typeText("1"))
Espresso.onView(ViewMatchers.withId(R.id.bt_plus)).perform(ViewActions.click())
}
@Test
fun result_should_be_invisible_when_input() {
// given
plus() // result view is visible
Espresso.onView(ViewMatchers.withId(R.id.tv_result)).check(ViewAssertionsEx.isVisibility())
// when
Espresso.onView(ViewMatchers.withId(R.id.et_left)).perform(ViewActions.typeText("22"))
// then Espresso.onView(ViewMatchers.withId(R.id.tv_result)).check(ViewAssertionsEx.isInvisible())
}
}
object ViewAssertionsEx {
@JvmStatic
fun isInvisible() = ViewAssertion { view, noViewFoundException ->
if (noViewFoundException != null ) throw noViewFoundException
Assert.assertEquals(View.INVISIBLE, view.visibility)
}
@JvmStatic
fun isVisibility() = ViewAssertion { view, noViewFoundException ->
if (noViewFoundException != null ) throw noViewFoundException
Assert.assertEquals(View.VISIBLE, view.visibility)
}
}
build.gradle (module:app)
dependencies {
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
}
這個測試案例是left輸入1 right輸入1 之後點選plus
接著驗證result是否有顯示
接著再點選left輸入別的數據後
驗證result是否有正常隱藏
突然想到忘記先介紹Espresso... 之後再找篇幅來補充好了
今天的代碼在此
https://github.com/mars1120/jetpackMvvmDemo/tree/mvvm-stage01