How can I convert for loop to list functions in Kotlin - kotlin

I'm reading The Big Nerd Ranch Guide and here is a question that wants you to use the list functions instead of for loop
var count = 0
for (answer in answerList) {
if (!answer.isCorrect) {
answer.isEnabled = false
answer.isSelected = false // deselect when answer is disabled
count++
if (count == 2) {
break
}
}
}
here is my solution, but I don't know how to deal with count
var count = 0
answerList
.filter { !it.isCorrect }
.forEach {
it.isSelected = false
it.isSelected = false
count++
}
.takeIf { count == 2 }

If you want to perform the actions for the first two (at most) items that are "not correct", you could do this, with take:
answerList
.filter { !it.isCorrect }
.take(2)
.forEach {
it.isEnabled = false
it.isSelected = false
}

Related

State flow Android Kotlin

I have a god view model for every thing I know this is wrong
but I am just experimenting with Flow
I have these two State flow variables in view model
private val _currentRestroMenu = MutableStateFlow<State<Menu>>(State.loading())
private val _userCart = MutableStateFlow(CustomerCart())
val currentRestroMenu: StateFlow<State<Menu>> = _currentRestroMenu
val userCart: StateFlow<CustomerCart> = _userCart
Below functions get data from server and update above state flow
private fun getRestroMenuFromCloudAndUpdateData(restroId: String) = viewModelScope.launch {
fireStoreRepository.getRestroMenu(restroId).collect { state ->
when (state) {
is State.Success -> {
_currentRestroMenu.value = State.success(state.data)
dataHolderMenuOnSearch = state.data
if (!viewedRestroMenu.contains(state.data)) {
viewedRestroMenu.add(state.data)
}
}
is State.Failed -> {
_currentRestroMenu.value = State.failed(state.message)
}
is State.Loading -> {
_currentRestroMenu.value = State.loading()
}
}
}
}
private fun getCart() = viewModelScope.launch(Dispatchers.IO) {
if (currentCart.cartEmpty) {
fireStoreRepository.getUserCartInfoFromCloud(dataStoreRepository.readFileDataStoreValue.first().savedUserId)
.collect { cartState ->
when (cartState) {
is State.Success -> {
_userCart.update {
it.copy(
cartId = cartState.data.cartId,
cartEmpty = cartState.data.cartEmpty,
cartItem = cartState.data.getCartItem(),
restroId = cartState.data.restroId,
cartTotalAmount = cartState.data.cartTotalAmount,
cartAddressId = cartState.data.cartAddressId,
cartDeliveryTime = cartState.data.cartDeliveryTime,
cartCookingInstructions = cartState.data.cartCookingInstructions,
cartAppliedOfferId = cartState.data.cartAppliedOfferId,
deliveryPartnerTipAmount = cartState.data.deliveryPartnerTipAmount,
cartDeliveryCharge = cartState.data.cartDeliveryCharge,
cartTax = cartState.data.cartTax,
deliveryInstructionId = cartState.data.deliveryInstructionId,
foodHandlingCharge = cartState.data.foodHandlingCharge,
cartNumberOfItems = cartState.data.cartNumberOfItems,
cartRestroName = cartState.data.cartRestroName
)
}
currentCart = cartState.data
}
is State.Failed -> {
if (cartState.message == "Result null") {
Log.d(
ContentValues.TAG,
"getCartFromCloud: No cart details found in cloud creating new cart"
)
_userCart.update {
it.copy(
cartId = dataStoreRepository.readFileDataStoreValue.first().savedUserId,
cartEmpty = true
)
}
currentCart = CustomerCart(
cartId = dataStoreRepository.readFileDataStoreValue.first().savedUserId,
cartEmpty = true
)
}
}
is State.Loading -> {
Log.d(ContentValues.TAG, "getCartFromCloud: Loading")
}
}
}
} else {
_userCart.value = currentCart
Log.d(ContentValues.TAG, "getCart: $currentCart ")
}
}
I am collecting these state flow from different fragments
every thing works fine except one fragment
here is the code
in on create method
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
godCustomerViewModel.currentRestroMenu.collectLatest { menuState ->
Log.d(TAG, "currentRestroMenu ::: mENUSELECT FIRED: ")
when (menuState) {
is State.Success -> {
restroMenu = menuState.data
binding.recyclerView2.hideShimmer()
getCartDetails(restroMenu)
}
is State.Failed -> {
Log.d(TAG, "currentRestroMenu: ")
}
is State.Loading -> {
binding.recyclerView2.showShimmer()
}
}
}
}
}
private fun getCartDetails(restroMenu: Menu) = viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
godCustomerViewModel.userCart.collectLatest {
if (it.restroId == restroMenu.restroId) {
categoryAdapterRestroDetails.setData(
restroMenu.menuCategories,
it.getCartItem()
)
} else {
categoryAdapterRestroDetails.setData(
restroMenu.menuCategories,
ArrayList()
)
}
}
}
}
I am passing the two collected values to adapter (retro menu and item in cart )
when the fragment is loaded for the first time everything works fine
I have add dish to cart function which updates the value of user cart
fun addDishToCart(dish: Dish) = viewModelScope.launch {
Log.d(ContentValues.TAG, "addDishToCart: view model invoked")
if (currentCart.checkIfCartBelongsToThisRestro(dish.dishRestroId)) {
currentCart.addDishToCart(dish).collect {
Log.d(ContentValues.TAG, "addDishToCartcollect: $currentCart")
_userCart.update {
it.copy(
cartEmpty = currentCart.cartEmpty,
cartItem = currentCart.getCartItem(),
restroId = currentCart.restroId,
cartTotalAmount = currentCart.cartTotalAmount,
cartNumberOfItems = currentCart.cartNumberOfItems,
)
}
}
} else {
// restro Conflict
Log.d(ContentValues.TAG, "addDishToCart: $currentCart")
_restroConflict.value = CartConflict(true, currentCart.cartRestroName, dish)
}
Log.d(ContentValues.TAG, "addDishToCart current cart: ${currentCart.getCartItem()}")
Log.d(ContentValues.TAG, "addDishToCart: user Cart : ${_userCart.value.getCartItem()} ")
}
Which also work fine initially
I also have a button to filter menu to veg non veg
fun filterMenuForVeg(value: Boolean, showAll: Boolean) = viewModelScope.launch {
if (!showAll) {
Log.d(ContentValues.TAG, "filterMenuForVeg: Entered veg :$value")
var filteredMenu = Menu()
filteredMenu.restroId = dataHolderMenuOnSearch.restroId
for (menuCategory in dataHolderMenuOnSearch.menuCategories) {
Log.d(ContentValues.TAG, "filterMenuForVeg: $dataHolderMenuOnSearch ")
for (dish in menuCategory.dishes) {
if (dish.dishVeg == value) {
Log.d(ContentValues.TAG, "found dish with veg $value: ")
var categoryAlreadySaved = false
filteredMenu.menuCategories.filter {
categoryAlreadySaved = it.categoryId == menuCategory.categoryId
true
}
if (!categoryAlreadySaved) {
Log.d(ContentValues.TAG, "menu category not found in filtered list ")
val menuCategoryToAdd = MenuCategories()
menuCategoryToAdd.menuCategoryName = menuCategory.menuCategoryName
menuCategoryToAdd.categoryId = menuCategory.categoryId
menuCategoryToAdd.restroId = menuCategory.restroId
menuCategoryToAdd.dishes.add(dish)
filteredMenu.menuCategories.add(menuCategoryToAdd)
} else {
Log.d(ContentValues.TAG, "menu category found in filtered list ")
filteredMenu.menuCategories.find {
if (it.categoryId == menuCategory.categoryId) {
it.restroId = menuCategory.restroId
it.dishes.add(dish)
}
true
}
}
}
}
}
Log.d(ContentValues.TAG, "filterMenuForVeg : $filteredMenu ")
_currentRestroMenu.value = State.success(filteredMenu)
} else {
// set to all data
_currentRestroMenu.value = State.success(dataHolderMenuOnSearch)
}
When I filter dish for veg or non veg then add dish to cart (Which only changes userCart State flow) the place where I am collecting these state flow
get fired twice
so set data to adapter is getting called twice
What Iam doing wrong
Could you collect the items with onEach instead of collectLatest? It would solve your problem probably.

How can I write do noting in Kotlin?

ESelect is a enum structure. I hope it will do noting when it is ESelect.NoAction in the following code.
It will cause compile error if I write ; after ESelect.NoAction ->, how can I fix it?
Code
aHomeViewModel.selectAction.observe(mLifecycleOwner, {
when(it) {
ESelect.SelectAll - > binding.chSelect.isChecked = true
ESelect.UnselectAll - > binding.chSelect.isChecked = false
ESelect.NoAction - > ; //It will do nothing
}
})
enum class ESelect {
SelectAll,
UnselectAll,
NoAction
}
You could return Unit (which is like void in Java). The code will look like:
aHomeViewModel.selectAction.observe(mLifecycleOwner, {
when(it) {
ESelect.SelectAll -> binding.chSelect.isChecked = true
ESelect.UnselectAll -> binding.chSelect.isChecked = false
ESelect.NoAction -> Unit
}
})
See the Docu: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/
You could use when as an expression instead of a statement, and for the NoAction case assign the existing value:
binding.chSelect.isChecked = when (it) {
ESelect.SelectAll -> true
ESelect.UnselectAll -> false
ESelect.NoAction -> binding.chSelect.isChecked
}
Or use if:
if (it == ESelect.SelectAll) {
binding.chSelect.isChecked = true
} else if (it == ESelect.UnselectAll) {
binding.chSelect.isChecked = false
}

How to remake the program so that words are passed in function arguments in the KOTLIN programming language?

Need to create a function that implements the attached algorithm, to which all words are passed in the function arguments.
For example:
f ("dfd" dd "ddd");
My code:
fun main() {
var s = readLine();
var w = Array(128){0} //To mark characters from a word 1
var g = Array(128){0}//When we encounter a space, we add units from the first array to the corresponding elements of the second, zeroing them in the first.
if(s!=null)
{
for(c in s)
{
if(c.toInt() > 127 || c.toInt()<0) {
println("Input error, try again");
return;
}
//Checking for space.
if(c.toInt() != 32) w[c.toInt()] = 1;
else
for(k in 0..127)
{
if(w[k] == 1)
{
g[k] += 1;
w[k] = 0;
}
}
}
//For the last word, if there was no space after it.
for(k in 0..127)
{
if(w[k] == 1)
{
g[k] += 1;
w[k] = 0;
}
}
}
//Displaying matched characters to the screen
for(k in 0..127)
{
if(g[k]>1)
{
println(k.toChar());
}
}
}
This program searches for characters that match at least two words in a string
Example
input: hello world
output: lo
There's already utilities for these in Kotlin, I highly recommend you to read the docs before asking these type of questions.
The groupingBy should do what you want:
readLine()?.let { input ->
input.groupingBy { it }.eachCount()
.forEach { if (it.value > 1 && it.key != ' ') println(it.key) }
}

Better way to replace nested if else in Kotlin

Is there any better way to replace below if else to more cleaner in Kotlin. I tried to replace with when statement but i couldn't match the logic.
if (!reached)
{
if (!info)
{
d.sinfo = extractinfo()
}
else
{
parserMessage("print something")
return d
}
info = true
}
else
{
if (d.media.isEmpty()){
parserMessage("print something")
return d
}
else{
if (d.media.at(d.media.size() - 1).media_information.isEmpty())
{d.media[d.media.size() - 1].minfo = extractinfo()}
else{
parserMessage("print something")
return d
}
}
}
Unless the code you have left out have some weird side effects, this code should be semantically equal:
when {
!reached && !info -> {
d.sinfo = extractinfo()
info = true
}
!reached && info -> {
parserMessage("print something")
return d
}
d.media.isEmpty() -> {
parserMessage("print something")
return d
}
d.media.at(d.media.size() - 1).media_information.isEmpty() -> {
d.media[d.media.size() - 1].minfo = extractinfo()
}
else -> {
parserMessage("print something")
return d
}
}
However, to say this, I had to fill in the gaps in the code you have presented myself, so I can't state this very confidently. It really helps your chances of getting a good answer if the code you want help with is runnable/understandable as presented.
By the way. This refactoring was partly done by pasting the code into IntelliJ and hitting Alt+Enter and choosing "Replace 'if' with 'when'" and "Flatten when"

Is there a way to merge filter and map into single operation in Kotlin?

The below code will look for "=" and then split them. If there's no "=", filter them away first
myPairStr.asSequence()
.filter { it.contains("=") }
.map { it.split("=") }
However seeing that we have both
.filter { it.contains("=") }
.map { it.split("=") }
Wonder if there's a single operation that could combine the operation instead of doing it separately?
You can use mapNotNull instead of map.
myPairStr.asSequence().mapNotNull { it.split("=").takeIf { it.size >= 2 } }
The takeIf function will return null if the size of the list returned by split method is 1 i.e. if = is not present in the string. And mapNotNull will take only non null values and put them in the list(which is finally returned).
In your case, this solution will work. In other scenarios, the implementation(to merge filter & map) may be different.
I see your point and under the hood split is also doing an indexOf-check to get the appropriate parts.
I do not know of any such function supporting both operations in a single one, even though such a function would basically just be similar to what we have already for the private fun split-implementation.
So if you really want both in one step (and require that functionality more often), you may want to implement your own splitOrNull-function, basically copying the current (private) split-implementation and adapting mainly 3 parts of it (the return type List<String>?, a condition if indexOf delivers a -1, we just return null; and some default values to make it easily usable (ignoreCase=false, limit=0); marked the changes with // added or // changed):
fun CharSequence.splitOrNull(delimiter: String, ignoreCase: Boolean = false, limit: Int = 0): List<String>? { // changed
require(limit >= 0, { "Limit must be non-negative, but was $limit." })
var currentOffset = 0
var nextIndex = indexOf(delimiter, currentOffset, ignoreCase)
if (nextIndex == -1 || limit == 1) {
if (currentOffset == 0 && nextIndex == -1) // added
return null // added
return listOf(this.toString())
}
val isLimited = limit > 0
val result = ArrayList<String>(if (isLimited) limit.coerceAtMost(10) else 10)
do {
result.add(substring(currentOffset, nextIndex))
currentOffset = nextIndex + delimiter.length
// Do not search for next occurrence if we're reaching limit
if (isLimited && result.size == limit - 1) break
nextIndex = indexOf(delimiter, currentOffset, ignoreCase)
} while (nextIndex != -1)
result.add(substring(currentOffset, length))
return result
}
Having such a function in place you can then summarize both, the contains/indexOf and the split, into one call:
myPairStr.asSequence()
.mapNotNull {
it.splitOrNull("=") // or: it.splitOrNull("=", limit = 2)
}
Otherwise your current approach is already good enough. A variation of it would just be to check the size of the split after splitting it (basically removing the need to write contains('=') and just checking the expected size, e.g.:
myPairStr.asSequence()
.map { it.split('=') }
.filter { it.size > 1 }
If you want to split a $key=$value-formats, where value actually could contain additional =, you may want to use the following instead:
myPairStr.asSequence()
.map { it.split('=', limit = 2) }
.filter { it.size > 1 }
// .associate { (key, value) -> key to value }