When You Handle The Filter On The Front Jetpack Compose
Published:
Say there is a Product that you want to implement. Each can have different type one to another, also has different status. Summary the product can have:
- Different Product Type
- Different Status
you are assigned to make a filter, on the front, not back.
so you can mix up those two filters.
Conditions:
- Mix both
productType
andstatus
- The
productType
includesAll Type
- The
status
includesAll Status
- The default filter is
All Type
andAll Status
- The Filter is never
empty
, if noneproductType
filter is clicked thenAll Type
is active - Same with
status
, namedAll Status
is active. - Can select more than one for
productTtype
filter - Can select more than one for
status
filter
So I need some trigger event after user submit its filter preference. It’s doesnt make any difference whether the product to be filtered first or the status, Say I want the productType
to be checked first,
every event triggered, if the list is as same as the default (all products - all status) if not then I need to clear the list, then addAll
from the all list that has been memorized in a variable. Thus I also need a variable that holds the default state (all products & all statuses) I can use it as a source data of reference
Steps:
- Provide two variables contain same list, one acts as a source data of reference, the another is to be displayed for UI that can be manipulated.
- Add them from the source (say it’s from backend)
- Provide a variable which can be used as a trigger for filter event (after Filter UI is closed, applied)
- Look at the filter UI, if it’s like a button style item, then provide a list that contains which ones have been chosen to be filtered, say you name it
addedFilterList<String>
.
If it is a checkbox style, you need a list that contains which ones have been checked (list of boolean is enough, can use index as a key). say you name itisCheckedStateList
- If filter candidate item is clicked, add to
addedFilterList
if it’s unclicked, remove that. - For the checkbox style, is similar
true
for active (checked),false
for inactive - We implement three kind of event whenever Filter UI is shown there,
one is that when all filter candidates is clicked (all product type in filter is clicked, excludingall type
), the next is when user clicksall type
.
the last is the other way around, when user clicks one of the product types 7a. If all filter candidates is clicked, then you can remove all of them, then add theall type
filter.addedFilterList.map { it.lowercase() }.containsAll(arrayListOf( PRODUCT_TYPE_A, PRODUCT_TYPE_B, ... PRODUCT_TYPE_X )){ addedFilterList.clear() addedFilterList.add("all type") }
7b. If
all type
is clicked, negate all other candidates if the fliteredList contains any of productType,
remove them,, then add theall type
.
7c. If one of the product types
is clicked, remove the all type
if it is on the addedFilterList beforehhand.
Recap for Filter UI click events: for the checkbox
case
LaunchedEffect(isAllStatusFilterCandidateClicked){
if(isAllStatusFilterCandidateClicked){
isCheckedStateList.replaceAll { false }
isCheckedStateList[0] = true
}
}
statusList.forEachIndexed { index, isChecked ->
//onCheckedChange =
if (index > 0 && it && isAllStatusAndOtherClicked()) {
handleWhenAllAndCriteriaClicked()
} else if (index == 0 && it && isAnyCriteriaClicked()) {
handleWhenSemuaStatusClicked()
} else if (index == 0 && !it && isAllStatusNotClicked()) {
handleWhenSemuaStatusUnclickedAndCriteriaEmpty()
return@EdufundCheckBox
}
isCheckedStateList[index] = it
}
for the button styled item case
//Your "All Type" Button
Modifier.clickable {
if (
addedFilterList
.subList(1, addedFilterList.size)
.isNotEmpty() ||
addedFilterList[0] !=
BuildConfig.PRODUCT_TYPE_ALL
) {
addedFilterList.clear()
}
addedFilterList.add(allTypeText)
}
if (isAllTypeClicked(addedFilterList)) {
LaunchedEffect(isAllTypeClicked(addedFilterList)) {
addedFilterList.clear()
addedFilterList.add(allTypeText)
}
} else if (
isAllAndAnyProductTypeClicked(
filterByProductOptionTextFieldState.addedFilterList,
semuaProdukText
)
) {
LaunchedEffect(
isAllTypeAndAnyProductTypeClicked(
filterByProductOptionTextFieldState.addedFilterList,
allTypeText
)
) { addedFilterList.remove(allTypeText) }
}
- The Event after filter is submitted (closed)
@Composable
private fun FilterLaunchedEffect(
isFilterSubmitted: MutableState<Boolean>,
statusList: ArrayList<String>,
addedFilterList: ArrayList<String>,
isCheckedStateList: ArrayList<Boolean>,
transactionList: SnapshotStateList<YourObj>,
allTransactionList: ArrayList<YourObj>,
onFilterSubmitted: () -> Unit
) {
LaunchedEffect(
isFilterSubmitted.value,
addedFilterList.isNotEmpty() ||
isCheckedStateList.any { true }
) {
if (
isFilterSubmitted.value &&
(addedFilterList.isNotEmpty() ||
isCheckedStateList.any { true })
) {
onFilterSubmitted()
val filterProductCandidateList = arrayListOf<((String) -> Boolean)>()
val filterStatusCandidateList = arrayListOf<((String) -> Boolean)>()
if (
addedFilterList.contains(
FILTER_ALL_TYPE
) && isCheckedStateList[0]
) {
if (transactionList != allTransactionList) {
transactionList.clear()
transactionList.addAll(allTransactionList)
}
} else {
transactionList.clear()
val filteredList = arrayListOf<YourObj>()
filteredList.addAll(transactionList)
addFilterByProductCandidateList(
isAllProductTypeSelected,
addedFilterList,
filterProductCandidateList,
filteredList
)
addFilterByStatusCandidateList(
statusList, //status list
isAllStatusChecked: Boolean,
isCheckedStateList: ArrayList<Boolean>,
filterStatusCandidateList,
filteredList
)
transactionList.addAll(filteredList)
}
}
isFilterSubmitted.value = false
}
}
private fun addFilterByProductCandidateList(
isAllProductTypeSelected: Boolean,
addedFilterList: ArrayList<String>,
filterProductCandidateList: ArrayList<(String) -> Boolean>,
filteredList: ArrayList<YourObj>
) {
if (!isAllProuctTypeSelected()) {
addedFilterList.forEach {
filterProductCandidateList.add {
value -> value == it
}
}
}
if (filterProductCandidateList.isNotEmpty()) {
filteredList.filterAndUpdateList { yourObj ->
filterProductCandidateList.any { product ->
product(yourObj.getProductType())
}
}
}
}
private fun addFilterByStatusCandidateList(
itemList: ArrayList<String>,
isAllStatusChecked: Boolean,
isCheckedStateList: ArrayList<Boolean>,
filterStatusCandidateList: ArrayList<(String) -> Boolean>,
filteredList: ArrayList<YourObj>
) {
if (!isSemuaChecked) {
isCheckedStateList.forEachIndexed { index, isChecked ->
if (isChecked) {
filterStatusCandidateList.add {
it == itemList[index]
}
}
}
}
if (filterStatusCandidateList.isNotEmpty()) {
filteredList.filterAndUpdateList { yourObj ->
filterStatusCandidateList.any { statusName ->
status(
statusName
)
}
}
}
}
I make use of kotlin filter{}
extension function, so it can mingle with:
val filterProductCandidateList = arrayListOf<((String) -> Boolean)>()
val filterStatusCandidateList = arrayListOf<((String) -> Boolean)>()
list.any{obj -> }
can act as a OR in the database so…. it helps a lot, can be convenient with these.
for the filterAndUpdateList{}
ext function you can take a look here: