强制重拨(Android 撰写)

forcing a recomposition (Android compose)

代码:

package com.example.saveandloadusername

import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.runtime.*

import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp

import com.example.saveandloadusername.ui.theme.SaveAndLoadUserNameTheme
import java.io.File
import java.io.IOException

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SaveAndLoadUserNameTheme {
                Surface(color = MaterialTheme.colors.background) {
                    MainScreen(baseContext)
                }
            }
        }
    }
}

@Composable
fun MainScreen(context: Context) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    )
    {
        var name by remember { mutableStateOf("")}

        if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
            Text(text="Hello, give me your name :)")
        } else {
            Text(text="welcome back ${readNameFromInternalStorage(context)}")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        OutlinedTextField(
            value=name,
            onValueChange={ name = it },
            label={Text(text="Name")},
            singleLine = true,
            keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words)
        )

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(
            onClick = {
                name = name.replace(" ", "".replace("\n", ""))
                if(name == "") {
                    Toast.makeText(context, "name invalid", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(context, "name (${name}) saved :D", Toast.LENGTH_SHORT).show()
                    saveNameToInternalStorage(name, context)
                }
            },
        )
        {
            Text(text="save")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(
            onClick = {
                if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
                    Toast.makeText(context, "you need to give me a name first ;)", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(
                        context,
                        "the name is: '${readNameFromInternalStorage(context)}'",
                        Toast.LENGTH_SHORT
                    ).show()
                }

            },
        )
        {
            Text(text="check")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(onClick = { cleanNameData(context)}) {
            Text("Remove name")
        }

    }

}

private fun saveNameToInternalStorage(name: String, context: Context): Boolean {
    return try {
        context.applicationContext.openFileOutput("name.txt", MODE_PRIVATE).use { stream ->
            stream.flush()
            stream.write(name.toByteArray())
            Log.i("SAVE_STATE","name ($name) written as ${name.toByteArray()}")
        }
        return true
    } catch(e: IOException) {
        e.printStackTrace()
        false
    }
}

private fun readNameFromInternalStorage(context: Context): String {
    val file = File(context.filesDir, "name.txt")
    return if (file.exists()) {
        val contentOfFile = file.readBytes().decodeToString()
        Log.i("CONTENTTT", contentOfFile)
        contentOfFile
    } else {
        ""
    }
}

private fun checkIfNameIsEmpty(name: String): Boolean {
    return name.isEmpty()
}

private fun cleanNameData(context: Context) {
    context.applicationContext.openFileOutput("name.txt", MODE_PRIVATE).use { stream ->
        stream.flush()
        Log.i("cleanNameData", "Name Removed from memory")
        Toast.makeText(context, "Name removed", Toast.LENGTH_SHORT).show()
    }
}


我创建了一个小应用程序来了解 android compose,但我偶然发现了一个我无法解决的问题,文本(位于 TextField 上方)在按下按钮“删除名称”或“保存”,文本仅在文本框中发生变化时更新,有没有办法手动强制重新组合该文本? 任何帮助表示赞赏:)

在下面的代码块中,您没有在任何地方使用 name 变量

if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
    Text(text="Hello, give me your name :)")
} else {
    Text(text="welcome back ${readNameFromInternalStorage(context)}")
}

因此,更改 name 值不会对重组产生任何影响。如果你真的想要,你需要(有点)remember{readNameFromInternalStorage(context)} 为了让重组对文本产生影响。

首先,如果您需要做的只是在那儿显示那个名字,那为什么还要费力地将它存储到存储器中,然后再读回来呢?建议:可以直接使用name参数作为Text的值,修改后直接将新的name存入storage,如果交易成功,再更新变量。它将触发必要的重组。

接下来,文本未更新的原因是因为您用于从存储中获取名称的方法 returns 是原始类型,而不是 LiveData。因此,如果您自己实现该方法,请先尝试阅读 LiveData,而不是手动触发重组。实现起来应该不难。

但是(不推荐),如果您想要的只是一种手动重新触发合成的方法,那么这里就是。

您可能知道,Compose 使用 MutableState 对象来观察数据,您似乎对它非常熟悉,因为您已经在使用它了。因此,您需要做的只是添加一个 MutableState 类型的 'dummy' 变量,然后从相关按钮的 onClick 修改它。此外,您必须向 Compose 发送一条消息,表明正在 Text 中读取虚拟变量,因此它会触发重新组合。

@Composable
fun MainScreen(context: Context) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    )
    {
        var name by remember { mutableStateOf("")}
        var dummy by mutableStateOf(false) // don't even need to remember, long as it compiles

        if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
            dummy
            Text(text="Hello, give me your name :)")
        } else {
            dummy
            Text(text="welcome back ${readNameFromInternalStorage(context)}")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        OutlinedTextField(
            value=name,
            onValueChange={ name = it },
            label={Text(text="Name")},
            singleLine = true,
            keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words)
        )

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(
            onClick = {
                dummy = !dummy // modify to trigger recomposition
                name = name.replace(" ", "".replace("\n", ""))
                if(name == "") {
                    Toast.makeText(context, "name invalid", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(context, "name (${name}) saved :D", Toast.LENGTH_SHORT).show()
                    saveNameToInternalStorage(name, context)
                }
            },
        )
        {
            Text(text="save")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(
            onClick = {
                dummy = !dummy // The same here
                if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
                    Toast.makeText(context, "you need to give me a name first ;)", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(
                        context,
                        "the name is: '${readNameFromInternalStorage(context)}'",
                        Toast.LENGTH_SHORT
                    ).show()
                }

            },
        )
        {
            Text(text="check")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(onClick = {
                dummy = !dummy //That's all
                cleanNameData(context)
                      }) {
            Text("Remove name")
        }

    }

}

是的,应该这样做。

还有,您不需要粘贴整个代码库。只需提供必要的位,如果需要,我们会要求更多。谢谢,