ViewModel 并使用上下文来获取 50x50 的本地化字符串资源列表?

问题描述 投票:0回答:1

请在此处找到我的 Kotlin - Jetpack Compose 项目的简化用例:

  • 我需要构建一个 50 x 50 字符串的列表(我们称之为 listC)。该列表由 2 个包含 50 个字符串的列表组成:listA 和 listB。每个字符串都是本地化的,因此必须从字符串资源中检索它。

  • 这就是为什么我创建了一个 MyData 类,如下(简化的代码语法):

class MyData(val context: Context) {
  var listA : MutableList<String> = mutableListOf()
  var listB : MutableList<String> = mutableListOf()
  var listC : MutableList<String> = mutableListOf()

init{
   initializeData()
}

fun initializeData() {
   listA.apply {
     add(context.resources.getString(R.string.valueA1)
     ...
     add(context.resources.getString(R.string.valueA50)
   }

   listB.apply {
     add(context.resources.getString(R.string.valueB1)
     ...
     add(context.resources.getString(R.string.valueB50)
   }

   for a in listA
     for b in listB
         listC.add("$a: $b")
}
  • listC 项目具有简化语法“$a: $b”,但显示的字符串可以是“$a: $b”、“$b: $a”、“$a”或“$ b”。

  • 这些 50 x 50 字符串之一将根据 ScreenViewModel 中定义的业务逻辑向用户显示,因此应在 ScreenViewModel 中检索数据,如下所示:

class ScreenViewModel : ViewModel() {

   val context = LocalContext.current
   var myData = MyData(context) 

}

显然这是非常糟糕的做法,但我完全不知道为什么也不知道如何做得更好。

您能帮我学习实现这一目标的正确方法吗?

对于初学者来说,这是一个很难掌握的话题:我应该学习哪些资源才能更好地理解这个问题?

非常感谢。

android kotlin android-viewmodel
1个回答
0
投票

此时,您可能已经知道您想要执行的操作的最佳实践,但我对您的代码进行了一些操作,并尝试应用“在 ViewModel 中使用字符串资源”一文中的建议。任何人都可以随意提出如何改进这一点或提出问题。

strings.xml

中,我有(2 个列表中的每一个都缩短为 4 项):

<resources>
    <string name="valueA1">turtle pie</string>
    <string name="valueA2">vanilla gelato</string>
    <string name="valueA3">strawberry shortcake</string>
    <string name="valueA4">pan au chocolat</string>
    <string name="valueB1">espresso</string>
    <string name="valueB2">black tea</string>
    <string name="valueB3">chamomile tea</string>
    <string name="valueB4">pour-over coffee</string>
    <!-- other string resources here -->
</resources>

对于 ViewModel,我在这里没有使用 
val context = LocalContext.current

,因为我相信它只能在可组合函数中工作。当此 ViewModel 在 Composable 中实例化时,它的

init
函数就会运行。我将字符串资源 ID 添加到列表 A 和 B(Int 数据类型),然后创建一个 Pairs 列表,即我的列表 C。最后,从列表 C 中选取一个项目,然后作为 LiveData
pickedItem
值发出。我的
ScreenViewModel.kt
看起来像这样:
class ScreenViewModel : ViewModel() {

    // Lists A and B contain Int string resource IDs only, not actual string values
    private val _strResIdsA = mutableListOf<Int>()
    private val _strResIdsB = mutableListOf<Int>()

    // List C is a list that will contain paired Int resource IDs from lists A and B
    private var _strResIdPairsC = mutableListOf<Pair<Int, Int>>()

    // Use LiveData to allow the UI to observe and use the value of the picked item once it's set
    private val _pickedItem by lazy { MutableLiveData<StringValue>() }
    val pickedItem: LiveData<StringValue> get() = _pickedItem

    init {
        // Populate lists A, B, and C  and then
        // start the pick an item after invoking the callback (to ensure list population is done)
        initializeData {
            // Pick a list C item based on below function's logic
            val pickedItemStrResPair = pickFromListCPairs()

            // Emit value of picked item
            val stringValue =
                StringValue.StringResourcePair(
                    pickedItemStrResPair.first,
                    pickedItemStrResPair.second
                )
            _pickedItem.postValue(stringValue)
        }
    }


    private fun createListCPairs(): List<Pair<Int, Int>> {

        // Create an empty mutable list of pairs
        val pairs = mutableListOf<Pair<Int, Int>>()

        // Populate the list of pairs based on list A and list B values
        for (resIdA in _strResIdsA) {
            for (resIdB in _strResIdsB) {
                val pair = Pair(resIdA, resIdB)
                pairs.add(pair)
            }
        }

        return pairs.toList()
    }


    private fun pickFromListCPairs(): Pair<Int, Int> {
        //Change logic as needed; Currently picks randomly
        val randomItemIndex = Random.nextInt(_strResIdPairsC.size);
        return _strResIdPairsC[randomItemIndex]
    }

    private fun initializeData(callback: () -> Unit) {

        // Populate list A
        _strResIdsA.apply {
            add(R.string.valueA1)
            add(R.string.valueA2)
            add(R.string.valueA3)
            add(R.string.valueA4)
            //todo Add the rest of the string resources
        }

        // Populate list B
        _strResIdsB.apply {
            add(R.string.valueB1)
            add(R.string.valueB2)
            add(R.string.valueB3)
            add(R.string.valueB4)
            //todo Add the rest of the string resources
        }

        // Populate list C
        _strResIdPairsC = createListCPairs().toMutableList()
        Log.i("logtag","list of pairs (size of ${_strResIdPairsC.size}): $_strResIdPairsC")

        // Invoke callback once population of lists is done
        callback.invoke()
    }
}

我的 
MainActivity.kt

有一个可组合函数,可以实例化 ViewModel 并观察其

pickedItem
LiveData。这就是我把你的
val context = LocalContext.current
移到的地方。一旦
pickedItem
数据更新,我们就会在
Text
元素中显示其值。请注意,文本的值使用关键字
var pickedItemText by remember { mutableStateOf("") }
声明为
remember
,以确保
Text
元素的值将在“重组”时更新。
class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            AnythingGoesTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    DisplayPickedItem()
                }
            }
        }
    }
}

@Composable
fun DisplayPickedItem() {    
    // Instantiate ViewModel
    val vm = viewModel<ScreenViewModel>()

    // Get context here instead of within the ViewModel
    val context = LocalContext.current

    // Declare variable for picked item's value here
    var pickedItemText by remember { mutableStateOf("") }
    
    // Observe to wait for emitted value from the pickedItem LiveData of the ViewModel
    vm.pickedItem.observe(LocalLifecycleOwner.current) {
        // Set observed string value to display using Text later
        pickedItemText = it.asString(context)
        Log.d("logtag", "pickedItemText=$pickedItemText")
    }

    // Display text output
    Text(text = pickedItemText)
}

我没有使用您的 
MyData

类,而是基于

在 ViewModel 中使用字符串资源
文章创建了这个
StringValue
类,其中我添加了一个新的 class StringResourcePair 来支持我对字符串资源 ID 对的实现:
sealed class StringValue {

    data class DynamicString(val value: String) : StringValue()

    object Empty : StringValue()

    class StringResource(
        @StringRes val resId: Int,
        vararg val args: Any
    ) : StringValue()

    // I added this to let us use the pair of string resources from list C
    class StringResourcePair(
        @StringRes val resIdA: Int,
        @StringRes val resIdB: Int,
        vararg val args: Any
    ) : StringValue()

    fun asString(context: Context?): String {
        return when (this) {
            is Empty -> ""
            is DynamicString -> value
            is StringResource -> context?.getString(resId, *args).orEmpty()
            is StringResourcePair -> {
                val stringA = context?.getString(resIdA, *args).orEmpty()
                val stringB = context?.getString(resIdB, *args).orEmpty()
                "$stringA: $stringB"
            }

        }
    }
}

一些阅读资源:

LiveData:
    https://developer.android.com/topic/libraries/architecture/livedata
  • 当按键更改时重新触发记住计算:
  • https://developer.android.com/develop/ui/compose/state#retrigger-remember
  • 配对:
  • https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-pair/
  • Kotlin 回调函数:
  • https://medium.com/@agayevrauf/kotlin-callback-functions-with-code-examples-3eb1f6bcadcf
© www.soinside.com 2019 - 2024. All rights reserved.