Я даже не уверен, в чем именно заключается мой вопрос или какое хорошее название для этого, кроме как в целом: как, черт возьми? Я не разработчик, я просто пытаюсь сделать приложение для себя. Я пробовал использовать отладчик с разрывами кода/функций/строк, входами и выходами, но я вообще не понимаю журнал и что происходит.
Итак контекст такой:
Пытаюсь создать автономное приложение с базой данных для отслеживания коллекции чего-либо. Желаемое поведение, которое я хочу, - это когда приложение запускается, оно переходит в «домашнее» представление, в котором перечислены данные в режиме просмотра «сохранено» (хранилище данных пользовательских настроек). Я переключился между двумя различными представлениями макета данных (таблицей или списком) на этой домашней странице. По сути, я попробовал объединить приложения Inventory и Dessert Release из лабораторий кода в качестве отправной точки.
Вот текущее поведение того, что происходит:
Включая короткое видео (запись экрана):
https://thewikihow.com/video_2zG9WwfOuSg
Приложение запускается, экран в течение нескольких секунд пуст, затем я вижу обновление экрана и ненадолго вижу таблицу данных, прежде чем она заменяется моей вещью «если список пуст, отобразить добавление элементов» (хотя в база данных [Комната, Дао]). Таким образом, при запуске список по какой-то причине не заполняется (он заполняется только в том случае, если я повторно перехожу к этому же экрану), независимо от того, по умолчанию у меня есть представление списка или таблицы (но есть и другое странное поведение в отношении что).
На этом этапе, если я нажимаю переключатель, чтобы изменить вид, переключатель работает так, как и должен: значок меняется вперед и назад при нажатии. Однако данные в базе данных ни в коем случае не отображаются ниже, и этот переключатель работает только тогда, когда представление списка пусто (или, по крайней мере, когда оно думает, что список пуст, что на самом деле не так).
Только когда я нажимаю на нижнюю панель навигации по значку дома (это не панель навигации, это настраиваемая нижняя панель приложения, которую я написал), представление обновляется и отображается таблица данных (раньше я добавлял данные в базу данных). ), но затем, как только таблица отобразится, переключатель вида больше не работает. За одним исключением... если я изменю значение по умолчанию для isTableView на «false», произойдет то же самое поведение, за исключением представления списка, однако при отображении представления списка я могу нажать кнопку изменения представления, и значок переключения представления изменится. но он возвращается в состояние «пустой список», и значок переключателя меняется, но мне все равно приходится повторно переходить на главный экран, чтобы снова открыть представление списка. Я предполагаю, что у меня что-то не так с представлением таблицы (это maven Central oleksandrbalan/lazytable, который я получил с gitHub).
Я пробовал поменять код и изменить некоторые заказы, но не могу понять, как это ломается, и я играл с этим две недели и не могу понять.
Итак, я собираюсь дать, как я предполагаю, весь соответствующий код, но если нужно увидеть другой код, я могу отредактировать его и добавить его. Например, я не включил код конструкции для режим просмотра таблицы или списка или что-то еще, но это на той же странице ниже.
Домашний экран:
fun HomeScreen(
navigateToHome: () -> Unit,
navigateToStats: () -> Unit,
navigateToAddEntry: () -> Unit,
navigateToEditEntry: (Int) -> Unit,
modifier: Modifier = Modifier,
viewmodel: HomeViewModel = viewModel(factory = AppViewModelProvider.Factory),
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val homeUiState by viewmodel.homeUiState.collectAsState()
val items = homeUiState.items
val isTableView = homeUiState.isTableView
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
CellarTopAppBar(
title = stringResource(HomeDestination.titleRes),
scrollBehavior = scrollBehavior,
canNavigateBack = false,
)
},
bottomBar = {
CellarBottomAppBar(
modifier = Modifier
.padding(0.dp),
navigateToHome = navigateToHome,
navigateToStats = navigateToStats,
navigateToAddEntry = navigateToAddEntry,
)
},
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier = Modifier
.fillMaxSize()
.padding(top = 64.dp, bottom = 66.dp, start = 0.dp, end = 0.dp)
) {
HomeHeader(
homeUiState = homeUiState,
selectView = viewmodel::selectView,
isTableView = isTableView,
)
HomeBody(
homeUiState = homeUiState,
items = items,
isTableView = isTableView,
onItemClick = navigateToEditEntry,
modifier = modifier
.fillMaxWidth()
.padding(0.dp),
contentPadding = innerPadding,
)
}
}
}
@Composable
private fun HomeHeader(
modifier: Modifier = Modifier,
homeUiState: HomeUiState,
selectView: (Boolean) -> Unit,
isTableView: Boolean,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
//Switch table/list view//
Text(
text = "View:",
textAlign = TextAlign.Start,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
modifier = Modifier
.padding(0.dp)
)
IconButton(
onClick = { selectView(!isTableView) },
modifier = Modifier
.padding(2.dp)
.size(28.dp)
.align(Alignment.Bottom)
) {
Icon(
painter = painterResource(homeUiState.toggleIcon),
contentDescription = stringResource(homeUiState.toggleContentDescription),
tint = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.size(24.dp)
.padding(0.dp)
)
}
}
}
@Composable
private fun HomeBody(
modifier: Modifier = Modifier,
homeUiState: HomeUiState,
items: List<Items>,
isTableView: Boolean,
onItemClick: (Int) -> Unit,
contentPadding: PaddingValues = PaddingValues(0.dp),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier = Modifier
.fillMaxWidth()
.padding(0.dp)
) {
if (items.isEmpty()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
) {
Text(
text = stringResource(R.string.no_items),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier
.padding(0.dp),
)
}
}
else {
if (isTableView) {
TableViewMode(
itemsList = homeUiState.items,
contentPadding = contentPadding,
modifier = Modifier
.padding(0.dp),
)
}
else {
ListViewMode(
itemsList = homeUiState.items,
onItemClick = { onItemClick(it.id) },
contentPadding = contentPadding,
modifier = Modifier
.padding(0.dp)
.fillMaxWidth()
)
}
}
}
}
@Composable
fun TableViewMode(
itemsList: List<Items>,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
){
CellarLazyTable(
itemsTbl = itemsList,
contentPadding = contentPadding,
modifier = modifier
.fillMaxWidth()
.padding(0.dp)
)
}
@Composable
fun ListViewMode(
itemsList: List<Items>,
onItemClick: (Items) -> Unit,
contentPadding: PaddingValues = PaddingValues(0.dp),
modifier: Modifier = Modifier,
){
LazyColumn(
modifier = modifier
.fillMaxWidth()
.padding(0.dp),
){
items(items = itemsList, key = { it.id }) { item ->
CellarListItem(
item = item,
modifier = Modifier
.padding(0.dp)
.clickable {
onItemClick(item)
}
)
}
}
}
ГлавнаяViewМодель:
class HomeViewModel(
itemsRepository: ItemsRepository,
private val preferencesRepo: PreferencesRepo
): ViewModel() {
companion object {
private const val TIMEOUT_MILLIS = 5_000L
}
val homeUiState: StateFlow<HomeUiState> =
merge(
itemsRepository.getAllItemsStream().map { HomeUiState(it) },
preferencesRepo.isTableView.map { isTableView -> HomeUiState(isTableView = isTableView) },
).stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
initialValue = HomeUiState()
)
/* Toggle Cellar View */
fun selectView(isTableView: Boolean) {
viewModelScope.launch {
preferencesRepo.saveViewPreference(isTableView)
}
}
}
data class HomeUiState(
val items: List<Items> = listOf(),
val isTableView: Boolean = true,
val toggleContentDescription: Int =
if (isTableView) R.string.list_view_toggle else R.string.table_view_toggle,
val toggleIcon: Int =
if (isTableView) R.drawable.list_view else R.drawable.table_view,
)
merge
просто берет последний экземпляр из любого из потоков, которые вы с ним используете. Если вы добавите onEach
между merge
и stateIn
, вы обнаружите, что ваш экземпляр HomeUiState(isTableView = isTableView)
(без данных) заменяется вашими HomeUiState(it)
и наоборот, когда ваши данные изменяются - merge
объединяет потоки данных, он не знает, как волшебным образом объединить два ваших HomeUiState
объекта.
Вместо merge
вам, вероятно, нужно объединить, поскольку именно это позволяет вам написать код для создания окончательного объекта HomeUiState
:
val homeUiState: StateFlow<HomeUiState> =
combine(
itemsRepository.getAllItemsStream(),
preferencesRepo.isTableView
) { items, isTableView ->
HomeUiState(items, isTableView)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
initialValue = HomeUiState()
)
Ваш вопрос был действительно хорош. Объяснения и фрагменты кода позволили легко все просмотреть. Желаем удачи в дальнейшем!
Кстати, это сработало. Теперь мне нужно выяснить, почему мой «onItemClick» не переходит в пункт назначения редактирования элемента. Я уже видел, где забыл добавить аргументы в навигационный график, не уверен, что удаление «.map» в сочетании с домом Модель просмотра - это то, что это сделало, или я где-то забыл что-то еще. Мне нужно больше прочитать о том, что это означает/делает (.map). Я попробую разобраться в этом вопросе самостоятельно.
Ах, я пытался использовать объединение раньше, но продолжал получать ошибки, и я не мог понять примеры объединения, которые я видел (все они делали разные вещи), чтобы понять, как его написать. У меня есть только смутное представление о том, как работает State (возможно, мне стоит пройти более продвинутую лабораторию по коду о состояниях пользовательского интерфейса и моделях представления). Я подумал, что это должна быть проблема в потоке состояний. Я проверю это позже, и если это исправит, я отмечу это как решение. Заранее спасибо, но, по крайней мере, это поможет мне лучше понять потоки состояний. Примечание: у вас есть идеи по лучшему названию моего вопроса?