날짜 형식을 지정(날짜 → 텍스트)하고 파싱(텍스트 → 날짜)하는 클래스다.
SimpleDateFormat("E MMM d", Locale.getDefault())
“E MMM d”
와 같은 패턴 문자열은 날짜 및 시간 형식의 표현이다. 2018년 1월 4일이면 “Wed, Jul 4”와 같이 된다.Locale
객체는 특정한 지리적, 정치적, 문화적 지역을 나타낸다. 지역의 규칙에 맞게 숫자나 날짜와 같은 정보 표시를 병경하는 데 사용한다. 날짜와 시간은 세계 각지에서 서로 다르게 작성되기 때문에 언어에 매우 민감하다. 때문에 Locale.getDefault()
로 사용자의 기기에 설정된 언어 정보를 가져와 SimpleDateFormat 생성자에 전달해야 한다.?:
의 형태로 왼쪽 표현식이 null이 아닐 때 이 값을 사용한다는 것을 의미한다. null 체크를 간단하게 수행하는 연산자다. 왼쪽 피연산자가 null이 아니면 그 값을 반환하고, null이면 오른쪽 피연산자를 반환한다.
_price.value = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
위 코드는 quantity.value가 null이면 0을, 아니면 해당 값을 그대로 사용한다는 뜻!
cf) 이 연산자는 엘비스 프레슬리의 이름을 땄다. 옆에서 보면 앞머리를 높이 세운 엘비스 프레슬리 같으니까!
문자열 resouce를 사용하고 ViewModel에서 매개변수를 전달하는 방법은 다음 예시처럼 하면 된다.
// fragment_flavor.xml
<TextView
android:id="@+id/subtotal"
...
android:text="@{@string/subtotal_price(viewModel.price)}"
tools:text="Subtotal $5.00" />
// string.xml
<resources>
...
<string name="subtotal_price">Subtotal %s</string>
...
</resources>
LiveData 소스에서 데이터 조작을 실행하고 결과 LiveData 객체를 반환한다. 쉽게 말해 LiveData 값을 다른 값으로 변환한다. Observer가 객체를 관찰하고 있지 않으면 변환은 일어나지 않는다는 걸 유의하자.
Transformations.map()
이 변환 메서드 중 하나다.
LiveData 변환을 사용할 수 있는 예는 다음과 같이 있다.
private val _price = MutableLiveData<Double>()
val price: LiveData<String>
위와 같이 Double에서 String으로 변경하려면 다음처럼 한다.
private val _price = MutableLiveData<Double>()
val price: LiveData<String> = Transformations.map(_price) {
NumberFormat.getCurrencyInstance().format(it)
}
Transformations.map()
으로 새로운 변수를 초기화하고 _price와 람다 함수를 전달한다.getCurrencyInstance
는 현지 통화 형식으로 변경하는 메서드다.NavGraph에서 대상 간 이동할 때 백 스택을 관리하는 속성이다. 아래 그림과 같이 스택에 쌓여있다 생각하고 두 가지 속성을 알아보자.
app:popUpTo="@id/startFragment"
인 경우, StartFragment
에 도달할 때까지 백 스택에 있는 대상들이 없어진다. 그래서 StartFragment으로 이동하면 실제로 StartFrament를 새 대상으로 백 스택에 추가하기 때문에 두 개나 백 스택에 생긴 것이다. 때문에 뒤로가기를 눌러 종료하려면 두 번 탭해야 한다! app:popUpTo="@id/startFragment"
와 app:popUpToInclusive="true"
을 함께 활용하면 StartFragment
가 하나만 생성된다. 때문에 한 번 탭해서 종료가 가능하다. cf) Design 탭에서 아래와 같이 활용한다. 이동하고자 하는 화살표를 클릭하고 속성을 설정하자. plurals
resource로 서로 다른 문자열 선택하기// string.xml
<plurals name="cupcakes">
<item quantity="one">%d cupcake</item>
<item quantity="other">%d cupcakes</item>
</plurals>
// SummaryFragment.kt
fun sendOrder() {
val numberOfCupcakes = sharedViewModel.quantity.value ?: 0
val orderSummary = getString(
R.string.order_details,
resources.getQuantityString(R.plurals.cupcakes, numberOfCupcakes, numberOfCupcakes),
sharedViewModel.flavor.value.toString(),
sharedViewModel.date.value.toString(),
sharedViewModel.price.value.toString()
)
val intent = Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.new_cupcake_order))
.putExtra(Intent.EXTRA_TEXT, orderSummary)
if (activity?.packageManager?.resolveActivity(intent, 0) != null) {
startActivity(intent)
}
}
유닛테스트인 경우,
계측테스트인 경우,
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun quantity_twelve_cupcakes() {
val viewModel = OrderViewModel() // 테스트할 ViewModel 인스턴스 생성
viewModel.quantity.observeForever {} // 하단 설명
viewModel.setQuantity(12) // 테스트 메서드에 값 입력
assertEquals(12, viewModel.quantity.value) // 예상 값 추론하여 실제와 확인
}
observeForever
→ LiveData 객체의 값을 테스트할 때 객체를 관찰하기 위해 사용한다.