Android Jetpack Compose – Swipe-to-Dismiss with Material 3
Last Updated :
04 Jul, 2023
Swipe-to-dismiss functionality is a widely-used interaction pattern that allows users to easily remove items from a list or dismiss cards by swiping them off the screen. In this article, we will explore how to implement swipe-to-dismiss in Jetpack Compose with the new Material 3 components. We will use an email app as an example to demonstrate the usage of swipe-to-dismiss in a real-world scenario.
swipe to dismiss with jetpack compose m3
Overview of Swipe-to-Dismiss
Swipe-to-dismiss is a gesture-based interaction pattern that allows users to remove items from a list or dismiss cards by swiping them horizontally. It provides a convenient way for users to interact with content and manage their data efficiently. Implementing swipe-to-dismiss in Jetpack Compose with Material 3 involves using the SwipeToDismiss
composable along with the new Material 3 components. A sample video is given below to get an idea about what we are going to do in this article.
Step by Step Implementation
Step 1: Create a New Project in Android Studio
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. While choosing the template, select Empty Compose Activity. If you do not find this template, try upgrading the Android Studio to the latest version. We demonstrated the application in Kotlin, so make sure you select Kotlin as the primary language while creating a New Project.
Step 2: Setting up the Email App
Add the following dependencies to the app/build.gradle file.
dependencies {
implementation "androidx.core:core-ktx:1.10.1"
implementation "androidx.annotation:annotation:1.6.0"
implementation "androidx.activity:activity-compose:1.7.2"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"
implementation platform("androidx.compose:compose-bom:2023.06.00")
implementation "androidx.compose.ui:ui"
implementation "androidx.compose.ui:ui-tooling-preview"
implementation "androidx.compose.material3:material3"
implementation "androidx.compose.foundation:foundation"
androidTestImplementation platform("androidx.compose:compose-bom:2023.06.00")
androidTestImplementation "androidx.compose.ui:ui-test-junit4"
debugImplementation "androidx.compose.ui:ui-tooling"
debugImplementation "androidx.compose.ui:ui-test-manifest"
}
To demonstrate the swipe-to-dismiss functionality, we will create a simple email app using the new Material 3 components. The app will display a list of email messages, and users will be able to swipe left or right to dismiss individual messages. We will use Jetpack Compose LazyColumn
to display the list of email items.
EmailMessage
The EmailMessage
data class represents an email message within the email application. It contains two properties:
sender
: This property stores the name or email address of the sender who sent the email message.
message
: This property stores the content or body of the email message.
The EmailMessage
data class is used to encapsulate the essential information related to an email message, allowing easy access and manipulation of its sender and message content.
Kotlin
data class EmailMessage(
val sender: String,
val message: String
)
|
EmailViewModel
The EmailViewModel
class is responsible for managing email messages in the email application. It has the following properties and methods:
Properties:
messagesState
: A StateFlow
that represents the current list of email messages.
Methods:
refresh()
: Refreshes the list of email messages.
removeItem(currentItem: EmailMessage)
: Removes a specific email message from the list.
The EmailViewModel
class acts as the intermediary between the UI components and the data source, handling the state and operations related to email messages. It provides methods for updating and manipulating the list of messages.
Kotlin
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class EmailViewModel : ViewModel() {
private val _messagesState = MutableStateFlow(emptyList<EmailMessage>())
val messagesState: StateFlow<List<EmailMessage>> = _messagesState.asStateFlow()
init {
_messagesState.update { sampleMessages() }
}
/**
* Refreshes the list of email messages.
*/
fun refresh() {
_messagesState.update {
sampleMessages()
}
}
/**
* Removes an email message from the list.
* @param currentItem The email message to be removed.
*/
fun removeItem(currentItem: EmailMessage) {
_messagesState.update {
val mutableList = it.toMutableList()
mutableList.remove(currentItem)
mutableList
}
}
private fun sampleMessages() = listOf(
EmailMessage( "John Doe" , "Hello" ),
EmailMessage( "Alice" , "Hey there! How's it going?" ),
EmailMessage( "Bob" , "I just discovered a cool new programming language!" ),
EmailMessage( "Geek" , "Have you seen the latest tech news? It's fascinating!" ),
EmailMessage( "Mark" , "Let's grab a coffee and talk about coding!" ),
EmailMessage( "Cyan" , "I need help with a coding problem. Can you assist me?" ),
)
}
|
Step 3: Implementing the Email Item
Each email item in the list will be represented by the EmailMessageCard
composable. This composable will display the sender’s name, message content, and a person icon using the Material 3 components. The EmailMessageCard
will be responsible for rendering the visual representation of the email message.
Kotlin
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
/**
* Composable that represents a single email message card.
*
* @param emailMessage The email message to display.
*/
@Composable
fun EmailMessageCard(emailMessage: EmailMessage) {
ListItem(
modifier = Modifier.clip(MaterialTheme.shapes.small),
headlineContent = {
Text(
emailMessage.sender,
style = MaterialTheme.typography.titleMedium
)
},
supportingContent = {
Text(
emailMessage.message,
style = MaterialTheme.typography.bodySmall
)
},
leadingContent = {
Icon(
Icons.Filled.Person,
contentDescription = "person icon" ,
Modifier
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primaryContainer)
.padding( 10 .dp)
)
}
)
}
|
Step 4: Creating the Dismiss Background:
To provide visual feedback during the swipe-to-dismiss action, we need to create the DismissBackground
composable. This composable will use the new Material 3 components to display a background color and an appropriate icon based on the swipe direction. The DismissBackground
composable will be used as the background for each email item.
Kotlin
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@OptIn (ExperimentalMaterial3Api:: class )
@Composable
fun DismissBackground(dismissState: DismissState) {
val color = when (dismissState.dismissDirection) {
DismissDirection.StartToEnd -> Color( 0xFFFF1744 )
DismissDirection.EndToStart -> Color( 0xFF1DE9B6 )
null -> Color.Transparent
}
val direction = dismissState.dismissDirection
Row(
modifier = Modifier
.fillMaxSize()
.background(color)
.padding( 12 .dp, 8 .dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
if (direction == DismissDirection.StartToEnd) Icon(
Icons.Default.Delete,
contentDescription = "delete"
)
Spacer(modifier = Modifier)
if (direction == DismissDirection.EndToStart) Icon(
painter = painterResource(R.drawable.baseline_archive_24),
contentDescription = "Archive"
)
}
}
|
Step 5: Incorporating Swipe-to-Dismiss
To enable swipe-to-dismiss functionality, we will use the SwipeToDismiss
composable provided by the Material 3 library. The SwipeToDismiss
composable allows us to wrap the email item and the dismiss background together. It provides the necessary gesture handling and animations for the swipe-to-dismiss action, leveraging the power of Material 3.
Kotlin
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeOut
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
@OptIn (ExperimentalMaterial3Api:: class )
@Composable
fun EmailItem(
emailMessage: EmailMessage,
onRemove: (EmailMessage) -> Unit
) {
val context = LocalContext.current
var show by remember { mutableStateOf( true ) }
val currentItem by rememberUpdatedState(emailMessage)
val dismissState = rememberDismissState(
confirmValueChange = {
if (it == DismissValue.DismissedToStart || it == DismissValue.DismissedToEnd) {
show = false
true
} else false
}, positionalThreshold = { 150 .dp.toPx() }
)
AnimatedVisibility(
show,exit = fadeOut(spring())
) {
SwipeToDismiss(
state = dismissState,
modifier = Modifier,
background = {
DismissBackground(dismissState)
},
dismissContent = {
EmailMessageCard(emailMessage)
}
)
}
LaunchedEffect(show) {
if (!show) {
delay( 800 )
onRemove(currentItem)
Toast.makeText(context, "Item removed" , Toast.LENGTH_SHORT).show()
}
}
}
|
Step 6: Running the application
Once we have implemented the swipe-to-dismiss functionality, it’s important to thoroughly check it. We should verify that swiping left or right on an email item correctly triggers the dismiss action and removes the item from the list. The SwipeToDismissActivity
is an Android ComponentActivity
a subclass that serves as the entry point for the Swipe to Dismiss feature in the email application. In the onCreate()
method, the activity sets its content using the setContent
function, which inflates the UI layout and displays the EmailApp
composable. Run the application and check the output.
Kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
class SwipeToDismissActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super .onCreate(savedInstanceState)
setContent {
EmailApp()
}
}
}
@OptIn (ExperimentalMaterial3Api:: class )
@Preview
@Composable
fun EmailApp(emailViewModel: EmailViewModel = viewModel()) {
val messages by emailViewModel.messagesState.collectAsState()
MaterialTheme {
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "Email App" ) },
actions = {
IconButton(onClick = emailViewModel::refresh) {
Icon(Icons.Filled.Refresh, contentDescription = "Refresh" )
}
}
)
}
) { paddingValues ->
LazyColumn(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize(),
contentPadding = PaddingValues(vertical = 12 .dp),
) {
itemsIndexed(
items = messages,
key = { _, item -> item.hashCode() }
) { _, emailContent ->
EmailItem(emailContent, onRemove = emailViewModel::removeItem)
}
}
}
}
}
|
Output:
Conclusion
In this article, we explored the implementation of swipe-to-dismiss functionality in Jetpack Compose with the new Material 3 components. By incorporating swipe-to-dismiss using the SwipeToDismiss
composable and leveraging the Material 3 components, we can provide a seamless and visually pleasing user experience in our apps. Swipe-to-dismiss enhances the interaction and usability of our apps, allowing users to efficiently manage their content.
Share your thoughts in the comments
Please Login to comment...