Building a Searchable List in Jetpack Compose with SearchView

Last updated Jan 05, 2025

In this Jetpack compose tutorial, we'll learn how to implement a SearchView with a filtered list in Jetpack Compose. The search view will allow users to type text, and the list below will be filtered in real-time based on the search query. Android SearchView facilitates a seamless search experience. Users can input text, and the list below instantly adjusts to reflect the entered query, providing a refined and relevant set of results.

 

Step 1: Create Data Model

First, let's create a data class to hold our list items

data class Person(
    val id: Int,
    val name: String,
    val email: String
)

 

Step 2: Create the Search View Component

Here's our main search view implementation:

@Composable
fun SearchView(
    modifier: Modifier = Modifier,
    hint: String = "Search...",
    onQueryChange: (String) -> Unit
) {
    var query by remember { mutableStateOf("") }

    OutlinedTextField(
        value = query,
        onValueChange = { newQuery ->
            query = newQuery
            onQueryChange(newQuery)
        },
        modifier = modifier
            .fillMaxWidth()
            .padding(16.dp),
        placeholder = {
            Text(
                text = hint,
                color = Color.Gray
            )
        },
        leadingIcon = {
            Icon(
                imageVector = Icons.Default.Search,
                contentDescription = "Search Icon",
                tint = Color.Gray
            )
        },
        trailingIcon = {
            if (query.isNotEmpty()) {
                IconButton(
                    onClick = {
                        query = ""
                        onQueryChange("")
                    }
                ) {
                    Icon(
                        imageVector = Icons.Default.Clear,
                        contentDescription = "Clear Icon",
                        tint = Color.Gray
                    )
                }
            }
        },
        singleLine = true,
        shape = RoundedCornerShape(12.dp),
        colors = TextFieldDefaults.outlinedTextFieldColors(
            focusedBorderColor = MaterialTheme.colors.primary,
            unfocusedBorderColor = Color.Gray
        )
    )
}

 

Step 3: Create the Filterable List Component

Now, let's create a component to display our filtered list

@Composable
fun FilterableList(
    items: List,
    query: String
) {
    val filteredItems = remember(query, items) {
        if (query.isEmpty()) {
            items
        } else {
            items.filter { item ->
                item.name.contains(query, ignoreCase = true) ||
                item.email.contains(query, ignoreCase = true)
            }
        }
    }

    LazyColumn(
        modifier = Modifier.fillMaxWidth(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(filteredItems) { person ->
            PersonCard(person = person)
        }
    }
}

@Composable
fun PersonCard(person: Person) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp),
        elevation = 4.dp
    ) {
        Column(
            modifier = Modifier
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text(
                text = person.name,
                style = MaterialTheme.typography.h6
            )
            Spacer(modifier = Modifier.height(4.dp))
            Text(
                text = person.email,
                style = MaterialTheme.typography.body2,
                color = Color.Gray
            )
        }
    }
}

 

Step 4: Implement the Main Screen

Here's how we put everything together in our main screen:

@Composable
fun SearchScreen() {
    var searchQuery by remember { mutableStateOf("") }
    
    // Sample data
    val people = remember {
        listOf(
            Person(1, "John Doe", "john.doe@example.com"),
            Person(2, "Jane Smith", "jane.smith@example.com"),
            Person(3, "Bob Johnson", "bob.johnson@example.com"),
            Person(4, "Alice Brown", "alice.brown@example.com"),
            Person(5, "Charlie Wilson", "charlie.wilson@example.com")
        )
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.White)
    ) {
        // Top app bar
        TopAppBar(
            title = { Text("Search People") },
            backgroundColor = MaterialTheme.colors.primary,
            contentColor = Color.White
        )

        // Search view
        SearchView(
            hint = "Search by name or email...",
            onQueryChange = { query ->
                searchQuery = query
            }
        )

        // Filtered list
        FilterableList(
            items = people,
            query = searchQuery
        )
    }
}

 

 

Usage

To use this search functionality in your app:

  1. Add the required imports

import androidx.compose.animation.*

import androidx.compose.foundation.background

import androidx.compose.foundation.layout.*

import androidx.compose.foundation.lazy.LazyColumn

import androidx.compose.foundation.lazy.items

import androidx.compose.foundation.shape.RoundedCornerShape

import androidx.compose.material.*

import androidx.compose.material.icons.Icons

import androidx.compose.material.icons.filled.Clear

import androidx.compose.material.icons.filled.Search

import androidx.compose.material3.Card

import androidx.compose.material3.CardDefaults

import androidx.compose.material3.CardElevation

import androidx.compose.material3.ElevatedCard

import androidx.compose.material3.ExperimentalMaterial3Api

import androidx.compose.material3.Icon

import androidx.compose.material3.IconButton

import androidx.compose.material3.MaterialTheme

import androidx.compose.material3.OutlinedTextField

import androidx.compose.material3.Text

import androidx.compose.material3.TextFieldDefaults

import androidx.compose.material3.TopAppBar

import androidx.compose.material3.TopAppBarColors

import androidx.compose.runtime.*

import androidx.compose.ui.Modifier

import androidx.compose.ui.graphics.Color

import androidx.compose.ui.tooling.preview.Preview

import androidx.compose.ui.unit.dp

 

2. Set up your theme in your MainActivity:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourAppTheme {
                SearchScreen()
            }
        }
    }
}

 

Features

This implementation includes:

  • Real-time filtering as user types
  • Clear button to reset search
  • Smooth animations with LazyColumn
  • Material Design styling
  • Support for multiple search criteria (name and email)
  • Responsive layout
  • Clean, reusable components

 

Customization Options for Searchview implementation

You can customize the appearance by:

  1. Modifying the card design

ElevatedCard (

   modifier = Modifier

       .fillMaxWidth()

       .padding(vertical = 4.dp),

  elevation = CardDefaults.cardElevation(),

   shape = RoundedCornerShape(8.dp,8.dp,8.dp,8.dp)

)

 

2. Changing the search view style:

SearchView(
    modifier = Modifier
        .fillMaxWidth()
        .padding(16.dp)
        .height(56.dp),
    hint = "Custom hint...",
    // Custom colors and shapes
)

 

3. Adding animations:

AnimatedVisibility(
    visible = query.isNotEmpty(),
    enter = fadeIn() + slideInVertically(),
    exit = fadeOut() + slideOutVertically()
) {
    // Filtered content
}

 

Best Practices

  1. State Management: Use remember and mutableStateOf for local state
  2. Reusability: Create modular components that can be reused
  3. Performance: Use remember(key) for expensive computations
  4. User Experience: Provide clear feedback and smooth animations
  5. Error Handling: Handle empty states and loading states appropriately

 

Complete Code for Jetpack Compose Searchview implementation 

import androidx.compose.animation.*

import androidx.compose.foundation.background

import androidx.compose.foundation.layout.*

import androidx.compose.foundation.lazy.LazyColumn

import androidx.compose.foundation.lazy.items

import androidx.compose.foundation.shape.RoundedCornerShape

import androidx.compose.material.*

import androidx.compose.material.icons.Icons

import androidx.compose.material.icons.filled.Clear

import androidx.compose.material.icons.filled.Search

import androidx.compose.material3.Card

import androidx.compose.material3.CardDefaults

import androidx.compose.material3.CardElevation

import androidx.compose.material3.ElevatedCard

import androidx.compose.material3.ExperimentalMaterial3Api

import androidx.compose.material3.Icon

import androidx.compose.material3.IconButton

import androidx.compose.material3.MaterialTheme

import androidx.compose.material3.OutlinedTextField

import androidx.compose.material3.Text

import androidx.compose.material3.TextFieldDefaults

import androidx.compose.material3.TopAppBar

import androidx.compose.material3.TopAppBarColors

import androidx.compose.runtime.*

import androidx.compose.ui.Modifier

import androidx.compose.ui.graphics.Color

import androidx.compose.ui.tooling.preview.Preview

import androidx.compose.ui.unit.dp

 

// Data class for list items

data class Person(

   val id: Int,

   val name: String,

   val email: String

)

 

// Search view component

 

@OptIn(ExperimentalMaterial3Api::class)

@Composable

fun SearchView(

   modifier: Modifier = Modifier,

   hint: String = "Search...",

   onQueryChange: (String) -> Unit

) {

   var query by remember { mutableStateOf("") }

 

   OutlinedTextField(

       value = query,

       onValueChange = { newQuery ->

           query = newQuery

           onQueryChange(newQuery)

       },

       modifier = modifier

           .fillMaxWidth()

           .padding(16.dp),

       placeholder = {

           Text(

               text = hint,

               color = Color.Gray

           )

       },

       leadingIcon = {

           Icon(

               imageVector = Icons.Default.Search,

               contentDescription = "Search Icon",

               tint = Color.Gray

           )

       },

       trailingIcon = {

           if (query.isNotEmpty()) {

               IconButton(

                   onClick = {

                       query = ""

                       onQueryChange("")

                   }

               ) {

                   Icon(

                       imageVector = Icons.Default.Clear,

                       contentDescription = "Clear Icon",

                       tint = Color.Gray

                   )

               }

           }

       },

       singleLine = true,

       shape = RoundedCornerShape(12.dp),

       colors = TextFieldDefaults.outlinedTextFieldColors(

           focusedBorderColor = MaterialTheme.colorScheme.primary,

           unfocusedBorderColor = Color.Gray

       )

   )

}

 

// Filterable list component

@Composable

fun FilterableList(

   items: List,

   query: String

) {

   val filteredItems = remember(query, items) {

       if (query.isEmpty()) {

           items

       } else {

           items.filter { item ->

               item.name.contains(query, ignoreCase = true) ||

                       item.email.contains(query, ignoreCase = true)

           }

       }

   }

 

   LazyColumn(

       modifier = Modifier.fillMaxWidth(),

       contentPadding = PaddingValues(16.dp),

       verticalArrangement = Arrangement.spacedBy(8.dp)

   ) {

       items(filteredItems) { person ->

           PersonCard(person = person)

       }

   }

}

 

// Card component for each person

@Composable

fun PersonCard(person: Person) {

   ElevatedCard (

       modifier = Modifier

           .fillMaxWidth()

           .padding(vertical = 4.dp),

      elevation = CardDefaults.cardElevation()

   ) {

       Column(

           modifier = Modifier

               .padding(16.dp)

               .fillMaxWidth()

       ) {

           Text(

               text = person.name,

               style = MaterialTheme.typography.headlineSmall

           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(

               text = person.email,

               style = MaterialTheme.typography.bodyMedium,

               color = Color.Gray

           )

       }

   }

}

 

// Main screen composable

@OptIn(ExperimentalMaterial3Api::class)

@Preview

@Composable

fun SearchScreen() {

   var searchQuery by remember { mutableStateOf("") }

 

   // Sample data

   val people = remember {

       listOf(

           Person(1, "John Doe", "john.doe@example.com"),

           Person(2, "Jane Smith", "jane.smith@example.com"),

           Person(3, "Bob Johnson", "bob.johnson@example.com"),

           Person(4, "Alice Brown", "alice.brown@example.com"),

           Person(5, "Charlie Wilson", "charlie.wilson@example.com")

       )

   }

 

   Column(

       modifier = Modifier

           .fillMaxSize()

           .background(Color.White)

   ) {

       // Top app bar

       TopAppBar(

           title = { Text("Search People") },

           colors = TopAppBarColors(

               containerColor = MaterialTheme.colorScheme.primary,

               scrolledContainerColor = Color.White,

               navigationIconContentColor = Color.Green,

               titleContentColor = Color.White,

               actionIconContentColor = Color.White

           ),

 

       )

 

       // Search view

       SearchView(

           hint = "Search by name or email...",

           onQueryChange = { query ->

               searchQuery = query

           }

       )

 

       // Filtered list

       FilterableList(

           items = people,

           query = searchQuery

       )

   }

}

 

// Optional: Add animations

@Composable

fun AnimatedFilterableList(

   items: List,

   query: String

) {

   val filteredItems = remember(query, items) {

       if (query.isEmpty()) {

           items

       } else {

           items.filter { item ->

               item.name.contains(query, ignoreCase = true) ||

                       item.email.contains(query, ignoreCase = true)

           }

       }

   }

 

   LazyColumn(

       modifier = Modifier.fillMaxWidth(),

       contentPadding = PaddingValues(16.dp),

       verticalArrangement = Arrangement.spacedBy(8.dp)

   ) {

       items(filteredItems) { person ->

           AnimatedVisibility(

               visible = true,

               enter = fadeIn() + slideInVertically(),

               exit = fadeOut() + slideOutVertically()

           ) {

               PersonCard(person = person)

           }

       }

   }

}

 

 

Example 2 for How to create Searchview in Jetpack Compose LIstview

Step 1: Create android application in android studio

Step 2: Follow step for setup Jetpack Compose with Android Studio

In this example we are managing the data inside listview with Viewmodel and kotlin data classes

Lets create search Item Data

data class SearchData(var name: String? = null, var emailId: String? = null)

 

View Model

package com.example.jetpack.viewmodels

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.example.jetpack.widget.SearchData
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import java.util.*
import kotlin.collections.ArrayList


class SearchViewModel(appObj: Application) : AndroidViewModel(appObj) {
    private val _searchList = MutableStateFlow(listOf())
    val list = arrayListOf(
        SearchData(
            name = "Ankit Singh", emailId = "an@gmail.com"
        ),
        SearchData(
            name = "Neha Shaw", emailId = "ne@gmail.com"
        ),
        SearchData(
            name = "Arpita Ghosh", emailId = "ar@gmail.com"
        ),
        SearchData(
            name = "Akash Tiwari", emailId = "ak@gmail.com"
        ),
        SearchData(
            name = "Anisha Tiwari", emailId = "an@gmail.com"
        ),
        SearchData(
            name = "Rowdy Rathore", emailId = "ro@gmail.com"
        ),
        SearchData(
            name = "Jit Singh", emailId = "ji@gmail.com"
        ),
        SearchData(
            name = "Pravin Raj", emailId = "pr@gmail.com"
        ),
        SearchData(
            name = "Sneha Rao", emailId = "sn@gmail.com"
        ),
        SearchData(
            name = "Ranjana Rathore", emailId = "ran@gmail.com"
        ),
        SearchData(
            "Kamala Rathore", emailId = "ka@gmail.com"
        )
    )
    val searchList: StateFlow> get() = _searchList

    fun searchedItems(searchedText: String) {
        if (searchedText.isNotEmpty()) {
            val resultList = ArrayList()
            for (data in list) {
                if (data.name?.lowercase(Locale.getDefault())
                        ?.contains(searchedText, ignoreCase = true) == true
                ) {
                    resultList.add(data)
                }
            }
            _searchList.value = resultList
        } else {
            _searchList.value = list
        }
    }

    init {
        fetchList()
    }

    private fun fetchList() {
        viewModelScope.launch {
            _searchList.emit(list)
        }
    }
}

 

Here we have used stateFlow which emits list of serachData whenever search data will changed that can be observed while showing list items.

Lets create UI to display data inside listview

@Composable
fun SearchListItem(searchData: SearchData) {
    Card(
        modifier = Modifier
            .padding(top = 16.dp)
            .fillMaxWidth(),
        elevation = 4.dp,
        border = BorderStroke(width = 1.2.dp, Color.Blue)
    ) {
        Row(content = {
            Icon(
                Icons.Default.Contacts,
                "",
                tint = Color.Blue,
                modifier = Modifier
                    .padding(start = 16.dp, top = 16.dp)
            )
            Column(modifier = Modifier.padding(16.dp)) {
                Text(
                    text = "${searchData.name}",
                    style = TextStyle(
                        color = Color.Blue,
                        fontSize = 21.sp,
                        fontWeight = FontWeight.Bold
                    )
                )
                Text(text = "${searchData.emailId}", modifier = Modifier.padding(top = 8.dp))
            }
        })

    }
}

 

Let's create ui for SearchView ( we will create search view with prefix icon as magnifying glass, in the middle we will use text field and in the end cross icon) with SearchViewTextField composable function where we are going the pass the mutable state so that whenever user type any thing it will be save in state and according to typed data list will be rendered.

 

@Composable
fun SearchViewTextField(state: MutableState) {
    Box(
        modifier = Modifier
            .border(width = 1.dp, color = Color.Gray, shape = CircleShape)
            .fillMaxWidth()
    ) {
        BasicTextField(
            value = state.value,
            onValueChange = {
                state.value = it
            },
            modifier = Modifier
                .background(Color.White, CircleShape)
                .height(38.dp)
                .fillMaxWidth(),
            singleLine = true,
            maxLines = 1,
            decorationBox = { innerTextField ->
                Row(
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.padding(horizontal = 10.dp)
                ) {
                    Icon(
                        imageVector = Icons.Default.Search,
                        contentDescription = "image",
                        tint = Color.Blue
                    )
                    Box(
                        modifier = Modifier.weight(1f),
                        contentAlignment = Alignment.CenterStart
                    ) {
                        if (state.value == TextFieldValue("")) Text(
                            "Search"
                        )
                        innerTextField()
                    }
                    if (state.value != TextFieldValue("")) {
                        IconButton(
                            onClick = {
                                state.value = TextFieldValue("")
                            },
                        ) {
                            Icon(
                                imageVector = Icons.Default.Close,
                                contentDescription = "image",
                                tint = Color.Blue
                            )
                        }
                    }
                }
            }
        )
    }
}

 

Create top App bar

TopAppBar(
    title = { Text("Search View Demo") }
)

 

 

Lets combine Viewmodel, SearchView, List together and top app bar

@Composable
fun SearchViewDemo(searchViewModel: SearchViewModel) {
    JetPackTheme(
    ) {
        Scaffold(
            modifier = Modifier.fillMaxSize(),
            content = {
                SearchContent(searchViewModel)

            }, topBar = {
                TopAppBar(
                    title = { Text("Search View Demo") }
                )
            }
        )
    }
}

@Composable
fun SearchContent(searchViewModel: SearchViewModel) {
    val searchList = searchViewModel.searchList.collectAsState()
    val searchBy = remember { mutableStateOf(TextFieldValue("")) }
    Column(
        Modifier
            .padding(top = 16.dp, start = 16.dp, end = 16.dp)
            .fillMaxSize()
    ) {
        SearchViewTextField(searchBy)
        searchViewModel.searchedItems(searchBy.value.text)
        LazyColumn(
            contentPadding = PaddingValues(
                bottom = 100.dp
            )
        ) {
            items(
                items = searchList.value,
                itemContent = {
                    SearchListItem(searchData = it)
                })
        }
    }
}

 

 

 

Complete example code to implement searchview to search the listview items with jetpack compose

package com.example.jetpack

import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Contacts
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModelProvider
import com.example.jetpack.ui.theme.JetPackTheme
import com.example.jetpack.viewmodels.SearchViewModel
import com.example.jetpack.widget.SearchViewDemo

class MainActivity : ComponentActivity() {

    @ExperimentalMaterialApi
    @ExperimentalFoundationApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
        val searchViewModel = ViewModelProvider(this).get(SearchViewModel::class.java)
        setContent {
            SearchViewDemo(searchViewModel)
        }
    }
}

@ExperimentalMaterialApi
@ExperimentalFoundationApi
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
}

@Composable
fun SearchViewDemo(searchViewModel: SearchViewModel) {
    JetPackTheme(
    ) {
        Scaffold(
            modifier = Modifier.fillMaxSize(),
            content = {
                SearchContent(searchViewModel)

            }, topBar = {
                TopAppBar(
                    title = { Text("Search View Demo") }
                )
            }
        )
    }
}

@Composable
fun SearchContent(searchViewModel: SearchViewModel) {
    val searchList = searchViewModel.searchList.collectAsState()
    val searchBy = remember { mutableStateOf(TextFieldValue("")) }
    Column(
        Modifier
            .padding(top = 16.dp, start = 16.dp, end = 16.dp)
            .fillMaxSize()
    ) {
        SearchViewTextField(searchBy)
        searchViewModel.searchedItems(searchBy.value.text)
        LazyColumn(
            contentPadding = PaddingValues(
                bottom = 100.dp
            )
        ) {
            items(
                items = searchList.value,
                itemContent = {
                    com.example.jetpack.widget.SearchListItem(searchData = it)
                })
        }
    }
}

@Composable
fun SearchListItem(searchData: SearchData) {
    Card(
        modifier = Modifier
            .padding(top = 16.dp)
            .fillMaxWidth(),
        elevation = 4.dp,
        border = BorderStroke(width = 1.2.dp, Color.Blue)
    ) {
        Row(content = {
            Icon(
                Icons.Default.Contacts,
                "",
                tint = Color.Blue,
                modifier = Modifier
                    .padding(start = 16.dp, top = 16.dp)
            )
            Column(modifier = Modifier.padding(16.dp)) {
                Text(
                    text = "${searchData.name}",
                    style = TextStyle(
                        color = Color.Blue,
                        fontSize = 21.sp,
                        fontWeight = FontWeight.Bold
                    )
                )
                Text(text = "${searchData.emailId}", modifier = Modifier.padding(top = 8.dp))
            }
        })

    }
}

@Composable
fun SearchViewTextField(state: MutableState) {
    Box(
        modifier = Modifier
            .border(width = 1.dp, color = Color.Gray, shape = CircleShape)
            .fillMaxWidth()
    ) {
        BasicTextField(
            value = state.value,
            onValueChange = {
                state.value = it
            },
            modifier = Modifier
                .background(Color.White, CircleShape)
                .height(38.dp)
                .fillMaxWidth(),
            singleLine = true,
            maxLines = 1,
            decorationBox = { innerTextField ->
                Row(
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.padding(horizontal = 10.dp)
                ) {
                    Icon(
                        imageVector = Icons.Default.Search,
                        contentDescription = "image",
                        tint = Color.Blue
                    )
                    Box(
                        modifier = Modifier.weight(1f),
                        contentAlignment = Alignment.CenterStart
                    ) {
                        if (state.value == TextFieldValue("")) Text(
                            "Search"
                        )
                        innerTextField()
                    }
                    if (state.value != TextFieldValue("")) {
                        IconButton(
                            onClick = {
                                state.value = TextFieldValue("")
                            },
                        ) {
                            Icon(
                                imageVector = Icons.Default.Close,
                                contentDescription = "image",
                                tint = Color.Blue
                            )
                        }
                    }
                }
            }
        )
    }
}

data class SearchData(var name: String? = null, var emailId: String? = null)

 

 

Output for SearchView with Listview Items

Searchview implementation with Jetpack compose

 

Article Contributed By :
https://www.rrtutors.com/site_assets/profile/assets/img/avataaars.svg

3409 Views