I'm trying to save user data via a Player class as seen below:
class Player(name: String, age: Int, gender: String) {
}
and I'm wondering what the best way to save the class instances is. I think internal storage fits best as it's internal app data that the user doesn't need to directly access.
However there are not many resources that explain saving class instances - I only see examples of saving key-value pairs.
Code:
import kotlinx.android.synthetic.main.activity_player_details.*
class PlayerDetails : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player_details)
val intent = getIntent()
val players = intent.getIntExtra("number_of_players", 1)
println(players)
next_details.setOnClickListener(this)
}
override fun onClick(v: View?) {
val name: String = player_name.text.toString()
val age = if (player_age.text.toString().isNotEmpty()) player_age.text.toString().toInt() else 0
val genderId: Int = gender.checkedRadioButtonId
val gender: String = if (genderId > 0) resources.getResourceEntryName(genderId) else ""
if (name.isNotEmpty() && genderId > 0 && age > 0 ){
println(name)
println(age)
println(gender)
val player = Player(name, age, gender) // I WANT TO SAVE THIS INSTANCE
} else {
blankFields()
}
}
private fun blankFields() {
blank_fields_error.visibility = View.VISIBLE
}
}
Any advice appreciated.
Basically what you're asking is called "serialization".
In Android you have several ways to serialize an object for storage:
Use Java standard serialization (not recommended). Note that this requires a binary storage (e.g. database BLOB) or be converted to Base64 to store in a text format.
Use a serialization library, e.g. JSON, YAML, etc... This is going to be several magnitudes slower that a binary serialization (Android's Parcelable or Java's Serializable), and also slower than binary + Base64, so in my opinion not really a valid option unless you absolutely want the data stored to be human-readable.
Note that Parcelable is not suitable for consitent storage, so it is not an option.
Note that however, in my experience, I tested a lot of serialization methods (mainly for for IPC) and Serializable was fast enough without adding all the bloated code to use Parcelable. Parcelable only provided a negligible speed gain not worth the hassle of implementing and correctly maintaining Parcelable classes.
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 am using a POJO data class with GSON to parse the data which is being called from the Firestore database.
For example, I have a POJO class with few non-nullable, and nullable values like userID
data class Users(id:String="", userID:String="" ...)
I am then using GSON to parse the JSON data to object for that class
val gson = Gson()
val jsonObjects = gson.toJson(querySnapshot.data)
val parseData = gson.fromJson(jsonObjects,Users::class.java)
My question is if someone uploads data in the database and forgets to add the userID (i.e. it is null), is there a way I can check if the User data class is valid when being parsed?
I am using a check like if(userID == ""){return false} . But as the number of non-nullable fields grows it gets tedious and there must be a better way to check this.
My question is if someone uploads data in the database and forgets to add the userID (i.e. it is null), is there a way I can check if the User data class is valid when being parsed?
If you don't want to have null values at all, why would you then let the user the possibility to provide incomplete data? The simplest solution would be to restrict the data that is added to the database. How? Simply by creating some constraints. For example, your users cannot get access to a feature in your app if they do not fill in all the required fields. That's an operation that is widely used.
If you already have null values, then checking against nullity is a solution you can go ahead with. In Kotlin, null safety can be handled in many ways, either by checking for null in conditions, using safe calls, elvis operator or even using the !! operator.
Maybe the extension method of Kotlin is an accepted approach.
Let me show a demo, I assume the class User looks like this:
data class User(
val id: String,
val userId: String? // nullable
)
Create a extension method User.isValid() to verify the fields you want.
fun User.isValid() = when {
userId.isNullOrEmpty() -> false
else -> true
}
The method isNullOrEmpty() is in stdlib of Kotlin. I test the code, it works fine.
val user1 = User("id 001", null)
val user2 = User("id 002", "userId 001")
println(user1.isValid()) //==> print false
println(user2.isValid()) //==> print true
Now, back to your worry point:
...But as the number of non nullable fields grow it gets tedious
I changed the class User
data class User(
val id: String,
val userId: String?,
val email: String?,
val name: String?
)
it means that when the fields of userId, email, name, any of them is null, the User is invalid.
Just add conditions in extention method, like this:
fun User.isValid() = when {
userId.isNullOrEmpty()
|| email.isNullOrEmpty()
|| name.isNullOrEmpty() -> false
else -> true
}
We just need to maintain the method of isValid.
conclusion
Kotlin Extension Method can be used in your case.
It's better don't use id="", also can create an extension method for this empty string if need.
fun String.Companion.empty() = ""
data class User(
val id: String,
val userId: String? = String.empty()
...
)
All the extension methods can be placed in a class, like UserExt.kt for easy maintaining.
I'm new to coding in kotlin and want to implement an immutable class that represents a project with various fields inside.
The easiest way to do this is by using a data class and using the copy() method so that anytime one of the app user modifies a field it results in the backend in a call to the copy method with the modified field producing the new project.
My problem is that this way does not allow for prior checking of parameters (eg : limit string size of the owner, making sure the number of people added to the project is reasonable etc).
If this was java, I'd use a builder pattern but this seems to defeat the purpose of kotlin, and i've read articles that are positive to using builders in kotlin (https://www.baeldung.com/kotlin/builder-pattern)
and others that are completely against (https://code-held.com/2021/01/23/dont-use-builder-in-kotlin/).
I haven't found any way to "modify" the copy method and to add the parameter sanitization checks that are needed for each parameter. I would appreciate any "smooth" idea to implement this, if anybody has found it. The goal would also be to throw exeptions/sealed classes variables so that the app UI can tell the user what went wrong instead of a generic error message just mentioning that the project was not modified.
I agree with the second link. If you look at the comments on the Baeldung article, you'll see even they were convinced and pledged to revise the article.
You can throw exceptions in an init block but if these are exceptions that are not caused by programmer error, it would be more Kotlin-idiomatic to expose a single constructor-like function that returns a wrapper or just null for invalid input.
Examples:
data class Person(val name: String, val age: Int = 0) {
init {
if (age < 0) {
throw IllegalArgumentException("Age $age is less than 0.")
}
}
}
If you want to return a wrapper or nullable, a data class isn't suitable for preventing invalid input because the generated copy() function will always return a fully constructed object. Sadly, Kotlin does not support overriding the generated copy() function.
sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val reason: String): Result<T>()
class Person private constructor(val name: String, val age: Int = 0) {
companion object {
fun build(name: String, age: Int = 0): Result<Person> {
return when {
age < 0 -> Failure("Age $age is less than 0.")
else -> Success(Person(name, age))
}
}
}
fun buildCopy(name: String = this.name, age: Int = this.age) = build(name, age)
}
I have two different data class, eg:
Guitar & Piano.
I wanna create a list to store both data class, eg: instruments,
so that I can add both dataclass into list by:
instruments.add(Guitar())
instruments.add(Piano())
I thinking about using:
val instruments = arrayListOf<Any>()
My question is it any better way to achieve this?
Kotlin does not support multi-typing. However, you can apply workarounds.
First, to help modeling your problem, you could create a super type (interface or abstract class) as suggested in comments, to extract common properties, or just have a "marker" interface. It allows to narrow accepted objects to a certain category, and improve control.
Anyhow, you can filter any list to get back only values of wanted type using filterIsInstance :
enum class InstrumentFamily {
Strings, Keyboards, Winds, Percussions
}
abstract class Instrument(val family : InstrumentFamily)
data class Guitar(val stringCount : Int) : Instrument(InstrumentFamily.Strings)
data class Piano(val year: Int) : Instrument(InstrumentFamily.Keyboards)
fun main() {
val mix = listOf(Guitar(6), Piano(1960), null, Guitar(7), Piano(2010))
val guitars: List<Guitar> = mix.filterIsInstance<Guitar>()
guitars.forEach { println(it) }
val pianos : List<Piano> = mix.filterIsInstance<Piano>()
pianos.forEach { println(it) }
}
However, beware that this operator will scan all list, so it can become slow if used with large lists or many times. So, don't rely on it too much.
Another workaround would be to create an index per type, and use sealed classes to ensure full control over possible types (but therefore, you'll lose extensibility capabilities).
Exemple :
import kotlin.reflect.KClass
enum class InstrumentFamily {
Strings, Keyboards, Winds, Percussions
}
sealed class Instrument(val family : InstrumentFamily)
data class Guitar(val stringCount : Int) : Instrument(InstrumentFamily.Strings)
data class Piano(val year: Int) : Instrument(InstrumentFamily.Keyboards)
/** Custom mapping by value type */
class InstrumentContainer(private val valuesByType : MutableMap<KClass<out Instrument>, List<Instrument>> = mutableMapOf()) : Map<KClass<out Instrument>, List<Instrument>> by valuesByType {
/** When receiving an instrument, store it in a sublist specialized for its type */
fun add(instrument: Instrument) {
valuesByType.merge(instrument::class, listOf(instrument)) { l1, l2 -> l1 + l2}
}
/** Retrieve all objects stored for a given subtype */
inline fun <reified I :Instrument> get() = get(I::class) as List<out I>
}
fun main() {
val mix = listOf(Guitar(6), Piano(1960), null, Guitar(7), Piano(2010))
val container = InstrumentContainer()
mix.forEach { if (it != null) container.add(it) }
container.get<Guitar>().forEach { println(it) }
}
I am trying to create a time stamp using the firebase server value.
In the documentation in says to use
firebase.database.ServerValue.TIMESTAMP
*edit: before I didn't realize I was looking at the JS documentation. I've relinked it to the Android ones though they don't seem to specify a format there. I'm still looking.
But it return unresolved referance "firebase"
I've looked at other similar question, and they offered other formats like Firebase.ServerValue.TIMESTAMP and i've tried them but they don't work either and I think they're outdated.
My other firebase services are working just fine (Authorization, Database & Storage) so I can't understand why am I getting this error.
I am trying o achive that to create a simple timestamp the I will later, after pulled from the server, would convert to a nicer format with PrettyTime.
This is the part of the code the is giving me the error:
class NewQuestionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_question)
val questionTitle : EditText = findViewById(R.id.new_question_title)
val questionDetails = findViewById<EditText>(R.id.new_question_details)
val questionTags = findViewById<EditText>(R.id.new_question_tags)
val questionButton = findViewById<Button>(R.id.new_question_btn)
questionButton.setOnClickListener {
postQuestion(questionTitle.text.toString(), questionDetails.text.toString(), questionTags.text.toString(), firebase.database.ServerValue.TIMESTAMP)
}
}
private fun postQuestion(title : String, details : String, tags : String, timestamp : String) {
val uid = FirebaseAuth.getInstance().uid
val ref = FirebaseDatabase.getInstance().getReference("/questions").push()
val newQuestion = Question(title, details, tags, timestamp)
ref.setValue(newQuestion)
.addOnSuccessListener {
Log.d("postQuestionActivity", "Saved question to Firebase Database")
}.addOnFailureListener {
Log.d("postQuestionActivity", "Failed to save question to database")
}
}
}
It is very odd a class start with lower case. I guess the most suitable approach is to allow the IDE to make the import for you, try with
ServerValue.TIMESTAMP