2

I want to set the UI elements from the code for an English learning App. There are more than 100 topics for each section of the app so I just created three pages of fragment changing UI elements according to the string I receive from getStringExtra. as follows:

when (activity?.intent?.getStringExtra("clickedGrammarTopic")) {

            "To Be - am/is/are" -> {
                binding.firstPageToolbarText.text = getText(R.string.tobe_amisare_fp_toolbar_text)
                binding.firstPageTextOne.text =  getText(R.string.tobe_amisare_fp_1)
                binding.firstPageTablePronoun.text = getText(R.string.tobe_amisare_table_pronoun)
                binding.firstPageTableAuxillaryVerb.text = getText(R.string.tobe_amisare_table_auxillary_verb)
                binding.firstPageTableVerb.text = getText(R.string.tobe_amisare_table_pronoun)
                binding.firstPageTablePronounI.text = getText(R.string.tobe_amisare_table_pronoun_I)
                binding.firstPageTableAuxAm.text = getText(R.string.tobe_amisare_table_aux_am)
                binding.firstPageTableVerbOne.text = getText(R.string.tobe_amisare_table_verb_one)
                binding.firstPageTableTranslation.text = getText(R.string.tobe_amisare_table_translation)
                binding.firstPageTablePronounYou.text = getText(R.string.tobe_amisare_table_pronoun_you)
                binding.firstPageTableAuxAre.text = getText(R.string.tobe_amisare_table_aux_are)
                binding.firstPageTableVerbTwo.text = getText(R.string.tobe_amisare_table_verb_two)
                binding.firstPageTableTranslationTwo.text = getText(R.string.tobe_amisare_table_translation)
                binding.firstPageTablePronounHe.text = getText(R.string.tobe_amisare_table_pronoun_he)
                binding.firstPageTableAuxIs.text = getText(R.string.tobe_amisare_table_aux_is)
                ....
            }

The problem here is whenever I try to add a new topic, I need to write over 50 lines of code. Is there an easier way to deal with this? If not, would this method have any negative effect on the overall performance of the app? (tried designing each page on .xml files ended up in a mess)

Aschente
  • 105
  • 7
  • There will have to be at least one spot in the app where you have to do this mapping, it is possible to move it to a different spot, but it would still be 50 lines, just a bit less text maybe. As far as performance goes, no, this is not a problem, it just sets all the texts once, hopefully you only call this code when you have to change the texts. – Ma3x Jan 28 '22 at 18:28
  • One thing that you could do is to use find + replace (possibly with a regex for the find part) when adding a new topic. Then it becomes just a copy + paste of the existing code and a find + replace. Then the fact that it is 100, or even 1000 lines does not matter anymore. It is equally fast to do the addition of a new topic. That is assuming that you use a consistent naming of the resource strings. In that case even this could help: https://stackoverflow.com/questions/4427608/android-getting-resource-id-from-string – Ma3x Jan 28 '22 at 18:37
  • Why are all the views in your layout starting with "firstPage"? Do you have multiple pages in a single layout? – Tenfour04 Jan 28 '22 at 18:48
  • @Ma3x I think I only call this code once. If the user clicks on the third topic of the recyclerview, I get the name of the third topic, send it via putExtra, get the string with getStringExtra, and set the strings accordingly. I'll have a look at find + replace you mentioned. Thanks a lot! – Aschente Jan 28 '22 at 19:38
  • @Tenfour04 Yes, I use ViewPager2 and the extract in my question is from the first fragment. On other pages, it follows secondPageStringName, thirdPageStringName, and so on. – Aschente Jan 28 '22 at 19:40

2 Answers2

2

You can create a helper function that does the original getText calls, but with resource name as a parameter, instead of the resource id.

fun Context.text(name: String): CharSequence {
    val resId = resources.getIdentifier(name, "string", this.packageName)
    return resources.getText(resId)
}

EDIT: Since this means that we are accessing resources without using ids, you might have to turn off resource shrinking (shrinkResources false), so they are not removed from release builds or use this trick instead https://stackoverflow.com/a/44129736

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources false // <--
            // ...
        }
    }
}

Then the main function is the one that has the mapping. But now you only need to have this once. It can use a resourcePrefix parameter that will be used to discriminate resource strings for each topic.

fun setTextsForTopic(resourcePrefix: String) {
    requireContext().run {
        binding.firstPageToolbarText.text = text("${resourcePrefix}_fp_toolbar_text")
        binding.firstPageTextOne.text = text("${resourcePrefix}_fp_1")
        binding.firstPageTablePronoun.text = text("${resourcePrefix}_table_pronoun")
        binding.firstPageTableAuxillaryVerb.text = text("${resourcePrefix}_table_auxillary_verb")
        binding.firstPageTableVerb.text = text("${resourcePrefix}_table_pronoun")
        binding.firstPageTablePronounI.text = text("${resourcePrefix}_table_pronoun_I")
        binding.firstPageTableAuxAm.text = text("${resourcePrefix}_table_aux_am")
        binding.firstPageTableVerbOne.text = text("${resourcePrefix}_table_verb_one")
        binding.firstPageTableTranslation.text = text("${resourcePrefix}_table_translation")
        binding.firstPageTablePronounYou.text = text("${resourcePrefix}_table_pronoun_you")
        binding.firstPageTableAuxAre.text = text("${resourcePrefix}_table_aux_are")
        binding.firstPageTableVerbTwo.text = text("${resourcePrefix}_table_verb_two")
        binding.firstPageTableTranslationTwo.text = text("${resourcePrefix}_table_translation")
        binding.firstPageTablePronounHe.text = text("${resourcePrefix}_table_pronoun_he")
        binding.firstPageTableAuxIs.text = text("${resourcePrefix}_table_aux_is")
        // ...
        // fill these in for the second page
        binding.secondPageToolbarText.text = text("${resourcePrefix}_???")
        binding.secondPageTextOne.text = text("${resourcePrefix}_???")
        // ...
        // fill these in for the third page
        binding.thirdPageToolbarText.text = text("${resourcePrefix}_???")
        binding.thirdPageTextOne.text = text("${resourcePrefix}_???")
        // ...
    }
}

Then the usage becomes just a single line for every topic. So adding a new topic is now a single line of code (after updating your strings.xml file).

when (activity?.intent?.getStringExtra("clickedGrammarTopic")) {
    "To Be - am/is/are" -> setTextsForTopic("tobe_amisare")
    // adding a new topic is now a single line (after updating your strings.xml file)
    "Past tense" -> setTextsForTopic("past_tense")
}

You have to be consistent with the naming of string ids, of course. But even if that is not the case now, you can just do a single refactoring of string ids to get that sorted once and for all.

Ma3x
  • 5,761
  • 2
  • 17
  • 22
  • @Aschente: see my late edit about resource shrinking in release builds. Don't forget to set that to `false`, if you decide to use this solution. Alternatively, you can use OneDev's solution and keep `shrinkResources true`. – Ma3x Jan 28 '22 at 20:01
1

put your text/string in string array

    <string-array name="lesson_one">
        <item>item1</item>
        <item>item2</item>
        <item>item3</item>
        <item>item4</item>
    </string-array>

for UI element define a list of UI ids:

    private val ids = arrayListOf(
        R.id.text1,
        R.id.text2,
    )

and loop through it

        for (i in 0..binding.root.childCount) {
            if (binding.root.getChildAt(i) is TextView) {
                if (binding.root.getChildAt(i).id == ids[i]) {
                    binding.root.getChildAt(i).text = resources.getStringArray(R.array.lesson_one)[i]
                }
            }
        }

and get your text from string array, your code goes concise

OneDev
  • 557
  • 3
  • 14