I have a scenario where I am suppose to delete objects form a list of objects,
but only if:
a given property of an object is NULL and occurs in the list more than once
To clarify with an example:
A("b", null) should be deleted, because object with property name="b" occurs in A("b", 3)
A("c", null) should remain, because it object with property name="c" occurs in the listonly once
A("d", null) one occurrence should be deleted and one should stay
Thank you in advance.
class A(
val name: String,
val amount: Int?
)
val itemList = mutableListOf<A>(
A("a", 2),
A("b", 3),
A("b", null),
A("c", null),
A("d", null),
A("d", null)
)
This solution creates a new list instead of mutating the original one:
itemList
.groupBy { it.name }
.values
.flatMap { nameGroup ->
val (nullAmountSubgroup, notNullAmountSubgroup) = nameGroup.partition { it.amount == null }
notNullAmountSubgroup.ifEmpty { listOf(nullAmountSubgroup.first()) }
}
Just to make sure this is you desired output:
val l: List<A> = itemList
.groupBy { it.name }
.map {
return#map it.value.firstOrNull { el -> el.amount != null } ?: it.value.first()
}
l.forEach { println("${it.name}: ${it.amount}") }
Related
I am a beginner trying to learn Kotlin by changing an old tutorial to Compose.
I have a ViewModel with
private val _registerStatus = MutableLiveData<Resource<String>>()
val registerStatus: LiveData<Resource<String>> = _registerStatus
fun register(email: String, password: String, repeatedPassword: String) {
_registerStatus.postValue(Resource.loading(null))
if(email.isEmpty() || password.isEmpty() || repeatedPassword.isEmpty()) {
_registerStatus.postValue(Resource.error("Please fill out all the fields", null))
return
}
if(password != repeatedPassword) {
_registerStatus.postValue(Resource.error("The passwords do not match", null))
return
}
viewModelScope.launch {
val result = repository.register(email, password)
_registerStatus.postValue(result)
}
}
and Fragment with:
viewModel.registerStatus.observe(viewLifecycleOwner, Observer { result ->
result?.let {
when(result.status) {
Status.SUCCESS -> {
registerProgressBar.visibility = View.GONE
showSnackbar(result.data ?: "Successfully registered an account")
}
Status.ERROR -> {
registerProgressBar.visibility = View.GONE
showSnackbar(result.message ?: "An unknown error occurred")
}
Status.LOADING -> {
registerProgressBar.visibility = View.VISIBLE
}
}
}
})
How could I adapt this code to use Jetpack Compose?
I understand I need to use "ObserveAsState" in a Composable :
registerViewModel.registerStatus.observeAsState()
Truth is, I think I don't really understand the nullable issue, or what val result is doing, or what result -> result?.let is doing, except being some way to pass a non-null value in? If removed, I must make registerStatus.status non-null or safe. So I can do the below:
#Composable
fun subscribeToObservers() {
val registerStatus by registerViewModel.registerStatus.observeAsState()
when(registerStatus!!.status){
Status.SUCCESS -> {
}
Status.ERROR -> {
}
Status.LOADING -> {
}
}
}
, or do I need to get the "result" value over?
Anything to help me toward understanding the issues better would be really appreciated.
You should avoid using !! as that means you know that that variable is never null. Your application is going to crash if the variable is ever null when going through this piece of code.
I want also to note that logically, you might not be able to find a scenario in which your variable is going to be null so you might get tempted to use !! but it is better to avoid it just in case.
Using nullableVariable?.let { -> nonNullVariable } is much safer as it only runs if the variable is not null. Unlike !!, it won't cause a crash if the variable is null.
I would write the code like this:
#Composable
fun subscribeToObservers() {
val registerStatus by registerViewModel.registerStatus.observeAsState()
registerStatus?.let { nonNullRegisterStatus ->
when(nonNullRegisterStatus) {
Status.SUCCESS -> {}
Status.ERROR -> {}
Status.LOADING -> {}
}
}
}
There are two ways you can approach this:
Compose style
ViewModel
private val _registerStatus : MutableState<Resource<String>> = mutableStateOf(Resource.init(null))
val registerStatus: State<Resource<String>> = _registerStatus
fun register(email: String, password: String, repeatedPassword: String) {
_registerStatus.value = Resource.loading(null)
if(email.isEmpty() || password.isEmpty() || repeatedPassword.isEmpty()) {
_registerStatus.value = Resource.error("Please fill out all the fields", null)
return
}
if(password != repeatedPassword) {
_registerStatus.value = Resource.error("The passwords do not match", null)
return
}
viewModelScope.launch {
val result = repository.register(email, password)
_registerStatus.value = result
}
}
Composable
#Composable
fun subscribeToObservers() {
val registerStatus by viewModel.registerStatus
when(registerStatus.status){
Status.INIT -> {}
Status.SUCCESS -> {}
Status.ERROR -> {}
Status.LOADING -> {}
}
}
You can make _registerStatus not null in this way or if you initialize it as private val _registerStatus : MutableState<Resource<String>?> = mutableStateOf(null) handle null like:
#Composable
fun subscribeToObservers() {
val registerStatus by viewModel.registerStatus
when(registerStatus.status){
Status.INIT -> {}
Status.SUCCESS -> {}
Status.ERROR -> {}
Status.LOADING -> {}
null -> {}
}
}
Kotlin style
ViewModel
private val _registerStatus : MutableStateFlow<Resource<String>> = mutableStateOf(Resource.init(null))
val registerStatus: StateFlow<Resource<String>> = _registerStatus
fun register(email: String, password: String, repeatedPassword: String) {
_registerStatus.tryEmit(Resource.loading(null))
if(email.isEmpty() || password.isEmpty() || repeatedPassword.isEmpty()) {
_registerStatus.tryEmit(Resource.error("Please fill out all the fields", null))
return
}
if(password != repeatedPassword) {
_registerStatus.tryEmit(Resource.error("The passwords do not match", null))
return
}
viewModelScope.launch {
val result = repository.register(email, password)
_registerStatus.tryEmit(result)
}
}
Composable
#Composable
fun subscribeToObservers() {
val registerStatus by viewModel.registerStatus.collectAsState()
when(registerStatus.status){
Status.INIT -> {}
Status.SUCCESS -> {}
Status.ERROR -> {}
Status.LOADING -> {}
null -> {}
}
}
MutableStateFlow are thread safe and kotlin's solution to xJava This method is thread-safe and can be safely invoked from concurrent coroutines without external synchronization.
Kotlin provides one more type of flow called SharedFlow and it's upon your use case to use SharedFlow or StateFlow.
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
The main difference between SharedFlow and StateFlow
I have a method that take a list as a parameter that performs some operation on it and returns the new list. However, in my for..loop I would to keep passing in the updated list until the for..loop has completed.
Is there a way to do this?
fun main(args: Array<String>) {
val listOfSeatRows = (1..127).toList()
// Just loop until all the listOfPass has completed.
listOfPass.forEach { seatPass ->
val seat = Seat.valueOf(seatPass.toString())
// I want to pass in the new updated list not the same list
getListOfSeatRows(listOfSeatRows, seat)
}
}
This method takes the list and return a updated list. However, in the for..loop above I would like to pass in the list that is returned from this method
private fun getListOfSeatRows(listOfSeat: List<Int>, seatPosition: Seat): List<Int> {
return when(seatPosition) {
Seat.F, Seat.L -> {
listOfSeat.windowed(listOfSeat.count() / 2).first()
}
Seat.B, Seat.R -> {
listOfSeat.windowed(listOfSeat.count() / 2).last()
}
}
}
enum class Seat(seat: Char) {
F('F'),
B('B'),
L('L'),
R('R')
}
Either you mutate the variable:
fun main(args: Array<String>) {
var listOfSeatRows = (1..127).toList()
// Just loop until all the listOfPass has completed.
listOfPass.forEach { seatPass ->
val seat = Seat.valueOf(seatPass.toString())
// I want to pass in the new updated list not the same list
listOfSeatRows = getListOfSeatRows(listOfSeatRows, seat)
}
}
or you mutate the list:
fun main(args: Array<String>) {
var listOfSeatRows = (1..127).toMutableList()
// Just loop until all the listOfPass has completed.
listOfPass.forEach { seatPass ->
val seat = Seat.valueOf(seatPass.toString())
// I want to pass in the new updated list not the same list
reduceListOfSeatRows(listOfSeatRows, seat)
}
}
private fun reduceListOfSeatRows(listOfSeat: MutableList<Int>, seatPosition: Seat) {
val half = listOfSeat.size / 2
when(seatPosition) {
Seat.F, Seat.L -> {
while (listOfSeat.size > half) listOfSeat.removeLast()
}
Seat.B, Seat.R -> {
while (listOfSeat.size > half) listOfSeat.removeFirst()
}
}
}
If you stick with mutating the property, your function can be simplified (and avoid wasteful creation of multiple intermediate lists) using take/takeLast:
private fun getListOfSeatRows(listOfSeat: List<Int>, seatPosition: Seat): List<Int> {
return when(seatPosition) {
Seat.F, Seat.L -> {
listOfSeat.take(listOfSeat.size / 2)
}
Seat.B, Seat.R -> {
listOfSeat.takeLast(listOfSeat.size / 2)
}
}
}
recursion
maybe that's will help with some enhancement depending on your code:
var ss = 1
val listOfPass = listOf<Char>('F', 'L','B','R')
fun main(args: Array<String>) {
val listOfSeatRows = (1..127).toList()
val answer = getListOfSeatRows(
listOfSeatRows,
listOfSeatRows.count() / 2,
Seat.valueOf(listOfPass[0].toString())
)
println(answer)
}
private fun getListOfSeatRows(listOfSeat: List<Int>, count: Int, seatPosition: Seat): List<Int> {
val tempList: List<Int> = when (seatPosition) {
Seat.F, Seat.L -> {
listOfSeat.windowed(count).first()
}
Seat.B, Seat.R -> {
listOfSeat.windowed(count).last()
}
else -> listOfSeat
}
if(count == 0 || count == 1) return listOfSeat
if (listOfPass.size > ss) {
val seat = Seat.valueOf(listOfPass[ss++].toString())
return getListOfSeatRows(tempList, count / 2, seat)
}
return listOfSeat
}
In List One, I am getting some items. Each time those items are changing. Sometimes, I can get more than one record in the List.
In a second List, I would like to store all the data of List One. So, I can then display all the items of List Two.
To make it more clear.
List One = "/temp/file1.jpeg"
List Two = "/temp/file1.jpeg"
List One = "/temp/file2.jpeg"
List Two = "/temp/file1.jpeg,/temp/file2.jpeg"
I have tried this
void _openDocumentFileExplorer({fileType: FileType.custom}) async {
setState(() => _loadingPath = true);
try{
_paths = (await FilePicker.platform.pickFiles(
type: fileType,
allowMultiple: true,//_multiPick,
allowedExtensions: ['pdf']))?.files;
} on PlatformException catch (e) {
print("Unsupported operation" + e.toString());
} catch (ex) {
print('$ex');
}
if (!mounted) return;
setState(() {
_loadingPath = false;
_fileName = _paths != null ?
_paths!.map((e) => e.name).toString() : '...';
});
}
ListView.separated(
itemCount:
_paths != null && _paths!.isNotEmpty
? _paths!.length
: 1,
itemBuilder:
(BuildContext context, int index) {
final bool isMultiPath =
_paths != null && _paths!.isNotEmpty;
final String name = _paths!
.map((e) => e.name)
.toList()[index];
//filesGB store the full path + the file name
final filesGB = _paths!
.map((e) => e.path)
.toList()[index]
.toString();
print (filesGB);
_paths?.addAll(allFiles!.map((e) ));
allFiles.addAll(filesGB.toList());
allFiles.addAll(filesGB);
// allFilesV2.addAll(filesGB);
but it does not work. I am getting this error message.
"The argument type 'String' can't be assigned to the parameter type 'Iterable'"
Please, do you have any suggestion?
I think you can use SPREAD OPERATOR (...) using a triple dot for merging one array into another.
For example:
List list1= ["/temp/file1.jpeg"];
List list2 = [];
after some time
list1 = ["/temp/file2.jpeg"];
so whenever your list one change do
list2 = [...list2,...list1];
print(list2);
output: ["/temp/file1.jpeg","/temp/file2.jpeg"]
I think it would help.
I have an array:
var month: List<String> = arrayListOf("January", "February", "March")
I have to filter the list so I am left with only "January".
You can use this code to filter out January from array, by using this code
var month: List<String> = arrayListOf("January", "February", "March")
// to get the result as list
var monthList: List<String> = month.filter { s -> s == "January" }
// to get a string
var selectedMonth: String = month.filter { s -> s == "January" }.single()
There are a number of functions for filtering collections, if you want to keep only values matching "January", you can use the simple filter():
val months = listOf("January", "February", "March")
months.filter { month -> month == "January" } // with explicit parameter name
months.filter { it == "January" } // with implicit parameter name "it"
These will give you a list containing only "January".
If you want all months that are not "January", you can either reverse the condition using !=, or use filterNot():
months.filter { it != "January" }
months.filterNot { it == "January" }
These will give you a list containing "February" and "March".
Note that unlike Java, using the == and != operators in Kotlin is actually the same as calling the equals function on the objects. For more, see the docs about equality.
For the complete list of collection functions in the standard library, see the API reference.
You want to filter this list of Strings containing months.
var month : List<String> = arrayListOf("January", "February", "March")
You can use filterNot() method of list. It returns a list containing all elements except the given predicate.
var filteredMonthList : List<String> = month.filterNot { s -> s == "January" }
// results: ["February", "March"]
You can use filter() method of list. It returns a list containing all elements matching the given predicate.
var filteredMonthList : List<String> = month.filter { s -> s == "January" }
// results: ["January"]
After filter() if we use single() method then it will return a single value and throw an exception if more than one value is in the list.
var filteredMonth : String = month.filter { s -> s == "January" }.single()
// result: "January"
I am just sharing that if you have custom list and check whether it is null or blank you can check in Kotlin in single line
Just do it like that
fun filterList(listCutom: List<Custom>?) {
var fiterList = listCutom!!.filter { it.label != "" }
//Here you can get the list which is not having any kind of lable blank
}
You can check multiple conditions also
fun filterList(listCutom: List<Custom>?) {
var fiterList = listCutom!!.filter { it.label != "" && it.value != ""}
//Here you can get the list which is not having any kind of lable or value blank
}
Note : I am assuming that label & value are the variables of Custom Model class.
You can also use find or findLast. This is specifically meant to return only one value instead of a list of String returned in case of filter.
var month = arrayListOf("January", "February", "March")
var result = month.find { s -> s == "January" }
Filtering by predicate
val numbers = listOf("one", "two", "three", "four")
var items: List<String> = numbers.filter { s -> s == "one" }
var item = numbers.singleOrNull { it == "one" }
if (item != null) {
print("FOUND:$item")
} else {
print("Not FOUND!")
}
I have those data classes:
data class RouteType(
#SerializedName("type")
val type: String,
#SerializedName("items")
val items: List<RouteItem>)
data class RouteItem(
#SerializedName("id")
val id: String,
#SerializedName("route")
private val route: List<DoubleArray>)
I want to filter list of RouteType by type and filter list of RouteItem in it by id.
My code now:
// val filter: HashMap<String, List<String>>
val result = routeTypes // List<RouteType>
.filter { it.type in filter.keys }
.map {
routeType -> routeType.items.filter { it.id in filter[routeType.type]!! }
}
How to make .map return list with filtered list in it? Or maybe there's another way?
EDIT
Thanks, but flatmap not exactly what I need, I think. flatmap returns nested list(List<RouteItem>), but I want List<RouteType>.
I got it by this code:
val result = routeTypes
.filter { it.type in filter.keys }
.map {
routeType -> RouteType(
routeType.type,
routeType.items.filter { it.id in filter[routeType.type]!! })
}
Is there another way to get it?
Since your data is immutable (that's a good thing) you need to copy it while filtering. Use copy to make it more extensible:
val result = routeTypes
.filter { it.type in filter.keys }
.map { it.copy(items = it.items.filter { it.id in filter[routeType.type]!! }) }
You can use flatMap for this, it works as map, but merges all your mapped collections to one:
val result = routeTypes // List<RouteType>
.filter { it.type in filter.keys }
.flatMap {
routeType -> routeType.items.filter { it.id in filter[routeType.type]!! }
}