Como encontrar a abstração certa para trabalhar com strings no Android

Em nossos projetos, tentamos cobrir o código com testes conforme necessário e aderir aos princípios de SOLID e arquitetura limpa. Gostaríamos de compartilhar com os leitores da Habr a tradução do artigo de Hannes Dorfmann, autor de uma série de publicações sobre o desenvolvimento do Android. Este artigo descreve uma técnica que pode ajudá-lo a abstrair o trabalho com strings para ocultar os detalhes da interação com diferentes tipos de recursos de string e tornar mais fácil escrever testes de unidade. 





Se você está trabalhando em um grande aplicativo Android e suspeita que seu código pode ficar confuso ao trabalhar com recursos de fontes diferentes, ou se deseja simplificar a escrita de testes em strings, este artigo pode ser útil para você. Traduzido com permissão do autor.





Foto: Unsplash
Foto: Unsplash

. , 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_textInt). -: 





@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, , . , . , – , , . !








All Articles