I've been playing around with Kotlin for Android. I have a mutable list which is a list of objects. Now I want to persist them, but I don't know what's the best way to do it. I think it can be done with SharedPreferences but I don't know how to parse the objects to a plain format or something. The objects are actually coming from a data class, maybe that can be useful.
Thanks
It is very easy to persist any data within SharedPreferences. All you need to do is get Gson implementation 'com.squareup.retrofit2:converter-gson:2.3.0' and then create class you would like to persist like this:
class UserProfile {
#SerializedName("name") var name: String = ""
#SerializedName("email") var email: String = ""
#SerializedName("age") var age: Int = 10
}
and finally in your SharedPreferences
fun saveUserProfile(userProfile: UserProfile?) {
val serializedUser = gson.toJson(userProfile)
sharedPreferences.edit().putString(USER_PROFILE, serializedUser).apply()
}
fun readUserProfile(): UserProfile? {
val serializedUser = sharedPreferences.getString(USER_PROFILE, null)
return gson.fromJson(serializedUser, UserProfile::class.java)
}
You can also use Firebase too for data persistence, you can use SharedPreferences, but personally I find it more easy and comfortable using Firebase.
I will leave a link here so you can take a look at disk persistence behavior of Firebase.
Hope this helps you!
For lists it think using sqlite will provide you with better control of your data.
Check out anko's sqlite wiki for more info.
Shared Preferences are mostly used in storing data like settings or configs, it's not meant for storing large data though it can do it as long as you implement Parcable. If you think your data contained in your MutableList is large, best way is to make a database.
Feel free to use this Kotlin extension functions to store and edit your list to SharedPreferences. I think they are realy useful.
inline fun <reified T> SharedPreferences.addItemToList(spListKey: String, item: T) {
val savedList = getList<T>(spListKey).toMutableList()
savedList.add(item)
val listJson = Gson().toJson(savedList)
edit { putString(spListKey, listJson) }
}
inline fun <reified T> SharedPreferences.removeItemFromList(spListKey: String, item: T) {
val savedList = getList<T>(spListKey).toMutableList()
savedList.remove(item)
val listJson = Gson().toJson(savedList)
edit {
putString(spListKey, listJson)
}
}
fun <T> SharedPreferences.putList(spListKey: String, list: List<T>) {
val listJson = Gson().toJson(list)
edit {
putString(spListKey, listJson)
}
}
inline fun <reified T> SharedPreferences.getList(spListKey: String): List<T> {
val listJson = getString(spListKey, "")
if (!listJson.isNullOrBlank()) {
val type = object : TypeToken<List<T>>() {}.type
return Gson().fromJson(listJson, type)
}
return listOf()
}
Related
I want to reference an object within this class I have below:
class HerbData {
object Dill {
const val herbName: String = "This is Dill!"
const val scientificName: String = "Anethum Graveolens"
val dullThumbnail: Int = R.drawable.dill_thumbnail_attr
}
object Peppermint {
val herbName: String = "This is Peppermint!"
}
}
Is there anyway that I can reference the object by using a string in Kotlin? Here is somewhat what I mean:
HerbData."Dill".herbname
I can't find anything on this topic for Kotlin.
Another way you could do this is with an enum class. The advantage over a map is that you have a data structure you can reference directly in code, so you could use HerbData.Dill as well as HerbData["Dill"]. And that will enable you to take advantage of compile-time checking and lint warnings, refactoring, exhaustive pattern matching, code completion etc, because the data is defined in your code
enum class HerbData(
val herbName: String,
val scientificName: String? = null,
val dullThumbnail: Int? = null
) {
Dill("This is Dill!", "Anethum Graveolens", R.drawable.dill_thumbnail_attr),
Peppermint("This is Peppermint!");
companion object {
operator fun get(name: String): HerbData? =
try { valueOf(name) } catch(e: IllegalArgumentException) { null }
}
}
fun main() {
// no guarantee these lookups exist, need to null-check them
HerbData["Peppermint"]?.herbName.run(::println)
// case-sensitive so this fails
HerbData["peppermint"]?.herbName.run(::println)
// this name is defined in the type system though! No checking required
HerbData.Peppermint.herbName.run(::println)
}
>> This is Peppermint!
null
This is Peppermint!
Enum classes have that valueOf(String) method that lets you look up a constant by name, but it throws an exception if nothing matches. I added it as a get operator function on the class, so you can use the typical getter access like a map (e.g. HerbData["Dill"]). As an alternative, you could do something a bit neater:
companion object {
// storing all the enum constants for lookups
private val values = values()
operator fun get(name: String): HerbData? =
values.find() { it.name.equals(name, ignoreCase = true) }
}
You could tweak the efficiency on this (I'm just storing the result of values() since that call creates a new array each time) but it's pretty simple - you're just storing all the enum entries and creating a lookup based on the name. That lets you be a little smarter if you need to, like making the lookup case-insensitive (which may or may not be a good thing, depending on why you're doing this)
The advantage here is that you're generating the lookup automatically - if you ever refactor the name of an enum constant, the string label will always match it (which you can get from the enum constant itself using its name property). Any "Dill" strings in your code will stay as "Dill" of course - that's the limitation of using hardcoded string lookups
The question really is, why do you want to do this? If it's pure data where no items need to be explicitly referenced in code, and it's all looked up at runtime, you should probably use a data class and a map, or something along those lines. If you do need to reference them as objects within the code at compile time (and trying to use HerbData."Dill".herbName implies you do) then an enum is a fairly easy way to let you do both
Declare a Data Class
data class HerbData (
val scientificName: String,
val dullThumbnail: Int
)
Initialize a muteable map and put data in it
val herbData = mutableMapOf<String, HerbData>()
herbData.put("Dill", HerbData("Anethum Graveolens", R.drawable.dill_thumbnail_attr))
herbData.put("Peppermint", HerbData("Mentha piperita", R.drawable.peppermint_thumbnail_attr))
You can now just
herbData["Dill"]?.scientificName
class HerbData {
interface Herb {
val herbName: String
val scientificName: String
}
object Dill : Herb {
override val herbName: String = "This is Dill!"
override val scientificName: String = "Anethum Graveolens"
}
object Peppermint: Herb {
override val herbName: String = "This is Peppermint!"
override val scientificName: String = "Mentha piperita"
}
companion object {
operator fun get(name: String): Herb? {
return HerbData::class
.nestedClasses
.find { it.simpleName == name }
?.objectInstance as? Herb
}
}
}
println(HerbData["Dill"]?.herbName) // Prints: This is Dill!
println(HerbData["Peppermint"]?.scientificName) // Prints: Mentha piperita
println(HerbData["Pepper"]?.herbName) // Prints: null
I need to make a small app that needs to parse from a raw JSON file around 200K cities.
I can't use any DB, but the user needs to be able to perform a city search.
The problem with the approach is that parsing such a big JSON file takes a long time, but I can't think of any other approach. I have a couple of requirements:
Optimise for fast searches. Loading time of the app is not so important.
Time efficiency for filter algorithm should be better than linear.
The list can be preprocessed into any other representation that you consider more efficient for searches and display. Provide information of why that representation is more efficient in the comments of the code.
In my repository I have something like:
class CitiesRepository(private val application: Application) {
fun getCities(): List<City> {
val bufferReader = application.assets.open("cities.json").bufferedReader()
val data = bufferReader.use {
it.readText()
}
val gson = GsonBuilder().create()
val type: Type = object : TypeToken<ArrayList<City?>?>() {}.type
return gson.fromJson(data, type)
}
}
and my ViewModel consists of:
class CitiesListViewModel(val app: Application) : AndroidViewModel(app) {
private val repository: CitiesRepository = CitiesRepository(app)
val allCities: LiveData<List<City>> =
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
val cities = repository.getCities()
withContext(Dispatchers.Main) {
emit(cities)
}
}
}
Is there anything else (better) I can do?
Thanks in advance!
I have ChatMessage class like the code below:
class ChatMessage(val uid: String, val text: String, val fromId: String, val toId: String, val timestamp: Long) {}
it has timestamp as its property
I have an empty list like this, later I will populate this messageList with the data:
val messageList = ArrayList<ChatMessage>()
and I want to rearrange that messageList based on the timestamp, so the lowest timestamp will be in the index 0 of that array.
how to do that in Kotlin ? I am sorry if this is trivial, I am new in programming and in Kotlin.
You can use sorting function from Kotlin standard library.
Let's say, your messageList need to sort based on the timestamp then use following syntax below:
val sortedList :List<ChatMessage> = messageList.sortedBy { chatMessage :ChatMessage -> chatMessage.timestamp }
Above piece of code will return you sorted List<ChatMessage> which you can further use.
Find out more here.
You can use sortedWith with compareBy like this:
val sortedList = messageList.sortedWith(compareBy({ it.timestamp }))
You can even sort by multiple properties:
val sortedList = messageList.sortedWith(compareBy({ it.timestamp }, { it.fromId }))
Welcome to Kotlin
1) You can omit the curly braces for a class if they are empty, also your ChatMessage seems to be a good fit for a data class:
data class ChatMessage(val uid: String, val text: String, val fromId: String, val toId: String, val timestamp: Long)
2) You could create a container which you use to collect all messages in an ordered PriorityQueue, especially if you only need it ordered. This could look like this:
class MessageContainer {
private val messageList = PriorityQueue<ChatMessage>(Comparator { c1: ChatMessage, c2: ChatMessage ->
c1.timestamp.compareTo(c2.timestamp)
})
fun addMessage(msg: ChatMessage) = messageList.offer(msg)
fun getAllMessages(): List<ChatMessage> {
val ordered = mutableListOf<ChatMessage>()
while (!messageList.isEmpty()) ordered.add(messageList.remove())
return ordered.toList()
}
}
Now if you insert objects of ChatMessage via addMessage, they will be ordered in the queue directly and when you invoke getAllMessages, you receive an ordered List<ChatMessage>:
val container = MessageContainer()
repeat(20) {
container.addMessage(ChatMessage("text$it", (10L..10_000L).random()))
}
container.getAllMessages() //will be ordered
I am using Moshi to deserialize json from our server but I have come across an issue I’m sure has a solution, I just can’t see it. Over the socket, we are send json that, at the top level, has three fields:
{
"data_type": "<actual_data_type>",
"data_id": "<actual_data_id>",
"data": <data_object>
}
The issue is that the data can actually be several different objects based on what data_type is can I’m not sure how to pass that information into the adaptor for Data. I’ve tried a couple different things, but it just gets closer and closer to me parsing the whole thing myself, which seems to defeat the point. Is there a way to pass information from one adaptor to another?
For anyone who wants to do something similar, I took the basic shape of a generic factory from here: https://github.com/square/moshi/pull/264/files (which also what #eric cochran is recommending in his comment) and made it more specific to fit my exact case.
class EventResponseAdapterFactory : JsonAdapter.Factory {
private val labelKey = "data_type"
private val subtypeToLabel = hashMapOf<String, Class<out BaseData>>(
DataType.CURRENT_POWER.toString() to CurrentPower::class.java,
DataType.DEVICE_STATUS_CHANGED.toString() to DeviceStatus::class.java,
DataType.EPISODE_EVENT.toString() to EpisodeEvent::class.java,
DataType.APPLIANCE_INSTANCE_UPDATED.toString() to ApplianceInstanceUpdated::class.java,
DataType.RECURRING_PATTERNS.toString() to RecurringPatternOccurrence::class.java,
DataType.RECURRING_PATTERN_UPDATED.toString() to RecurringPatternUpdated::class.java
)
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
if (!annotations.isEmpty() || type != EventResponse::class.java) {
return null
}
val size = subtypeToLabel.size
val labelToDelegate = LinkedHashMap<String, JsonAdapter<EventResponse<BaseData>>>(size)
for (entry in subtypeToLabel.entries) {
val key = entry.key
val value = entry.value
val parameterizedType = Types.newParameterizedType(EventResponse::class.java, value)
val delegate = moshi.adapter<EventResponse<BaseData>>(parameterizedType, annotations)
labelToDelegate.put(key, delegate)
}
return EventResponseAdapter(
labelKey,
labelToDelegate
)
}
private class EventResponseAdapter internal constructor(
private val labelKey: String,
private val labelToDelegate: LinkedHashMap<String, JsonAdapter<EventResponse<BaseData>>>
) : JsonAdapter<EventResponse<BaseData>>() {
override fun fromJson(reader: JsonReader): EventResponse<BaseData>? {
val raw = reader.readJsonValue()
if (raw !is Map<*, *>) {
throw JsonDataException("Value must be a JSON object but had a value of $raw of type ${raw?.javaClass}")
}
val label = raw.get(labelKey) ?: throw JsonDataException("Missing label for $labelKey")
if (label !is String) {
throw JsonDataException("Label for $labelKey must be a string but had a value of $label of type ${label.javaClass}")
}
val delegate = labelToDelegate[label] ?: return null
return delegate.fromJsonValue(raw)
}
// Not used
override fun toJson(writer: JsonWriter, value: EventResponse<BaseData>?) {}
}
}
The only thing to watch out for is that the RuntimeJsonAdapterFactory in the link uses Types.getRawType(type) to get the type with the generics stripped away. We, of course, don't want that because once the specific generic type has been found, we want the normal Moshi adapters to kick in and do the proper parsing for us.
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!)