I have a screen code like this (Shows a simple list).
What I'm seeking, is to delete the item when it was clicked.
How can I achieve this?
HorizontalScroller {
Row(modifier = Spacing(bottom = 16.dp, right = 16.dp)) {
posts.forEach { post ->
WidthSpacer(16.dp)
Clickable(onClick = {
// Delete the PostCardPopular I just added if it was clicked
}) {
PostCardPopular(post)
}
}
}
}
you can achieve this using a model Object and ModelList
#Model
object YourModel {
val posts = ModelList<Post>()
}
.
.
.
HorizontalScroller {
Row(modifier = Spacing(bottom = 16.dp, right = 16.dp)) {
for(post in YourModel.posts)
WidthSpacer(16.dp)
Clickable(onClick = {
YourModel.posts.remove(post)
}) {
PostCardPopular(post)
}
}
}
}
When you remove from ModelList, the UI will recompose.
Extra: Google releases a codelab for basic compose. https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/
Related
How to make UI changes in a paginated list with jetpack compose.
Use case
I have a paginated list which has data name(string) and like(boolean). If i click on the particular item in the list, i need to place a like button in the UI. But the image is not updated in UI based on condition.
Snippet
userList -> LazyPagingItems<AllDoctorsResponse.Data.Doctor>
//viewModel
userList.itemSnapshotList.find { it?.id == user.id }?.liked = true
// Composable
items(userList.itemCount){ index ->
userList[index]?.let {
if (it.liked == true) {
UserCardWithLike(it, onClick = { userId ->
onUserCardClicked(userId)
}, onLikeChange = { isLiked, user ->
onLikeChange(isLiked, user)
})
} else {
UserCard(it, onClick = { userId ->
onUserCardClicked(userId)
}, onLikeChange = { isLiked, user ->
onLikeChange(isLiked, user)
})
}
}
}
I don't use paging3 anymore, so I didn't test this code, but I think it will work:
items(userList.itemCount){ index ->
userList[index]?.let {
var liked by rememberSaveable { mutableStateOf(it.liked) }
if (liked == true) {
UserCardWithLike(
it,
onClick = { userId -> onUserCardClicked(userId) },
onLikeChange = { isLiked, user -> liked = false }
)
} else {
UserCard(
it,
onClick = { userId -> onUserCardClicked(userId) },
onLikeChange = { isLiked, user -> liked = true }
)
}
}
}
If you want something like notifyItemChange, it's not possible in Paging3. In that case, I suggest trying to rewrite the paging library, which is surprisingly easy.
https://gist.github.com/FishHawk/6e4706646401bea20242bdfad5d86a9e
How to create swipe to refresh in Jetpack compose using kotlin? Please Share proper reference link
SwipeRefresh is not available
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = { },
) {
LazyColumn {
}
}
Now you can use the built-in pullRefresh modifier.
Something like:
val refreshScope = rememberCoroutineScope()
var refreshing by remember { mutableStateOf(false) }
fun refresh() = refreshScope.launch {
refreshing = true
//...do something
refreshing = false
}
val state = rememberPullRefreshState(refreshing, ::refresh)
Box(Modifier.pullRefresh(state)) {
LazyColumn(Modifier.fillMaxSize()) {
if (!refreshing) {
items(itemCount) {
//...
}
}
}
PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter))
}
To create a swipe-to-refresh layout, we need to add dependency in buld.gradle which will provide swipe to refresh layout just like SwipeRefreshLayout in traditional android.
implementation 'com.google.accompanist:accompanist-swiperefresh:0.24.13-rc'
..
To create this kind of layout we require two APIs one SwipeRefresh for layout and another rememberSwipeRefreshState which will remember the state.
#Composable
fun SwipeRefreshCompose() {
var refreshing by remember { mutableStateOf(false) }
LaunchedEffect(refreshing) {
if (refreshing) {
delay(3000)
refreshing = false
}
}
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing = refreshing),
onRefresh = { refreshing = true },
) {
// list view
}
}
In the following viewModel code I am generating a list of items from graphQl server
private val _balloonsStatus =
MutableStateFlow<Status<List<BalloonsQuery.Edge>?>>(Status.Loading())
val balloonsStatus get() = _balloonsStatus
private val _endCursor = MutableStateFlow<String?>(null)
val endCursor get() = _endCursor
init {
loadBalloons(null)
}
fun loadBalloons(cursor: String?) {
viewModelScope.launch {
val data = repo.getBalloonsFromServer(cursor)
if (data.errors == null) {
_balloonsStatus.value = Status.Success(data.data?.balloons?.edges)
_endCursor.value = data.data?.balloons?.pageInfo?.endCursor
} else {
_balloonsStatus.value = Status.Error(data.errors!![0].message)
_endCursor.value = null
}
}
}
and in the composable function I am getting this data by following this code:
#Composable
fun BalloonsScreen(
navHostController: NavHostController? = null,
viewModel: SharedBalloonViewModel
) {
val endCursor by viewModel.endCursor.collectAsState()
val balloons by viewModel.balloonsStatus.collectAsState()
AssignmentTheme {
Column(modifier = Modifier.fillMaxSize()) {
when (balloons) {
is Status.Error -> {
Log.i("Reyjohn", balloons.message!!)
}
is Status.Loading -> {
Log.i("Reyjohn", "loading..")
}
is Status.Success -> {
BalloonList(edgeList = balloons.data!!, navHostController = navHostController)
}
}
Spacer(modifier = Modifier.weight(1f))
Button(onClick = { viewModel.loadBalloons(endCursor) }) {
Text(text = "Load More")
}
}
}
}
#Composable
fun BalloonList(
edgeList: List<BalloonsQuery.Edge>,
navHostController: NavHostController? = null,
) {
LazyColumn {
items(items = edgeList) { edge ->
UserRow(edge.node, navHostController)
}
}
}
but the problem is every time I click on Load More button it regenerates the view and displays a new set of list, but I want to append the list at the end of the previous list. As far I understand that the list is regenerated as the flow I am listening to is doing the work behind this, but I am stuck here to get a workaround about how to achieve my target here, a kind hearted help would be much appreciated!
You can create a private list in ViewModel that adds List<BalloonsQuery.Edge>?>
and instead of
_balloonsStatus.value = Status.Success(data.data?.balloons?.edges)
you can do something like
_balloonsStatus.value = Status.Success(myLiast.addAll(
data.data?.balloons?.edges))
should update Compose with the latest data appended to existing one
When I select item inside LazyColumn and navigate to this item I can interact with other items from previous screen(item list). Any ideas?
LazyColumn
LazyColumn {
val postList = homeViewModel.state.postList.value
postList?.let {
items(it) { post ->
PostL(
onPostClick = { navigateToPostDetails(post) },
post
)
}
}
}
navigateToPostDetails
fun navigateToPostDetails(post: Post) {
val postJson = Gson().toJson(post)
appNavController.navigate("postDetails/$postJson")
}
I'm currently playing around with the new Jetpack compose UI toolkit and I like it a lot. One thing I could not figure out is how to use stickyHeaders in a LazyColumn which is populated by the paging library. The non-paging example from the documentation is:
val grouped = contacts.groupBy { it.firstName[0] }
fun ContactsList(grouped: Map<Char, List<Contact>>) {
LazyColumn {
grouped.forEach { (initial, contactsForInitial) ->
stickyHeader {
CharacterHeader(initial)
}
items(contactsForInitial) { contact ->
ContactListItem(contact)
}
}
}
}
Since I'm using the paging library I cannot use the groupedBy so I tried to use the insertSeparators function on PagingData and insert/create the headers myself like this (please ignore the legacy Date code, it's just for testing):
// On my flow
.insertSeparators { before, after ->
when {
before == null -> ListItem.HeaderItem(after?.workout?.time ?: 0)
after == null -> ListItem.HeaderItem(before.workout.time)
(Date(before.workout.time).day != Date(after.workout.time).day) ->
ListItem.HeaderItem(before.workout.time)
// Return null to avoid adding a separator between two items.
else -> null
}
}
// In my composeable
LazyColumn {
items(workoutItems) {
when(it) {
is ListItem.HeaderItem -> this#LazyColumn.stickyHeader { Header(it) }
is ListItem.SongItem -> WorkoutItem(it)
}
}
}
But this produces a list of all my items and the header items are appended at the end. Any ideas what is the right way to use the stickyHeader function when using the paging library?
I got it to work by looking into the source code of the items function: You must not call stickyHeader within the items function. No need to modify the PagingData flow at all. Just use peek to get the next item without triggering a reload and then layout it:
LazyColumn {
val itemCount = workoutItems.itemCount
var lastWorkout: Workout? = null
for(index in 0 until itemCount) {
val workout = workoutItems.peek(index)
if(lastWorkout?.time != workout?.time) stickyHeader { Header(workout) }
item { WorkoutItem(workoutItems.getAsState(index).value) } // triggers reload
lastWorkout = workout
}
}
I believe the issue in your code was that you were calling this#LazyColumn from inside an LazyItemScope.
I experimented too with insertSeparators and reached this working LazyColumn code:
LazyColumn {
for (index in 0 until photos.itemCount) {
when (val peekData = photos.peek(index)) {
is String? -> stickyHeader {
Text(
text = (photos.getAsState(index).value as? String).orEmpty(),
)
}
is Photo? -> item(key = { peekData?.id }) {
val photo = photos.getAsState(index).value as? Photo
...
}
}
}
}