Analog of R.string in the android application

Hello everyone! My name is Vladimir, I am an Android developer at Alfa Capital. Surely any mobile application in the development process needs flexible configuration of text information at the expense of the server side. In this article, I will share our team's thoughts and solutions. I will also show an example of code generation using a gradle script, which greatly simplified the life of the android team.





How it all began

Our application contains quite a lot of textual information for users, ranging from tips and descriptions to disclaimers and large information screens. We work in the field of financial technology, and sometimes - in order to meet the requirements of the Central Bank - you need to quickly update information. This sometimes needs to be done at the request of our lawyers. In addition, sometimes you need a screen where in the first week there will be one text, and in the second week there will be another text.





I'll make a reservation right away: in its current form (and in the near future), the business is focused on Russia, so there is no need to support several languages ​​in the application in the project.





Faced with the problem of updating information, we began to think about storing texts (which in the future will have to be corrected) not in the application, but on the server. Some (for example, a large list of disclaimers for lawyers) were pulled out. But there were still many lines that were difficult to somehow combine and for which I did not want to make a network request and a boot screen every time. 





First, we tried to keep the texts on Firebase. In terms of functionality, this solution was quite suitable, besides, it added versioning and the ability to create a / b tests. It soon became clear that this was not what we needed after all. Then we formulated our requirements:





  1. Convenient and single source of texts for all mobile platforms (android / ios);





  2. Updating texts at runtime at the start of the application (to update important places without releasing fixes / releases);





  3. ;





  4. (.. / );





  5. , .





Firebase Remote Config β€” . , / . . - .





,   JSON . JSON, XML, Android? (Android iOS). JSON β€” , . , .  . ? , JSON c .





json :





{
 "screen1_text1": "Text 1",
 "screen1_text2": "Text 2 \nnext line",
 "screen1_text3": "Text 3",
 "screen1_text4": "Text 4"
}
      
      



 

JSON , assets. Lexemator, - . Lexemator, - , assets.





object Lexemator {
	fun getString(key: String): String
}
      
      







class MainActivity : Activity() {

   override fun onCreate(savedInstanceState: Bundle?) {
			 ...
       val textView = findViewById<TextView>(R.id.text1)
       textView.text = Lexemator.getString("screen1_text1")
   }
}

      
      



Firebase , : , . , R.string, Android Studio .





, .





Gradle -

JSON , , , . - , , . , , . gradle task.









import groovy.json.JsonSlurper

/**
*        strings.json    LL.
*     strings.json   LL.key  
*
*   strings.json   -   Exception.
*
*    ,   ,    strings.json
*/

def classFileName = "LL"
def stringsFileName = "strings.json"
def filePath = project.rootProject.getProjectDir().path + "/app/src/main/assets/json"
def outputPath = project.rootProject.getProjectDir().path + "/app/build/generated/strings"
def inputFile = new File(filePath + "/${stringsFileName}")
def outputFile = new File(outputPath + "/${classFileName}.kt")

task createStrings {

   /**
    *  -   inputFile,       
    * outputFile.
    *    ,  outputFile  ,    "UP-TO-DATE" 
    *     .
    */
   inputs.file(inputFile)
   outputs.file(outputFile)

   doLast {
       if (!inputFile.exists()) {
           throw RuntimeException(" ${inputFile}  ")
       }

       println("   ${outputFile.path}")
       outputFile.delete()
       outputFile.createNewFile()

       /**
        *     ,    (\n)  strings.json
        *      LL.kt .
        */
       def s1 = """package com.obolonnyy.lexemator

//<!--    gradle   create_strings.gradle -->

object ${classFileName} {
"""

       def s2 =
               """   
   fun addLexems(map: Map<String, String>) {
       map.forEach { k, v -> addLexem(k, v) }
   }

   fun addLexem(key: String, value: String) {
       when(key) {
"""

       def json = new JsonSlurper().parse(inputFile)
       assert json instanceof Map

       json.each { entry ->
           s1 += "    var ${entry.key} = \"\"\"${entry.value}\"\"\"\n        private set\n"
           s2 += "            \"${entry.key}\" -> ${entry.key} = value\n"
       }

       def result = s1 + "\n\n" + s2 + """        }
   }
}"""

       outputFile.write(result)
       println(" ${outputFile.path}  .")
   }
}

/**
* ,        .
*        LL.kt    .
*/
android {
   sourceSets {
       main {
           java {
               srcDirs += outputPath
           }
       }
   }
}
      
      



object LL, ( String, ) , . addLexems().





LL: L ( Lexemator), R, android.icu.lang.UCharacter.GraphemeClusterBreak.L. , , LL. 





LL : 





//<!--    gradle   create_strings.gradle -->

object LL {
   var screen1_text1 = """Text 1"""
       private set
   var screen1_text2 = """Text 2
next line"""
       private set
   var screen1_text3 = """Text 3"""
       private set
   var screen1_text4 = """Text 4"""
       private set


  
   fun addLexems(map: Map<String, String>) {
       map.forEach { k, v -> addLexem(k, v) }
   }

   fun addLexem(key: String, value: String) {
       when(key) {
           "screen1_text1" -> screen1_text1 = value
           "screen1_text2" -> screen1_text2 = value
           "screen1_text3" -> screen1_text3 = value
           "screen1_text4" -> screen1_text4 = value
       }
   }
}

      
      



LL :  





class MainActivity : Activity() {

   override fun onCreate(savedInstanceState: Bundle?) {
			...
       val textView = findViewById<TextView>(R.id.text1)
       textView.text = LL.screen1_text1
   }
}

      
      







β€” . , git . . Android . JSON 180 , .





A working example can be found at the link .








All Articles