Kotlin DataBinding pass static function into layout xml - android

In Java, i can easily pass static function to layout xml using:
public static String formatUnixTime(long timeInSeconds, String pattern) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, Locale.US);
String value = simpleDateFormat.format(new Date(timeInSeconds * 1000));
return value;
}
in xml:
android:text='#{Utils.formatUnixTime(model.start_time, "hh:mm:ss")}'
But i tried in Kotlin with companion but no luck. It said
error: cannot find symbol
import my.package.name.HistoryItemBindingImpl;
^
symbol: class HistoryItemBindingImpl
location: package my.package.name
This is what i tried in kotlin
class Utils {
companion object {
fun formatUnixTime(timeInSeconds : Long, pattern: String) : String {
val simpleDateFormat = SimpleDateFormat(pattern, Locale.US)
val value = simpleDateFormat.format(Date(timeInSeconds * 1000))
return value
}
}
And in xml
android:text='#{Utils.Companion.formatUnixTime(model.start_time, "hh:mm:ss")}'
Really hope someone can help. Thanks!
Update
With #Max Aves help. I fixed my code and below code will work. Maybe it will help someone.
class Utils {
companion object {
#JvmStatic
fun formatUnixTime(timeInSeconds : Long, pattern: String) : String {
val simpleDateFormat = SimpleDateFormat(pattern, Locale.US)
val value = simpleDateFormat.format(Date(timeInSeconds * 1000))
return value
}
And you can use this in xml
android:text='#{Utils.formatUnixTime(model.start_time, "hh:mm:ss")}'

Have you tried adding #JvmStatic annotation? It should help!
From official source:
Specifies that an additional static method needs to be generated from
this element if it's a function. If this element is a property,
additional static getter/setter methods should be generated.

Utilities are generally created as Kotlin File. Because Kotlin files methods are global. Which you can use from anywhere without making it #JvmStatic.
BindingAdapterDefault.kt
fun formatUnixTime(timeInSeconds: Long, pattern: String): String {
val simpleDateFormat = SimpleDateFormat(pattern, Locale.US)
return simpleDateFormat.format(Date(timeInSeconds * 1000))
}
That will work for you same, NO class, brackets, companion, object etc...
From XML
<import type="com.innovanathinklabs.sample.ui2.BindingAdapterDefaultKt"/>
android:text="#{BindingAdapterDefaultKt.formatUnixTime(1540966388,`hh:mm:ss`)}"
That's all you need.
I want add more info about this. You can directly call this method from Java and Kotlin too.
From Java class
import static com.package.BindingAdapterDefaultKt.formatUnixTime;
formatUnixTime(454545, "hh:mm:ss");
From Kotlin class
import com.package.formatUnixTime
formatUnixTime(454545, "hh:mm:ss");
Suggestion
I prefer creating BindingAdapter which was introduced with Data Binding, and is very powerful thing. It gives me more flexibility across the app.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:formatSeconds="#{1540966388}"
app:pattern="#{`hh:mm:ss`}"
/>
BindingAdapterDefault.kt
#BindingAdapter(value = ["formatSeconds", "pattern"])
fun secondsToDateText(textView: TextView, timeInSeconds: Long, pattern: String) {
val simpleDateFormat = SimpleDateFormat(pattern, Locale.US)
textView.text = simpleDateFormat.format(Date(timeInSeconds * 1000))
}

If you don't want to use #JvmStatic, you can use object instead of class:
object Utils {
fun formatUnixTime(timeInSeconds : Long, pattern: String) : String {
val simpleDateFormat = SimpleDateFormat(pattern, Locale.US)
val value = simpleDateFormat.format(Date(timeInSeconds * 1000))
return value
}
}
From xml import the Utils singleton class and then:
android:text='#{Utils.INSTANCE.formatUnixTime(model.start_time, "hh:mm:ss")}'

Related

How to work with KotlinX serialization for ArrayList<String> and Date types with Algolia

I am trying to use the kotlinx serialization with Algolia for types ArrayList<String> and Date but I cannot figure out the serialization technique. Any pointers because I have resorted to the manual way that is not recommended by Algolia.
Check this one
kotlinx.serialization
import kotlinx.serialization.*
import kotlinx.serialization.internal.*
import java.util.*
#Serializable
class DateWrapper(val date: Date)
#Serializer(forClass = Date::class)
object DateSerializer: KSerializer<Date> {
private val df: DateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SSS")
override fun save(output: KOutput, obj: Date) {
output.writeStringValue(df.format(obj))
}
override fun load(input: KInput): Date {
return df.parse(input.readStringValue())
}
override val serialClassDesc: KSerialClassDesc = SerialClassDescImpl("Date")
}
Register it at startup of your application:
kotlinx.serialization.registerSerializer("java.util.Date", DateSerializer)
JSON.stringify(DateWrapper(Date())) will give you {"date":["Date","10/11/2019 12:50:10.665"]}

Kotlin annotation - Require a parameter is a Constant variable from specific class

I have a function filter here
fun filter(category: String) {
...
}
and a Class with many constant string
object Constants {
val CAT_SPORT = "CAT_SPORT"
val CAT_CAR = "CAT_CAR"
...
}
How to ensure the parameter category is a constant string from Constants (or throw warning)?
I am looking for something like #StringRes.
I know Enum may do the trick but prefer not to code refactor at this moment.
Using androidx.annotation you can do something like this:
object Constants {
#Retention(AnnotationRetention.SOURCE)
#StringDef(CAT_SPORT, CAT_CAR)
annotation class Category
const val CAT_SPORT = "CAT_SPORT"
const val CAT_CAR = "CAT_CAR"
}
fun filter(#Constants.Category category: String) {
...
}

App level kotlin function in android project

Can anybody explain how I can release an app-level kotlin function in Android Studio project? I have an Android application and I try do someting like this:
var date: Date = Date()
/////////////////////////////////////////////////////////
// this block must be app-level fun
val format = “dd.MM.yyyy”
val simpleDateFormat = SimpleDateFormat(format)
var formattedDate = simpleDateFormat.format(date)
/////////////////////////////////////////////////////////
convert Date object to String with custom format. I do it many times (in different activities and fragments) in my project, so I think it will be good idea to releas this code as function (or class, if it will be more efficient). Thus I have date and format as input parameters and formattedDate as output. Also it will be good to set default format value
You can create an extension function on Date that accepts a format and uses it to convert the date to that format. You can also define the default format on the input parameter. Something like:
fun Date.toFormattedString(format: String = "dd.MM.yyyy"): String {
val simpleDateFormat = SimpleDateFormat(format)
return simpleDateFormat.format(this)
}
Place it in a file where the whole app can access it (e.g., a file named Extensions.kt in a module/package where you put all reusable and/or helper code) and then just use the function like someDate.toFormattedString().
Here is an example of my function contained in separate kotlin file called Time.kt
fun timeConverter(string: String?, i: Int): String {
val isoFormat = "yyyy-MM-dd'T'HH:mm:ss"
var expectedFormat = "dd/MM"
when(i){
0 -> expectedFormat = "dd/MM"
1 -> expectedFormat = "EEE"
2 -> expectedFormat = "HH:mm"
3 -> expectedFormat = "EEE, dd/MM"
}
val dateFormat = SimpleDateFormat(isoFormat, Locale.getDefault())
val date = dateFormat.parse(string)
return SimpleDateFormat(expectedFormat).format(date)
}
Make the function part of an object.
https://www.baeldung.com/kotlin-objects
Objects (not class) in Kotlin are static. If you import the object from where you use the function, it can be used anywhere without being instantiated.
You can have a DateUtil class that holds a format function as companion. You will be able to use it anywhere in your app without instantiating it.
class DateUtil{
companion object {
fun format(date: Date):String{
val format = "dd.MM.yyyy"
val simpleDateFormat = SimpleDateFormat(format)
return simpleDateFormat.format(date)
}
}
}
Then you call it: DateUtil.format(Date())

Kotlin: Java Util Date to String for Databindings

I want to use the Date value of my Data class in view via Databinding.
If I use the toString() method on the Date field it works. But I want to customize the Date value.
So I created the Utils object with Method. This is the Util object
object DateUtils {
fun toSimpleString(date: Date) : String {
val format = SimpleDateFormat("dd/MM/yyy")
return format.format(date)
}
}
But if I want to use this method in the xml like this
<data>
<import type="de.mjkd.journeylogger.Utils.DateUtils"/>
<variable
name="journey"
type="de.mjkd.journeylogger.data.Journey"/>
</data>
...
android:text="#{DateUtils.toSimpleString(journey.date)}"
I get an error cannot find method toSimpleString(java.util.Date) in class ...
This is my Dataclass:
data class Journey(var title: String, var date: Date?, var destination: String)
Whats wrong with this code?
Using the reserved word object in kotlin, that you really doing is declare a single instance. the equivalent in java is something more or less like:
class DataUtils {
static DataUtils INSTANCE;
public String toSimpleString()...
}
then when you call it you do a DateUtils.INSTANCE.toSimpleString()
You should capable to use DateUtils.INSTANCE.toSimpleString() in your xml
In order to make toSimpleString accessible from static context, you have to flag the method with#JvmStatic
object DateUtils {
#JvmStatic
fun toSimpleString(date: Date) : String {
val format = SimpleDateFormat("dd/MM/yyy")
return format.format(date)
}
}
Using extension function(doc)
#file:JvmName("DateUtils")//Use this to change your class name in java, by default is <the file name>Kt (DateUtilsKt in your case)
fun Date.toSimpleString() : String {
val format = SimpleDateFormat("dd/MM/yyy")
return format.format(this)
}
Then you can use it directly in xml as you are already doing:
android:text="#{DateUtils.toSimpleString(journey.date)}"
Why don't you just use a top-level function which is static by default? A top-level function is not defined in any class.
fun main(args: Array<String>){
println(toSimpleString(Date()))
}
fun toSimpleString(date: Date?) = with(date ?: Date()) {
SimpleDateFormat("dd/MM/yyy").format(this)
}
Also, notice how Jouney's date is nullable in your example and your toSimpleString only accepts a non-nullable Date!
I changed it, so that it will return the string of the current date in case null is passed.
More easy way would be to make a getDateString in model class.
android:text="#{journey.dateString)}"
class Journey {
lateinit var date: Date
fun getDateString(){
return DataUtils.toSimpleString(date)
}
}
I like this way because I don't need to import any class in this case.

Android Room error: TypeConverter not recognised for List of Enums

The Room library is not recognizing a TypeConverter I created for a List of enums. However, when I change this to an ArrayList of enums it works fine. Anyone has any idea why and what can I do to make this work with List? (Using List in Kotlin is easier and I really don't wanna be converting back and forwards to ArrayList just because of this).
Here is my code:
My model:
#Entity
data class Example(#PrimaryKey val id: String?,
val name: String,
var days: List<DayOfWeek>?)
DayOfWeek is an enum:
enum class DayOfWeek {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY;
val value: Int
get() = ordinal + 1
companion object {
private val ENUMS = DayOfWeek.values()
fun of(dayOfWeek: Int): DayOfWeek {
if (dayOfWeek < 1 || dayOfWeek > 7) {
throw RuntimeException("Invalid value for DayOfWeek: " + dayOfWeek)
}
return ENUMS[dayOfWeek - 1]
}
}
}
My TypeConverter:
private const val SEPARATOR = ","
class DayOfWeekConverter {
#TypeConverter
fun daysOfWeekToString(daysOfWeek: List<DayOfWeek>?): String? {
return daysOfWeek?.map { it.value }?.joinToString(separator = SEPARATOR)
}
#TypeConverter
fun stringToDaysOfWeek(daysOfWeek: String?): List<DayOfWeek>? {
return daysOfWeek?.split(SEPARATOR)?.map { DayOfWeek.of(it.toInt()) }
}
}
And I set it in my DB class like this:
#Database(entities = arrayOf(Example::class), version = 1)
#TypeConverters(DayOfWeekConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun exampleDao(): ExampleDao
}
My DAO looks like this:
#Dao
interface ExampleDao {
#Query("SELECT * FROM example")
fun getAll(): LiveData<List<Example>>
#Insert(onConflict = REPLACE)
fun save(examples: List<Example>)
}
The error I get with this code is:
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
e:
e: private java.util.List<? extends com.example.DayOfWeek> days;
Like I said above, if I change the days property to ArrayList<DayOfWeek> (and make the changes to ArrayList in DayOfWeekConverter) then everything works fine. If anyone can help me figure this out and tell me how I can use List here it'd be of great help, it is driving me crazy :/.
For some reason, Room does not like Kotlin List, but when I've replaced List with MutableList it started to work:
#Entity
data class Example(#PrimaryKey val id: String,
val name: String,
var days: MutableList<DayOfWeek>?)
class DayOfWeekConverter {
companion object {
#TypeConverter
#JvmStatic
fun daysOfWeekToString(daysOfWeek: MutableList<DayOfWeek>?): String? =
daysOfWeek?.map { it.value }?.joinToString(separator = SEPARATOR)
#TypeConverter
#JvmStatic
fun stringToDaysOfWeek(daysOfWeek: String?): MutableList<DayOfWeek>? =
daysOfWeek?.split(SEPARATOR)?.map { DayOfWeek.of(it.toInt()) }?.toMutableList()
}
}
This is not perfect solution, but hope you can investigate more with that.
Also you need to change #PrimaryKey to be not nullable
The full signature of List in Kotlin is List<out E>(List<? extend E> in Java), it doesn't make any sense to convert such generic type. In other words, Room doesn't know if the input is a DayOfWeek or its subclass.
As for ArrayList and MutableList, their full signature is ArrayList<E> and MutableList<E> correspondingly, the input type is fixed and hence Room knows how to convert it.
We have no way to store and get a List enum without array list. Room does not support it. But if you want to avoid using array list, you can create an object ListDayOfWeek with List is an attribute. I tried it and it's ok. If you need code, please reply here. I will post it.
You should not store it like that into your database. Better build something like that and store it as int:
enum class DaysOfWeek(var bitValue: Int) {
Monday(64),
Tuesday(32),
Wednesday(16),
Thursday(8),
Friday(4),
Saturday(2),
Sunday(1);
}
Finally your utility functions to convert from/to enum.
fun daysToBitValue(days: EnumSet<DaysOfWeek>): Int {
var daysBitValue = 0
for (`val` in days) daysBitValue += `val`.bitValue
return daysBitValue
}
private fun fromBitValues(origBitMask: Int): EnumSet<DaysOfWeek> {
val ret_val = EnumSet.noneOf(DaysOfWeek::class.java)
var bitMask = origBitMask
for (`val` in DaysOfWeek.values()) {
if (`val`.bitValue and bitMask == `val`.bitValue) {
bitMask = bitMask and `val`.bitValue.inv()
ret_val.add(`val`)
}
}
if (bitMask != 0) {
throw IllegalArgumentException(String.format(Locale.getDefault(), "Bit mask value 0x%X(%d) has unsupported bits 0x%X. Extracted values: %s", origBitMask, origBitMask, bitMask, ret_val))
}
return ret_val
}
Now you can either store an int and get the weekdays later:
#Entity
data class Example(#PrimaryKey val id: String?,
val name: String,
var days: Int = 0)
or you use the enum and write a proper typeconverter for that.
#Entity
data class Example(#PrimaryKey val id: String?,
val name: String,
var days: EnumSet<DaysOfWeek>?)
class DayOfWeekConverter {
#TypeConverter
fun daysOfWeekToString(daysOfWeek: EnumSet<DayOfWeek>?) =
daysOfWeek?.let{ YourUtilities.daysToBitValue(it) }
#TypeConverter
fun intToDaysOfWeek(daysOfWeek: Int?) =
daysOfWeek?.let { YourUtilities.fromBitValues(it) }
}
You can loop through the enum and get all days by using
for (aDaysOfWeekEnumSet in daysOfWeekEnumSet)
info{ "day = ${aDaysOfWeekEnumSet.name"}
Code is untested because im not on my Computer, but this should show the concept which works better then storing an enum (which is just an alias to a predefined value!)

Categories

Resources