【问题标题】:Display asset text file in Composable function using Kotlin Jetpack Compose使用 Kotlin Jetpack Compose 在 Composable 函数中显示资产文本文件
【发布时间】:2022-01-12 21:48:59
【问题描述】:

我正在尝试使用 Kotlin 从我的 Assets 文件夹中读取一个文本文件并将其显示到 Compose text 小部件。 Android Studio 北极狐 2020.3

以下代码成功运行并将文本文件显示到输出控制台,但是我不知道如何获取文本文件并将其传递给 Compose 文本小部件。

您会注意到我在 ReadDataFile() 中有 2 个 text() 调用。第一个 text() 在 try{} 之外并且可以正常工作,但是 try{} 内的 text() 会导致错误:“Try catch is not supported around composable function invocations”

我怎样才能做到这一点?

谢谢!

package com.learning.kotlinreadfile

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.learning.kotlinreadfile.ui.theme.KotlinReadFileTheme
import java.io.InputStream
import java.io.IOException

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            KotlinReadFileTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {
                    ReadDataFile()
                }
            }
        }
    }
}

@Composable
fun ReadDataFile() {
    println("Read Data File")
    Text("Read Data File")
    val context = LocalContext.current
    try {
        val inputStream: InputStream = context.assets.open("data.txt")
        val size: Int = inputStream.available()
        val buffer = ByteArray(size)
        inputStream.read(buffer)
        var string = String(buffer)
        println(string)
        //Text(string)      // ERROR: Try catch is not supported around composable function invocations
    } catch (e: IOException) {
        e.printStackTrace()
        println("Error")
    }
}

【问题讨论】:

  • 尝试在 try/catch 之后放置 //Text(string)。另外,把它换成 Column
  • @Alexander:做到了!

标签: kotlin android-jetpack-compose


【解决方案1】:

注意

文件读取 (I/O) 操作可能会很长,因此不建议使用 UI 范围读取文件。但这不是导致问题的原因,我只是警告您,如果它读取非常大的文件,它可能会使您的应用程序崩溃,因为它在 UI 线程中进行了很长时间的处理。如果您不熟悉此类问题,我建议您查看this link

按照最佳实践解决问题

幸运的是,Jetpack compose 与 reactive programming 配合得非常好,因此我们可以利用它来编写不会遇到上述问题的响应式代码。 我做了一个和你很相似的例子,希望你能理解:

UiState 文件

如前所述,读取文件可能是一个漫长的过程,所以让我们想象 3 种可能的状态,“正在加载”、“消息成功”和“错误”。在“成功消息”的情况下,我们将有一个可能为空的字符串,当实际从txt 文件中读取消息时,该字符串将不再为空:

package com.example.kotlinreadfile

data class UiState(
    val isLoading: Boolean,
    val isOnError: Boolean,
    val fileMessage: String?
)

MainActivity.kt 文件

这只是我们对 UI 的实现,在您的例子中是应用程序中排列的文本消息。只要我们想在屏幕上阅读这些消息,我们就会向我们的ViewModel 发出请求:

package com.example.kotlinreadfile

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.example.kotlinreadfile.ui.theme.KotlinReadFileTheme

class MainActivity : ComponentActivity() {

    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            KotlinReadFileTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {
                    val context = LocalContext.current
                    viewModel.loadData(context)
                    ScreenContent(viewModel.uiState.value)
                }
            }
        }
    }

    @Composable
    fun ScreenContent(uiState: UiState) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        ) {
            Text(text = "Read Data File")
            Spacer(modifier = Modifier.height(8.dp))
            when {
                uiState.isLoading -> CircularProgressIndicator()
                uiState.isOnError -> Text(text = "Error when try load data from txt file")
                else -> Text(text = "${uiState.fileMessage}")
            }
        }

    }
}

MainViewModel.kt 文件

如果您不熟悉ViewModel 课程,我推荐这个official documentation link。 在这里,我们将关注“我们的业务规则”,即我们将实际做什么来获取数据。由于我们正在处理输入/输出 (I/O) 操作,我们将使用 viewModelScope.launch(Dispatchers.IO) 在适当的范围内执行此操作:

package com.example.kotlinreadfile

import android.content.Context
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
import java.io.InputStream

class MainViewModel : ViewModel() {
    private val _uiState = mutableStateOf(
        UiState(
            isLoading = true,
            isOnError = false,
            fileMessage = null
        )
    )
    val uiState: State<UiState> = _uiState

    fun loadData(context: Context) {
        viewModelScope.launch(Dispatchers.IO) {
            try {
                val inputStream: InputStream = context.assets.open("data.txt")
                val size: Int = inputStream.available()
                val buffer = ByteArray(size)
                inputStream.read(buffer)
                val string = String(buffer)
                launch(Dispatchers.Main) {
                    _uiState.value = uiState.value.copy(
                        isLoading = false,
                        isOnError = false,
                        fileMessage = string
                    )
                }
            } catch (e: IOException) {
                e.printStackTrace()
                launch(Dispatchers.Main) {
                    _uiState.value = uiState.value.copy(
                        isLoading = false,
                        isOnError = true,
                        fileMessage = null
                    )
                }
            }
        }
    }
}

【讨论】:

  • 感谢您出色的帖子增强了解决方案。我理解您为什么认为这种方法很重要,我将尝试从您的帖子中学习如何做到这一点。
【解决方案2】:

jetpack compose by state refresh,请尝试

@Preview
@Composable
fun ReadDataFile() {
    var dataText by remember {
        mutableStateOf("asd")
    }
    println("Read Data File")
    Column {
        Text("Read Data File")
        Text(dataText)
    }
    val context = LocalContext.current
    LaunchedEffect(true) {
        kotlin.runCatching {
            val inputStream: InputStream = context.assets.open("data.txt")
            val size: Int = inputStream.available()
            val buffer = ByteArray(size)
            inputStream.read(buffer)
            String(buffer)
        }.onSuccess {
            it.logE()
            dataText = it
        }.onFailure {
            dataText = "error"
        }

    }
}

【讨论】:

  • 感谢您的代码!这也有效。我不得不注释掉“it.logE()”这一行,因为它没有被识别。那应该怎么做?
  • YellowSea 的代码是否解决了@PierreVieira 在上述提交中提出的长文件读取问题?
  • 对不起。 logE() 是我的日志打印。您可以删除或添加 fun String.logE(tag: String = "TAG") = Log.e(tag, this) @SqueezeOJ
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-19
相关资源
最近更新 更多