2

I have a Room app which displays a list of presets, and the user has the option of adding them by pressing a 'New Quick Preset' button:

Code for inserting new item into Room database (in ViewModel):

fun insertQuickPreset(quickPreset: QuickPreset) {
    viewModelScope.launch {
        repository.insertQuickPreset(quickPreset)
    }
}

State:

data class NewScreenState(
    @StringRes val nameError: Int? = null,
    @StringRes val widthError: Int? = null,
    @StringRes val heightError: Int? = null,
    val name: String = "",
    val width: String = "",
    val height: String = "",
    val quickPresets: List<QuickPreset>
)

Repo:

suspend fun insertQuickPreset(quickPreset: QuickPreset) {
    return withContext(Dispatchers.IO) {
        dao.insert(quickPreset)
    }
}

Composable:

Button(
    modifier = Modifier.align(Alignment.End),
    onClick = {
        viewModel.insertQuickPreset(QuickPreset(55,52))
    }
) {
    Text(
        "New Quick Preset"
    )
}

The issue is, when the user taps 'New Quick Preset', the state doesn't get updated, so for the user to see the results they need to go back and then press the button to enter the screen again.

I took a course in Compose and they never demonstrated how to add items, only how to fetch them. I have the code for adding an item, although I have no idea how I would update the state from here to reflect the added values? I usually utilize the .copy method but this is only for when the database contents are first fetched.

z.g.y
  • 5,512
  • 4
  • 10
  • 36
thebluepandabear
  • 263
  • 2
  • 7
  • 29

1 Answers1

1

Use SnapshotStateList.

val quickPresets: SnapShotStateList

and having it initialized like this way

val list = mutableStateListOf( your quick presets )

Any updates you do to a SnapshotStateList, such as

  • add new quickPreset
  • remove a quickPreset
  • update a quickPreset via .copy()

is guaranteed to perform a re-composition, assuming its being observed by a composable somewehere

Edit: Here is a snippet of my own database implementation

Repository

override fun fetchPeople(): Flow<List<People>> =
        peopleDao.getPeople().map { entityList ->
            entityList.map { entity ->
                entity.toModel()
            }
    }

override suspend fun savePerson(PersonModel: PersonModel): Long {
        return peopleDao.insertPerson(PersonModel.toEntity())
}

override suspend fun deletePerson(PersonModel: PersonModel) {
        peopleDao.deletePerson(PersonModel.toEntity())
}

And I'm just observing it via ViewModel

private val _people = mutableStateListOf<Person>()
val people: List<Person> = _people

...
...

// no need for any 'get/fectch' call, changes being made
// to the repository (e.g add or delete) will be propagated back via `flow`
viewModelScope.launch {
      repository.fetchPeople()
        .collect {
             _people.addAll(it)
        }
}

For your scrolling issue, you can do something like this,

 val scrollState = rememberLazyListState()
 val coroutineScope = rememberCoroutineScope()
    
 // scrollToItem after a successful `re-composition`
 SideEffect {
     coroutineScope.launch {
        scrollState.scrollToItem(index)
     }
 }

Let the composable that contains your list finish what ever it needs to do, and do the scrollToItem after its successful re-composition

z.g.y
  • 5,512
  • 4
  • 10
  • 36
  • I didn't ask though, my impression is with your question is that a `Composable` is not being updated by any changes you make in a `list`, but I assume you verify that any updates made to your database is being propagated back to your `view model`, like its implemented as `Flow` or you're performing a `dao.get()` something? – z.g.y Oct 17 '22 at 05:18
  • Not really sure what you mean, but after I add the item I do this: ```_state.value.quickPresets.add(quickPreset)``` And to preload i do: ``` val quickPresets = repository.getQuickPresets() _state.value = _state.value.copy( quickPresets = quickPresets.toMutableStateList() ) ``` Seems to work fine, the only issue I have is that it doesn't scroll to the bottom of the LazyRow, so I simply use `listState.animateScrollToItem` after it has been added, is that normal? (Sorry I am new to Compose.) – thebluepandabear Oct 17 '22 at 05:22
  • I'm assuming you're using `Room` database, I udpated my answer to show a snippet on how I communicate with my database and update my `Composable` when perform operations such as `add`, though did my previous answer work? – z.g.y Oct 17 '22 at 05:34
  • thanks , when you add items do you programmatically scroll to the end of the lazycolumn/lazyrow? (just making sure I am doing things properly), cuz if I don't, when I add new items it doesn't scroll to it, so it looks like a new item hasn't been added – thebluepandabear Oct 17 '22 at 05:36
  • So the problem now is the `scrolling`? , well I can only assume, but have your tried performing that call inside a `SideEffect`?, there are situations where `scrollToItem` or `animateToScroll` would not work if the current composable is in the middle of `re-composition`, but aside from that, I have no idea why your `scrollToItem` is not working, Ill edit my answer again for your scrolling problem, this may not solve yours as its different from mine, but you can give it a shot – z.g.y Oct 17 '22 at 05:39
  • 1
    no it is working, I was just wondering whether or not it is a **recommended practise** to ensure I am doing things properly. Also, thank u sir for ur effort in helping me. – thebluepandabear Oct 17 '22 at 05:40
  • I'm afraid we could get flag by spamming the comments, so I edited my answer to give you an idea how I do my `LazyList scrolling` safely in certain situations. And as far as I'm aware at `practices`, I think there's nothing being violated with the codes we have in this post, and you're welcome, also thanks for accepting the answer! – z.g.y Oct 17 '22 at 05:51
  • 1
    The snippet of my `SideEffect{ scrollTo }`, requires another logic and state that would trigger the containing composable to `re-compose` so the `SideEffect` will run. The pseudo would be, "when the list is updated, trigger another state so a `SideEffect` will be called to execute `scrollToIndex`", but the way you implement it via `LaunchEffect` is also another way. – z.g.y Oct 18 '22 at 03:25