Cómo encontrar la abstracción correcta para trabajar con cadenas en Android

En nuestros proyectos, intentamos cubrir el código con las pruebas necesarias y cumplir con los principios de arquitectura SÓLIDA y limpia. Nos gustaría compartir con los lectores de Habr la traducción del artículo de Hannes Dorfmann, autor de una serie de publicaciones sobre el desarrollo de Android. Este artículo describe una técnica que puede ayudarte a abstraer el trabajo con cadenas para ocultar los detalles de la interacción con diferentes tipos de recursos de cadena y facilitar la escritura de pruebas unitarias. 





Si está trabajando en una gran aplicación de Android y sospecha que su código puede confundirse al trabajar con recursos de diferentes fuentes, o si desea simplificar la escritura de pruebas en cadenas, este artículo puede serle útil. Traducido con permiso del 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