Caution
File read (I/O) operations can be long, so it is not recommended to use the UI scope to read files. But that's not what's causing the problem, I'm just warning you that if it reads very large files, it can make your app crash because it does a very long processing in the UI thread. I recommend checking this link if you're not familiar with this type of problem.
Problem solving following best practices
Fortunately Jetpack compose works very well with reactive programming, so we can take advantage of that to write reactive code that doesn't face the aforementioned problems.
I made an example very similar to yours, I hope you can understand:
UiState file
As stated earlier, reading a file can be a long process, so let's imagine 3 possible states, "loading", "message successful" and "error". In the case of "successful message" we will have a possibly null string that will no longer be null when the message is actually read from the txt file:
package com.example.kotlinreadfile
data class UiState(
val isLoading: Boolean,
val isOnError: Boolean,
val fileMessage: String?
)
MainActivity.kt file
Here will be just our implementation of the UI, in your case the text messages arranged in the application. As soon as we want to read these messages on the screen we will make a request to our 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 file
If you are unfamiliar with the ViewModel class I recommend this official documentation link.
Here we will focus on "our business rule", what we will actually do to get the data. Since we are dealing with an input/output (I/O) operation we will do this in an appropriate scope using 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
)
}
}
}
}
}