I'm using Hilt, and the MVVM pattern to get content that is in local files in my app.
Now I want to add to FileUiState
content that references UserData
(these are user preferences saved using ProtoDatastore), to be able to use it in a composable.
This is my code:
FileViewModel
@HiltViewModel
class FileViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val getFileUseCase: GetFileUseCase,
val userDataRepository: UserDataRepository,
) : ViewModel() {
private val fileTitleKey = "fileTitleKey"
private val fileNameKey = "fileNameKey"
private val route: FileRoute = savedStateHandle.toRoute()
private val fileName = savedStateHandle.getStateFlow(
key = fileNameKey,
initialValue = route.fileName,
)
private val fileTitle = savedStateHandle.getStateFlow(
key = fileTitleKey,
initialValue = route.fileTitle,
)
private val fileRequest = MutableStateFlow<FileRequestt?>(
FileRequestt(listOf(FileItem(fileName.value!!, fileTitle.value!!)), 1, 1, true, true, true)
)
val uiState: StateFlow<FileUiState> = fileRequest
.flatMapLatest {
it?.let(::loadDataFlow) ?: flowOf(FileUiState.Empty)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = FileUiState.Empty,
)
private fun loadDataFlow(fileRequest: FileRequestt): Flow<FileUiState> = flow {
emit(FileUiState.Loading)
try {
val result = getFileUseCase(fileRequest)
emit(FileUiState.Loaded(FileItemUiState(result)))
} catch (error: Exception) {
emit(FileUiState.Error(ExceptionParser.getMessage(error)))
}
}
fun setFileRequest(fileRequest: FileRequestt?) {
this.fileRequest.value = fileRequest
}
sealed class FileUiState {
data object Empty : FileUiState()
data object Loading : FileUiState()
class Loaded(val itemState: FileItemUiState) : FileUiState()
class Error(val message: String) : FileUiState()
}
}
GetFileUseCase
class GetFileUseCase @Inject constructor(
private val fileRepository: LocalFileRepository
) {
suspend operator fun invoke(fileRequest: FileRequestt): List<FileResponse> =
fileRepository.getFile(fileRequest)
}
UserDataRepository
interface UserDataRepository {
val userData: Flow<UserData>
//....
}
Composable
@ExperimentalFoundationApi
@Composable
internal fun FileScreen(
modifier: Modifier,
uiState: FileViewModel.FileUiState
) {
val state = rememberLazyListState()
//...
when (uiState) {
FileViewModel.FileUiState.Empty -> item { EmptyState() }
is FileViewModel.FileUiState.Error -> item { ErrorState() }
is FileViewModel.FileUiState.Loaded -> item {
//I need access to UserData here
FileToolbar(uiState = uiState)
FileResourceCardExpanded(uiState.itemState)
}
FileViewModel.FileUiState.Loading -> item { LoadingState() }
}
//...
How can I add the contents of UserData
to FileUiState
to have it available in the Composable?
I'm using Hilt, and the MVVM pattern to get content that is in local files in my app.
Now I want to add to FileUiState
content that references UserData
(these are user preferences saved using ProtoDatastore), to be able to use it in a composable.
This is my code:
FileViewModel
@HiltViewModel
class FileViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val getFileUseCase: GetFileUseCase,
val userDataRepository: UserDataRepository,
) : ViewModel() {
private val fileTitleKey = "fileTitleKey"
private val fileNameKey = "fileNameKey"
private val route: FileRoute = savedStateHandle.toRoute()
private val fileName = savedStateHandle.getStateFlow(
key = fileNameKey,
initialValue = route.fileName,
)
private val fileTitle = savedStateHandle.getStateFlow(
key = fileTitleKey,
initialValue = route.fileTitle,
)
private val fileRequest = MutableStateFlow<FileRequestt?>(
FileRequestt(listOf(FileItem(fileName.value!!, fileTitle.value!!)), 1, 1, true, true, true)
)
val uiState: StateFlow<FileUiState> = fileRequest
.flatMapLatest {
it?.let(::loadDataFlow) ?: flowOf(FileUiState.Empty)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = FileUiState.Empty,
)
private fun loadDataFlow(fileRequest: FileRequestt): Flow<FileUiState> = flow {
emit(FileUiState.Loading)
try {
val result = getFileUseCase(fileRequest)
emit(FileUiState.Loaded(FileItemUiState(result)))
} catch (error: Exception) {
emit(FileUiState.Error(ExceptionParser.getMessage(error)))
}
}
fun setFileRequest(fileRequest: FileRequestt?) {
this.fileRequest.value = fileRequest
}
sealed class FileUiState {
data object Empty : FileUiState()
data object Loading : FileUiState()
class Loaded(val itemState: FileItemUiState) : FileUiState()
class Error(val message: String) : FileUiState()
}
}
GetFileUseCase
class GetFileUseCase @Inject constructor(
private val fileRepository: LocalFileRepository
) {
suspend operator fun invoke(fileRequest: FileRequestt): List<FileResponse> =
fileRepository.getFile(fileRequest)
}
UserDataRepository
interface UserDataRepository {
val userData: Flow<UserData>
//....
}
Composable
@ExperimentalFoundationApi
@Composable
internal fun FileScreen(
modifier: Modifier,
uiState: FileViewModel.FileUiState
) {
val state = rememberLazyListState()
//...
when (uiState) {
FileViewModel.FileUiState.Empty -> item { EmptyState() }
is FileViewModel.FileUiState.Error -> item { ErrorState() }
is FileViewModel.FileUiState.Loaded -> item {
//I need access to UserData here
FileToolbar(uiState = uiState)
FileResourceCardExpanded(uiState.itemState)
}
FileViewModel.FileUiState.Loading -> item { LoadingState() }
}
//...
How can I add the contents of UserData
to FileUiState
to have it available in the Composable?
1 Answer
Reset to default 0That depends on the FileUiState.
You didn't show us how it should look like with the user data, so for the purpose of this answer I assume the user data should only be added to the Loaded
case. From what I can see it may well be possible that the user data is available in the other cases too, though for now I assume the ui state to be this:
sealed interface FileUiState {
data object Empty : FileUiState
data object Loading : FileUiState
data class Loaded(val itemState: FileItemUiState, val userData: UserData) : FileUiState
data class Error(val message: String) : FileUiState
}
Note that I changed the class to an interface (since you don't have any properties) and I made the inner classes to data classes.
The next step is to refactor the view model's uiState
to create this new FileUiState.
To better handle the loading state of the FileRequestt let's wrap it in something like this:
sealed interface FileResponseState {
data object Loading : FileResponseState
data class Loaded(val result: List<FileResponse>) : FileResponseState
}
I don't know the rest of your data model, so maybe this can be modelled somewhere or somehow better. For now let's change loadDataFlow
to return this new wrapper state:
private fun loadDataFlow(it: FileRequestt?): Flow<FileResponseState?> =
if (it == null) flowOf(null)
else flow {
emit(FileResponseState.Loading)
emit(FileResponseState.Loaded(getFileUseCase(it)))
}
The last piece of the puzzle is a constructor-like function1 to create a FileUiState from a FileResponseState and a UserData object:
private fun FileUiState(
fileResponseState: FileResponseState?,
userData: UserData,
): FileUiState = when (fileResponseState) {
null -> FileUiState.Empty
FileResponseState.Loading -> FileUiState.Loading
is FileResponseState.Loaded -> FileUiState.Loaded(
itemState = FileItemUiState(fileResponseState.result),
userData = userData,
)
}
Now everything can be put together like this:
val uiState: StateFlow<FileUiState> = combine(
fileRequest.flatMapLatest(::loadDataFlow),
userDataRepository.userData,
::FileUiState,
)
.catch { emit(FileUiState.Error(ExceptionParser.getMessage(it))) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = FileUiState.Empty,
)
combine
is the key here, as it combines multiple flows into one. Its first parameter is the fileRequest
flow transformed to a Flow<FileResponseState?>
, the second parameter is the new Flow<UserData>
. You could add any additional flows that you want to combine as additional parameters, but the last parameter is always the transformation function that converts the flows' content into a FileUiState
.
Note that, before the combined flow is eventually converted to a StateFlow, any errors that might have occurred so far are caught and wrapped in a FileUiState.Error
.
Now you can use this new FileUiState in your composables to access the UserData in the Loaded
case.
1 From the caller's perspective it looks like a constructor of FileUiState, but it actually is just a simple function. It behaves the same, though: It returns a new instance of FileUiState.
Constructor-like functions are a pattern to keep the class declaration separate from complex initialization logic.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745256147a4618970.html
评论列表(0条)