Could someone give me a little pointer on data classes. I'm finding a very steep learning curve with Kotlin, but I'm getting there slowly.
I have a data class of:
data class newGame(
val gamename: String,
val gamedate: String,
val players: List<Player>
) {
data class Player(
val player: String,
val player_id: Int,
val score: Int,
val points: Int
)
}
I can create an instance of the outer (newGame) class, but I'm struggling to get to grips with how I add Players. I thought I could do something like:
var gm: newGame
gm.gamename = GamesList.text.toString()
val p = newGame.Player
p.player = Player1.text.toString()
gm.players.add p
But Android Studio says I need a companion object and I'm not sure how that needs to look
The interface looks like:
interface CreateGame {
#POST("new")
fun addNewGame(
#Query("operation") operation: String,
#Body() newGame: newGame
): Call<gameEvent>
companion object {
fun create(): CreateGame {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BaseURL)
.build()
return retrofit.create(CreateGame::class.java)
}
}
I get how the companion object create() creates the instance of the game, but I can't figure out what I need to write so that I can add players to the inner class.
Could someone give me a pointer please?
I can create an instance of the outer (newGame) class, but I'm
struggling to get to grips with how I add Players. I thought I could
do something like:
Well, first understood the terms val and var. The property gamename is val which means its read-only, to write it you have to make it as var, same goes to all other properties. and players should be mutableListOf or ArrayList<> type so you can modify it later. List is immutable, which means you can't add after initialization.
data class newGame(
var gamename: String,
var gamedate: String,
var players: ArrayList<Player>
)
data class Player(
var player: String,
var player_id: Int,
var score: Int,
var points: Int
)
Now In your case, you need to add a secondary constructor OR initialize all properties while declaring as below
data class newGame(
var gamename: String? = null,
var gamedate: String? = null,
var players: ArrayList<Player> = ArrayList()
)
data class Player(
var player: String?=null,
var player_id: Int?=null,
var score: Int?=null,
var points: Int?=0
)
In main class
var gm: newGame = newGame()
gm.gamename = "GamesList.text.toString()"
val p = Player()
p.player = "Player1.text.toString()"
gm.players.add(p)
There is a difference between nested class and inner class.
A nested class cannot do the same thing as an inner class. An inner class has an implicit reference to its outer class instance. A nested class doesn't.
What you have is a nested class and I don't think your use case requires this structure. you can simply move the Player class outside the newGame class. and then you can create objects of newGame as
// Create Player objects by passing appropriate values to the constructor
val playerA = Player(player = "A", player_id = 1, score = 50, points = 50)
val playerB = Player(player = "B", player_id = 2, score = 60, points = 80)
// Create a newGame
val newGame = newGame(gamename = "MyGameName", gamedate = "GameDate", players = listOf(playerA, playerB))
Related
This seems like a lazy question, but the offical docs literally don't explain how to do this.
https://developer.android.com/training/data-storage/room
I have an entity like so:
#Entity
data class Shader(
#PrimaryKey(autoGenerate = true) val uid: Int,
val name: String,
val shaderMainText: String,
val paramsJson: String?
)
And some methods on my Dao:
#Insert
fun insertAll(vararg shaders: Shader)
#Delete
fun delete(shader: Shader)
#Update
fun update(shader: Shader)
How do I make a new record and insert it?
I've gotten so far as this:
val record = Shader(name="Foo", shaderMainText="bar", paramsJson=null)
But it complains that I'm missing the argument uid. Why do I need to provide a uid? It's supposed to be auto-generated?
An example of creating a new record would be appreciated.
You can set 0 for the default value of your uid field
#Entity
data class Shader(
#PrimaryKey(autoGenerate = true) val uid: Int = 0,
val name: String,
val shaderMainText: String,
val paramsJson: String?
)
When autoGenerate is true, Room ignores the default value, so it creates auto-incremented values.
Finally, this doesn't give you a compilation error:
val record = Shader(name="Foo", shaderMainText="bar", paramsJson=null)
The docs say:
If the field type is long or int (or its TypeConverter converts it to a long or int), Insert methods treat 0 as not-set while inserting the item.
If the field's type is Integer or Long (or its TypeConverter converts it to an Integer or a Long), Insert methods treat null as not-set while inserting the item.
So I would try setting the uid to be initialized to 0 or make it nullable and set to null.
Hope that helps!
https://developer.android.com/reference/android/arch/persistence/room/PrimaryKey#autoGenerate()
You Can Also try like this
The best practice is you always create an instance Class
1st Create a Database instance class
#Database(entities = [Shader::class], version = 1)
abstract class DatabaseHelper : RoomDatabase() {
companion object {
private var instance: DatabaseHelper? = null
fun getInstance(context: Context?): DatabaseHelper? {
if (instance == null) {
synchronized(DatabaseHelper::class) {
if (context != null) {
instance = Room.databaseBuilder(context.applicationContext, DatabaseHelper::class.java, "database.db").allowMainThreadQueries().build()
}
}
}
return instance
}
}
abstract fun getDao(): DBDao?
}
Create Your Shader data class like below
#Entity(tableName = "shader")
data class Shader(
#PrimaryKey(autoGenerate = true) var id: Int?,
val name: String,
val shaderMainText: String,
val paramsJson: String?
) {
constructor(name: String, shaderMainText: String, paramsJson: String?) : this(
null,
name,
shaderMainText,
paramsJson
)
}
Here you want to create constructor
Add data in Shader and insertAll Like Below
val record = Shader(name="Foo", shaderMainText="bar", paramsJson=null)
DatabaseHelper.getInstance(this)?.getDao()?.insertAll(record)
And Your Data added is successfully
There should be always a #PrimaryKey(autoGenerate = false) while inserting data in the DB
I try to make the project based on clean architecture so that each layer could have its own data model.
I have a database that contains 2 Entities: "movie" and "details".
#Entity(tableName = "movie")
data class MovieDbModel(
var page: Int,
#PrimaryKey(autoGenerate = false)
var id: Int,
var poster_path: String,
var overview: String,
var original_title: String)
#Entity(tableName = "details")
data class DetailsDbModel (
#PrimaryKey(autoGenerate = false)
val id: Int,
val genres: Genres,
val runtime: Int,
)
I want to map those 2 entities to 1 UI model "MovieAndDetailsUi " and convert it to MutableList that I will use for liveData.
data class MovieAndDetailsUi (
val page: Int,
val id: Int,
val poster_path: String,
val overview: String,
val original_title: String,
val genres: List<GenreUi>,
val runtime: Int,
)
But I have problems with that. Could you please show me the best way to do that? Thank you.
If you want to map those two you could create a companion object on the MoviesAndDetailsUi class that takes both entities and combines them, for example
data class MovieAndDetailsUi (
val page: Int,
val id: Int,
val poster_path: String,
val overview: String,
val original_title: String,
val genres: List<GenreUi>,
val runtime: Int,
) {
companion object {
fun fromDatabaseEntities(movie: MovieDbModel, details: DetailsDbModel) {
return MovieAndDetailsUI(
page = movie.page,
id = movie.id,
poster_path = movie.poster_path,
overview = movie.overview,
original_title = movie.original_title,
genres = details.genres,
runtime = details.runtime
)
}
}
}
That way on your view model you could just get the data from the DB and map it to the UI class, for example:
fun getMovies(): MutableList<MovieAndDetailsUi>() =
viewModelScope.launch() {
withContext(Dispatches.IO) {
val movies: List<MovieDbModel> = ... // Access the DB and get the movies
val details: List<DetailsDbModel = ... // Access the DB and get the details
val zipData = movies.zip(details) {movie, detail -> Pair(movie, detail)}
val mappedData = zipData.map { pair -> MovieAndDetailsUi.fromDatabaseEntities(pair.first, pair.second) }.toMutableList()
liveData.postValue(mappedData)
}
}
I also agree that in this case you should go with the flat structure on the entities as you can skip the mapping and also avoid having multiple classes with the same attributes basically, but in case you want to go this way here's how. Also, I don't know how you are associating the details and movie table and how you query the tables, you might have a problem there too as there's no foreing key on movies referencing to details or vice versa. I added the zip function just for the example but you should have a proper way to associate the movies and the details objects
I'm new to Room and haven't find any posts related to this.
I have two classes: BaseModel and ChildModel. ChildModel is a Room entity class, but BaseModel - isn't. How do I extend BaseModel from ChildModel properly? I tried to do it as in the code bellow...
BaseModel.kt:
#Parcelize
open class BaseModel(
open val id: String,
open var name: String,
open var creator: String,
open var dateOfLastEdit: Timestamp,
open var contributors: List<String>,
open var color: Int,
open var offline: Boolean
) : Parcelable {...}
ChildModel.kt (empty constructor used for firebase):
#Entity
#Parcelize
data class ChildModel(
#PrimaryKey(autoGenerate = false)
override val id: String = "",
override var name: String = "",
override var creator: String = "",
#TypeConverters(DateTypeConverter::class)
override var dateOfLastEdit: Timestamp = currentTime(),
#TypeConverters(ListStringConverter::class)
override var contributors: List<String> = emptyList(),
override var color: Int = NO_COLOR,
override var offline: Boolean = false,
var text: String = "",
var lastEditTextPosition: Int = 0,
var lastEditNamePosition: Int = 0,
var lastUserEdited: String = ""
) : Parcelable, BaseModel(id, name, creator, dateOfLastEdit, contributors, color, offline) {
constructor() : this("", "", "", currentTime(), emptyList(), NO_COLOR, false, "", 0, 0, "")
...
}
... but it gives error: "Field has non-unique column name" for all the fields which are overridden from BaseModel. Btw, when create BaseModel as an interface it builds successfully.
My database class:
#Database(entities = [ChildModel::class], version = 1)
#TypeConverters(DateTypeConverter::class, ListStringConverter::class)
abstract class CustomDatabase : RoomDatabase() {
abstract fun childModelDao(): ChildModelDao
companion object {
var INSTANCE: CustomDatabase? = null
fun getDatabase(context: Context): CustomDatabase? {
if (INSTANCE == null) {
synchronized(CustomDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
CustomDatabse::class.java,
DATABASE
).build()
}
}
return INSTANCE
}
fun destroyDatabase() {
INSTANCE = null
}
}
}
The error is not about upgrading database version, because I deleted the previous one.
Thanks in advance!
Ok, it seems that Room somehow creates two columns with same names (for parent class and for child class) I tried adding #Ignore annotation above every field inside BaseModel class. It fixed the issue, the project builds successfully, but I'm not sure that's the best approach.
In cases where an entity inherits fields from a parent entity, it's
usually easier to use the ignoredColumns property of the #Entity
attribute
Source:
https://developer.android.com/training/data-storage/room/defining-data
The example in the article shows an #Entity data class inheriting from an open class.
Based on #Anastasia Dronina's answer, it sounds like the reason to use the ignoredColumns property of the #Entity attribute is because Room will duplicate columns corresponding to all the superclass fields.
I have a Question. I have an App and store every data in the Class "Model" and I wanted to save it with serialization. This is the Model:
import android.os.Parcel
import android.os.Parcelable
import java.io.Serializable
private lateinit var att : IntArray
private lateinit var ess :String
private lateinit var skills : MutableList<IntArray>
private lateinit var attSkill : MutableList<IntArray>
class Model : Serializable {
init {
att = IntArray(9) { 1 }
ess = "6"
}
fun getAttributes() : IntArray{
return att
}
fun setAttributes(position : Int, value : Int){
att[position] = value
}
}
And I Save it with:
val fos = context.openFileOutput(filename, Context.MODE_PRIVATE)
val os = ObjectOutputStream(fos)
this.saveStuff() //[Just write Attributes to model here]
var newModel = controller.getModel()
os.writeObject(newModel)
and Load it with:
val fis = context.openFileInput(filename)
val input = ObjectInputStream(fis)
val model = input.readObject() as Model
this.loadStuff(model)
input.close()
Now I get this: Lets say I save values 1 2 3 4 with filename "test1" and after that I save values 4 3 2 1 in "test2". After that I load "test1" and get 4 3 2 1. In the matter of fact I just get the values my app has right in that moment of loading the data. Checked it in load function with Log.d().
After restarting the App if I load "test1" or "test2" I will get default values.
Am I missing something? Or is it just an example why people actually use Json for such things.
att, ess etc. are no members of Model, snd therefore are not serialized to disk. Pull them into the class definition to fix that:
class Model : Serializable {
private lateinit var att : IntArray
private lateinit var ess :String
private lateinit var skills : MutableList<IntArray>
private lateinit var attSkill : MutableList<IntArray>
init {
att = IntArray(9) { 1 }
ess = "6"
}
fun getAttributes() : IntArray{
return att
}
fun setAttributes(position : Int, value : Int){
att[position] = value
}
}
You will need to provide (de-)serialization logic for these attributes. Kotlinās #Parcelize annotation might be of interest here.
I cannot use the mutableListOf in the constructor. But I can use it in the
class body. Is this a known bug or just we cannot do it this way?
Here is my code:
class Model (val title: String, var listString: kotlin.collections.mutableListOf<String>, var selectedIndex: Int = 0) : FormFieldModel(FormFieldType.SELECT_BOX) {
var iad = mutableListOf<String>()
}
And I get Unresolved reference: mutableListOf in the constructor.
mutableListOf is a function. What you need is a type. So you should use MutableList instead.
var listString: MutableList<String>
Or, if you want to provide a default value to listString, you need to write it like this:
var listString: MutableList<String> = mutableListOf()
The function mutableListOf is a library function, being part of standard kotlin library.
fun <T> mutableListOf(vararg elements: T): MutableList<T> (source)
It gives you a MutableList<T> which is the return type and also has to be the parameter type of your constructor. To fix this, just make your listString a MutableList.
class Model (val title: String, var listString: MutableList<String>, var selectedIndex: Int = 0) : FormFieldModel(FormFieldType.SELECT_BOX) {
var iad = mutableListOf<String>()
}