I am trying to learn kotlin from basic android kotlin codelabs here, where a codelabs explains lambda and higher order functions. It shows an example of higher order function
sortedWith()
we are using this method if we have to sort names list based on string length, as given in the codelab
fun main() {
val peopleNames = listOf("Fred", "Ann", "Barbara", "Joe")
println(peopleNames.sorted())
println(peopleNames.sortedWith { str1: String, str2: String -> str1.length - str2.length })
}
The output of the above is given :
[Ann, Barbara, Fred, Joe]
[Ann, Joe, Fred, Barbara]
which is working fine, if i work on kotlin playground : here
However, if I try to run this code on IntelliJ IDEA, I am getting an error :
Error:(37, 25) Kotlin: Type inference failed: fun <T> Iterable<T>.sortedWith(comparator: kotlin.Comparator<in T> /* = java.util.Comparator<in T> */): List<T>
cannot be applied to
receiver: List<String> arguments: ((String, String) -> Int)
Error:(37, 35) Kotlin: Type mismatch: inferred type is (String, String) -> Int but kotlin.Comparator<in String> /* = java.util.Comparator<in String> */ was expected
Is there anything wrong with my kotlin version? My current kotlin version is :
1.3.50-release-112
Use compareBy
println( peopleNames.sortedWith(compareBy(
{ it.length },
{ it }
)))
output
[Ann, Barbara, Fred, Joe]
[Ann, Joe, Fred, Barbara]
Related
I have a custom Preferences class which uses Kotlin extensions to return a Preference string.
It works perfect in API 28, but won't compile in API 29. With Googles new rules about not allowing app updates that target below API 30, I need to update this app, but can't figure out this basic issue.
Here is my Preference class:
import android.content.SharedPreferences
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class PreferenceProperty<T> internal constructor(
private val getter: SharedPreferences.(key: String, defaultValue: T) -> T,
private val setter: SharedPreferences.Editor.(key: String, value: T) -> SharedPreferences.Editor,
private val defaultValue: T,
private val key: String? = null
) : ReadWriteProperty<SharedPreferences, T> {
override fun getValue(thisRef: SharedPreferences, property: KProperty<*>): T {
return thisRef.getter(key ?: property.name, defaultValue)
}
override fun setValue(thisRef: SharedPreferences, property: KProperty<*>, value: T) {
thisRef.edit().setter(key ?: property.name, value).apply()
}
}
fun SharedPreferences.stringPreference(defaultValue: String): PreferenceProperty<String> {
return PreferenceProperty(
SharedPreferences::getString,
SharedPreferences.Editor::putString,
defaultValue
)
}
fun SharedPreferences.nullableStringPreference(defaultValue: String? = null): PreferenceProperty<String?> {
return PreferenceProperty(
SharedPreferences::getString,
SharedPreferences.Editor::putString,
defaultValue
)
}
I am using the stringPreference, but also need nullableStringPreference for other parts of the code.
I use it as follows:
var notificationToken: String by stringPreference("")
But I get this error:
Type inference failed. Expected type mismatch: inferred type is PreferenceProperty<String?> but PreferenceProperty<String> was expected
So the issue is that, since API 29, it returns String?.
Does anyone know change in API29 caused this and how to work around it?
Thanks
Weird. There must have been something changed in how nullability annotations are interpretted to make it stricter. I don't see any signature or annotation change on the getString() method between the two versions of the SDK. Logically, this is the correct treatment of the return value since it's marked #Nullable. There's no annotation for the return value to match the nullability of a method parameter.
You can replace
SharedPreferences::getString
with
{ key, def -> getString(key, def)!! }
To get it working again. !! is logically safe here, but if you're pedantic about avoiding it you could use:
{ key, def -> getString(key, null) ?: def }
I try to use Firebase Firestore in a Kotlin Android project. I have an issue when I try to instantiate an object with DocumentSnapshot.toObject(Class valueType). I am trying to read a single object from a collection called 'news' with id eq 1. I can read the data but the API won't let me put the document into my custom object.
News.kt
data class News(
val id : String? = "",
val value : String? = ""
)
HomeFragment.kt
val db = FirebaseFirestore.getInstance()
var newsItem = db.collection("news").document("1")
newsItem.get()
.addOnSuccessListener { documentSnapshot ->
android.util.Log.d("TAG", "${documentSnapshot.id} => ${documentSnapshot.data}")
var newsTextView : TextView = view.findViewById(R.id.homeNewsText)
val newsText = documentSnapshot.toObject<News>()
}
Error in IDE is:
None of the following functions can be called with the arguments supplied.
toObject(Class<News!>) where T = News for fun <T : Any!> toObject(valueType: Class<T!>): T? defined in com.google.firebase.firestore.DocumentSnapshot
toObject(Class<News!>, DocumentSnapshot.ServerTimestampBehavior) where T = News for fun <T : Any!> toObject(valueType: Class<T!>, serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior): T? defined in com.google.firebase.firestore.DocumentSnapshot
Thanks!
It looks like you're not including the Kotlin Extensions (KTX) version of the Firebase SDK, which defines the generic overload of toObject that you're trying to use.
I'll show both options below, since this seems to be quite common.
Using the regular Java/Android SDK
Include the SDK with:
implementation 'com.google.firebase:firebase-firestore:21.5.0'
Get the object from the data with:
documentSnapshot.toObject(News::class.java)
Using the Android SDK with Kotlin Extensions
Include the SDK with:
implementation 'com.google.firebase:firebase-firestore-ktx:21.5.0'
Get the object from the data with:
documentSnapshot.toObject<News>()
Kotlin reference document said this example is valid.
https://kotlinlang.org/docs/reference/generics.html#upper-bounds
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
where T : Comparable<T>,
T : Cloneable {
return list.filter { it > threshold }.map { it.clone() }
}
But in Android studio 3.0, it shows thin red line under it.clone(). And error message is:
Type inference failed. Expected type mismatch. Required: List<T>
Found: List<Any>
Why this example cannot be compiled?
The problem is the use of clone(), which is protected as the compiler complains. The problem's already been discussed here: https://discuss.kotlinlang.org/t/is-the-documentation-correct/2925
I know Anko provides the functions parseSingle, parseOpt and parseList , I don't understand why the code of Android Developers (the book) need to design extensions parseList again.
Could you tell me? Thanks!
https://github.com/antoniolg/Kotlin-for-Android-Developers/blob/master/app/src/main/java/com/antonioleiva/weatherapp/data/db/ForecastDb.kt
override fun requestForecastByZipCode(zipCode: Long, date: Long) = forecastDbHelper.use {
val dailyRequest = "${DayForecastTable.CITY_ID} = ? AND ${DayForecastTable.DATE} >= ?"
val dailyForecast = select(DayForecastTable.NAME)
.whereSimple(dailyRequest, zipCode.toString(), date.toString())
.parseList { DayForecast(HashMap(it)) }
}
https://github.com/antoniolg/Kotlin-for-Android-Developers/blob/master/app/src/main/java/com/antonioleiva/weatherapp/extensions/DatabaseExtensions.kt
fun <T : Any> SelectQueryBuilder.parseList(parser: (Map<String, Any?>) -> T): List<T> =
parseList(object : MapRowParser<T> {
override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
})
Anko's parseList takes a MapRowParser, not a function. This simplifies usage. With Anko version you'd write
.parseList { mapRowParser { DayForecast(HashMap(it)) } }
instead. That's assuming there is a constructor function like mapRowParser which I can't find in their sources; otherwise, you could write it quite trivially.
Or rather, it's already written for you in the example code, just not as a separate function:
fun <T> mapRowParser(parser: (Map<String, Any?>) -> T): MapRowParser<T> =
object : MapRowParser<T> {
override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
}
I am honestly really surprised if this function doesn't exist already (maybe called something else, but what?). OTOH, if it does exist, Leiva should have used it.
If you expected simple single column rows result, you can use ready-made Anko parsers:
ShortParser
IntParser
LongParser
FloatParser
DoubleParser
StringParser
BlobParser
Your code might be:
val dailyForecast = select(DayForecastTable.NAME)
.whereSimple(dailyRequest, zipCode.toString(), date.toString())
.parseList(StringParser)
Or you can implement your own parser. Here below sample for three columns result (Int, String, String):
val userParser = rowParser { id: Int, name: String, email: String ->
Triple(id, name, email)
}
I have the following simple Kotlin extension functions:
// Get the views of ViewGroup
inline val ViewGroup.views: List<View>
get() = (0..childCount - 1).map { getChildAt(it) }
// Get the views of ViewGroup of given type
inline fun <reified T : View> ViewGroup.getViewsOfType() : List<T> {
return this.views.filterIsInstance<T>()
}
This code compiles and works fine. But, I want the function getViewsOfType to be a property, just like the views. Android Studio even suggests it. I let AS do the refactoring and it generates this code:
inline val <reified T : View> ViewGroup.viewsOfType: List<T>
get() = this.views.filterIsInstance<T>()
But this code doesn't compile. It causes error: "Type parameter of a property must be used in its receiver type"
What is the issue here? Searching for help on this error doesn't seem to lead to an answer.
The error means that you can only have a generic type parameter for an extension property if you're using said type in the receiver type - the type that you're extending.
For example, you could have an extension that extends T:
val <T: View> T.propName: Unit
get() = Unit
Or one that extends a type that uses T as a parameter:
val <T: View> List<T>.propName: Unit
get() = Unit
As for why this is, I think the reason is that a property can't have a generic type parameter like a function can. While we can call a function with a generic type parameter...
val buttons = viewGroup.getViewsOfType<Button>()
... I don't believe a similar syntax exists for properties:
val buttons = viewGroup.viewsOfType<Button> // ??