android - How to add Proto Datastore content to UIState for use in a Composable? - Stack Overflow

I'm using Hilt, and the MVVM pattern to get content that is in local files in my app.Now I want t

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?

Share Improve this question edited Feb 3 at 0:13 tyg 16.8k4 gold badges39 silver badges49 bronze badges asked Feb 2 at 21:15 A. CedanoA. Cedano 1,00117 silver badges50 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

That 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条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信