使用 Kotlin Jetpack Compose 在 Composable 函数中显示资源文本文件

Squ*_*eOJ 4 kotlin android-jetpack-compose

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

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

您会注意到我在 ReadDataFile() 内有 2 个 text() 调用。第一个 text() 位于 try{} 之外,并且工作正常,但是try{} 内的 text() 会导致错误:“可组合函数调用不支持 Try catch”

我怎样才能做到这一点?

谢谢!

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")
    }
}
Run Code Online (Sandbox Code Playgroud)

Pie*_*ira 12

警告

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

遵循最佳实践解决问题

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

用户界面状态文件

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

package com.example.kotlinreadfile

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

Run Code Online (Sandbox Code Playgroud)

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}")
            }
        }

    }
}

Run Code Online (Sandbox Code Playgroud)

MainViewModel.kt 文件

如果您不熟悉该类,ViewModel我推荐此官方文档链接。在这里,我们将重点关注“我们的业务规则”,即我们实际上将做什么来获取数据。由于我们正在处理输入/输出(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
                    )
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在可组合项中调用“viewModel.loadData(context)”是一个坏主意:这将在每次重组时触发。将其进一步移出可组合范围到 onCreate 可以解决此问题。(否则,使用 `LaunchedEffect`) (2认同)