In unseren Projekten versuchen wir, den Code nach Bedarf mit Tests abzudecken und die Prinzipien von SOLID und sauberer Architektur einzuhalten. Wir möchten den Lesern von Habr die Übersetzung des Artikels von Hannes Dorfmann, dem Autor einer Reihe von Veröffentlichungen zur Android-Entwicklung, mitteilen. Dieser Artikel beschreibt eine Technik, mit der Sie die Arbeit mit Zeichenfolgen abstrahieren können, um die Details der Interaktion mit verschiedenen Arten von Zeichenfolgenressourcen zu verbergen und das Schreiben von Komponententests zu vereinfachen.
Wenn Sie an einer großen Android-Anwendung arbeiten und den Verdacht haben, dass Ihr Code beim Arbeiten mit Ressourcen aus verschiedenen Quellen verwirrt wird, oder wenn Sie das Schreiben von Tests gegen Zeichenfolgen vereinfachen möchten, ist dieser Artikel möglicherweise hilfreich für Sie. Übersetzt mit Genehmigung des Autors.
. , Android Android.
?
Android? , , . , , , , , , . , :
R.string.some_text, resources.getString(R.string.some_text)
, , .. context.getString(R.string.some_text, «arg1», 123)
<string name=”some_formatted_text”>Some formatted Text with args %s %i</string>
, Plurals, , resources.getQuantityString(R.plurals.number_of_items, 2):
<plurals name="number_of_items">
<item quantity="one">%d item</item>
<item quantity="other">%d items</item>
</plurals>
, Android XML- strings.xml, String ( R.string.some_text). , , json .
, , ? . :
1. , , .
2. ( ) -, , .
: , http, , fallback- strings.xml. , :
class MyViewModel(
private val backend : Backend,
private val resources : Resources // Android context.getResources()
) : ViewModel() {
val textToDisplay : MutableLiveData<String> // MutableLiveData
fun loadText(){
try {
val text : String = backend.getText()
textToDisplay.value = text
} catch (t : Throwable) {
textToDisplay.value = resources.getString(R.string.fallback_text)
}
}
}
MyViewModel, . , loadText(), Resources, StringRepository ( ""), :
interface StringRepository{
fun getString(@StringRes id : Int) : String
}
class AndroidStringRepository(
private val resources : Resources // Android context.getResources()
) : StringRepository {
override fun getString(@StringRes id : Int) : String = resources.getString(id)
}
class TestDoubleStringRepository{
override fun getString(@StringRes id : Int) : String = "some string"
}
- StringRepository , , ?
class MyViewModel(
private val backend : Backend,
private val stringRepo : StringRepository //
) : ViewModel() {
val textToDisplay : MutableLiveData<String>
fun loadText(){
try {
val text : String = backend.getText()
textToDisplay.value = text
} catch (t : Throwable) {
textToDisplay.value = stringRepo.getString(R.string.fallback_text)
}
}
}
- -:
@Test
fun when_backend_fails_fallback_string_is_displayed(){
val stringRepo = TestDoubleStringRepository()
val backend = TestDoubleBackend()
backend.failWhenLoadingText = true // backend.getText()
val viewModel = MyViewModel(backend, stringRepo)
viewModel.loadText()
Assert.equals("some string", viewModel.textToDisplay.value)
}
interface StringRepository , ? . , :
StringRepository , (. ). , - , , String. .
, TestDoubleStringRepository , , ? TestDoubleStringRepository . -, R.string.foo R.string.fallback_text StringRepository.getString(), . , TestDoubleStringRepository, :
class TestDoubleStringRepository{
override fun getString(@StringRes id : Int) : String = when(id){
R.string.fallback_test -> "some string"
R.string.foo -> "foo"
else -> UnsupportedStringResourceException()
}
}
? ( )?
, .
TextResource
TextResource. , domain. , -. :
sealed class TextResource {
companion object { // ,
fun fromText(text : String) : TextResource = SimpleTextResource(text)
fun fromStringId(@StringRes id : Int) : TextResource = IdTextResource(id)
fun fromPlural(@PluralRes id: Int, pluralValue : Int) : TextResource = PluralTextResource(id, pluralValue)
}
}
private data class SimpleTextResource( // inline
val text : String
) : TextResource()
private data class IdTextResource(
@StringRes id : Int
) : TextResource()
private data class PluralTextResource(
@PluralsRes val pluralId: Int,
val quantity: Int
) : TextResource()
//
...
- TextResource:
class MyViewModel(
private val backend : Backend // , , , - , StringRepository.
) : ViewModel() {
val textToDisplay : MutableLiveData<TextResource> // String
fun loadText(){
try {
val text : String = backend.getText()
textToDisplay.value = TextResource.fromText(text)
} catch (t : Throwable) {
textToDisplay.value = TextResource.fromStringId(R.string.fallback_text)
}
}
}
:
1) textToDisplay c LiveData<String> LiveData<TextResource>, - , String. TextResource. , , , TextResource – , .
2) -. « » StringRepository ( Resources). , , , ? , TextResource. , Android, (R.string.fallback_text – Int). -:
@Test
fun when_backend_fails_fallback_string_is_displayed(){
val backend = TestDoubleBackend()
backend.failWhenLoadingText = true // backend.getText()
val viewModel = MyViewModel(backend)
viewModel.loadText()
val expectedText = TextResource.fromStringId(R.string.fallback_text)
Assert.equals(expectedText, viewModel.textToDisplay.value)
// data class- equals,
}
, : TextResource String, , , TextView? , Android, UI.
// context.getResources()
fun TextResource.asString(resources : Resources) : String = when (this) {
is SimpleTextResource -> this.text // smart cast
is IdTextResource -> resources.getString(this.id) // smart cast
is PluralTextResource -> resources.getQuantityString(this.pluralId, this.quantity) // smart cast
}
TextResource String UI ( ) , TextResource «» (.. ), R.string.* .
: - TextResource.asString(), . , when. resources.getString(). , TextResource , «/». , , : , TextResource, when TextResource.asString().
: , TextResource /. / TextResource, sealed class TextResouce abstract fun asString(r: Resources), . , / asString(r: Resources), ( , , /). ? , Resources API TextResource , (, SimpleTextResource ). , API, , ( ).
: dimens, , . , . , – , , . !