Open In App

Android Jetpack Compose – Swipe-to-Dismiss with Material 3

Last Updated : 04 Jul, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

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.

Frame-3-(3).png

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:

  1. sender: This property stores the name or email address of the sender who sent the email message.
  2. 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() {
  
    // StateFlow to hold the list of email messages
    private val _messagesState = MutableStateFlow(emptyList<EmailMessage>())
    val messagesState: StateFlow<List<EmailMessage>> = _messagesState.asStateFlow()
  
    init {
        // Initialize the list of email messages 
          // when the ViewModel is created
        _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
        }
    }
  
    /**
     * Generates a list of sample email messages.
     * @return The list of sample email messages.
     */
   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(
              // make sure add baseline_archive_24 resource to drawable folder
            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
  
/**
 * Composable representing an email item with swipe-to-dismiss functionality.
 *
 * @param emailMessage The email message to display.
 * @param onRemove Callback invoked when the email item is dismissed.
 */
@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()) {
    // Collect the state of messages from the view model
    val messages by emailViewModel.messagesState.collectAsState()
  
    MaterialTheme {
        Scaffold(
            topBar = {
                TopAppBar(
                    title = { Text(text = "Email App") },
                    actions = {
                        // Refresh button
                        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,
                    // Provide a unique key based on the email content
                    key = { _, item -> item.hashCode() }
                ) { _, emailContent ->
                    // Display each email item
                    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.



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads