error: Cannot find getter for field.
private final com.kbb.webviewolacakmi.model.content icerik = null;
I didn't manage to add the subparts of the json to the room.
Thanks to everyone who helped.
I would be very happy if you could write a clear code example.
Json File :
{
"date": "xxx",
"title": {
"rendered": "Title"
},
"content": {
"rendered": "content",
"protected": false
},
}
Data Class :
#Entity
data class Icerik(
#ColumnInfo(name="title")
#SerializedName("title")
val baslik:title?,
#ColumnInfo(name="content")
#SerializedName("content")
public val icerik:content?,
#ColumnInfo(name="date")
#SerializedName("date")
val tarih:String?,
#ColumnInfo(name="jetpack_featured_media_url")
#SerializedName("jetpack_featured_media_url")
val gorsel:String?,) {
#PrimaryKey(autoGenerate = true)
var uuid:Int=0
fun getIcerik(){
}
}
data class content(
#ColumnInfo(name="rendered")
#SerializedName("rendered")
public val content: String?,
#ColumnInfo(name="protected")
#SerializedName("protected")
val bool: Boolean?,
){
#PrimaryKey(autoGenerate = true)
var uuid:Int=0
}
data class title(
#ColumnInfo(name="rendered")
#SerializedName("rendered")
val ytitle:String?
){
#PrimaryKey(autoGenerate = true)
var uuid:Int=0
}
IcerikDatabase Class
#TypeConverters(value = [RoomTypeConverters::class])
#Database(entities = arrayOf(Icerik::class), version = 1)
abstract class IcerikDatabase:RoomDatabase() {
abstract fun icerikDao(): IcerikDAO
companion object {
#Volatile private var instance:IcerikDatabase? = null
private val lock=Any()
operator fun invoke(context: Context)= instance?: synchronized(lock){
instance?: databaseOlustur(context).also {
instance=it
}
}
private fun databaseOlustur(context: Context) = Room.databaseBuilder(
context.applicationContext, IcerikDatabase::class.java,
"icerikdatabase"
).build()
}
}
IcerikDao
interface IcerikDAO {
#Insert
suspend fun instertAll(vararg icerik:Icerik):List<Long>
#Query("SELECT * FROM icerik")
suspend fun getAllIcerik():List<Icerik>
#Query("SELECT * FROM icerik WHERE uuid=:icerikId ")
suspend fun getIcerik(icerikId:Int):Icerik
#Query("DELETE FROM icerik")
suspend fun deleteAllIcerik()
}
TypeConverter
class RoomTypeConverters {
#TypeConverter
fun fromTitleToJSONString(title: title?): String? {
return Gson().toJson(title)
}
#TypeConverter
fun toTitleFromJSONString(jsonString: String?): title? {
return Gson().fromJson(jsonString, title::class.java)
}
#TypeConverter
fun fromIcerikToJSONString(content: content?): String? {
return Gson().toJson(content)
}
#TypeConverter
fun toIcrerikFromJSONString(jsonString: String?): content? {
return Gson().fromJson(jsonString, content::class.java)
}
}
I believe that your issue is in regard, not to room, but with the JSON handling.
The JSON file that you have shown cannot directly build an Icerik object.
Rather you need to have an intermediate class that can be built with the JSON and then use that intermediate class to then build the IceRik object.
So you want an intermediate class something along the lines of:-
data class JsonIceRik(
val content: content,
val title: title,
val date: String
)
If the JSON is then amended to be:-
val myjson = "{\"date\": \"xxx\",\"title\": {\"rendered\": \"Title\"},\"content\": {\"rendered\": \"content\",\"protected\": false}}"
note the omission of the comma between the two closing braces
Then you could use:-
val m5 = Gson().fromJson(myjson,JsonIceRik::class.java)
To build the intermediate JsonIceRik object.
And then you could use:-
val i5 = Icerik(baslik = m5.title, icerik = m5.content, tarih = m5.date,gorsel = "whatever")
To build the Icerik from the intermediate JsonIceRik.
The result in the database would be:-
uuid in the title and content serve no purpose and will always be 0 if obtaining the data from JSON
the #PrimaryKey annotation only serves to introduce warnings -
A Table can only have 1 Primary Key ( a composite Primary Key can include multiple columns though (but you cannot use the #PrimaryKey annotation, you have to instead use the primarykeys parameter of the #Entity annotation) )
You might as well have :-
data class content(
#ColumnInfo(name="rendered")
#SerializedName("rendered")
val content: String?,
#ColumnInfo(name="protected")
#SerializedName("protected")
val bool: Boolean?,
)
data class title(
#ColumnInfo(name="rendered")
#SerializedName("rendered")
val ytitle:String?
)
Otherwise, as you can see, the data according to the JSON has been correctly stored.
Related
Image for reference
I am facing a situation whenever I add a new article into the database it will add the new article to the bottom of the RecyclerView, how can I implement it in such a way that it updates the top row instead?
Is it something got to do with the Adapter or do I have to tweak the Doa?
Room Database Implementation
**//ViewModel**
fun saveArticle(article: Article) = viewModelScope.launch {
newsRepository.upsert(article)
}
**
//NewsRepository**
suspend fun upsert(article: Article) = db.getArticleDao().upsert(article)
**//Implementation of the Database**
#Database(
entities = [Article::class],
version = 1
)
#TypeConverters(Converters::class)
abstract class ArticleDatabase : RoomDatabase() {
abstract fun getArticleDao(): ArticleDao
companion object {
#Volatile
private var instance: ArticleDatabase? = null
private val LOCK = Any()
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: createDatabase(context).also { instance = it }
}
private fun createDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
ArticleDatabase::class.java,
"article_db.db"
).build()
}
}
**//DOA**
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(article: Article): Long //returns the id's of the article
**//Entity**
#Entity(
tableName = "articles", indices = [Index(value = ["url","title"], unique = true)]
)
#Parcelize
data class Article(
#PrimaryKey(autoGenerate = true)
var id: Int? =null,
val author: String?,
val description: String?,
val source: Source?,
val title: String?,
val url: String?,
val urlToImage: String?,
val publishedAt: String?,
val content: String?
): Parcelable {
override fun hashCode(): Int {
var result = id.hashCode()
if(url.isNullOrEmpty()){
result = 31 * result + url.hashCode()
}
return result
}
}
Expecting the top row to be updated instead of the bottom row, the Room Database docs did not talk much about changing the way the article is inserted, tried to reference other people's work, couldn't find the solution
class MapConverters {
#TypeConverter
fun fromString(value: String): Map<Int, IntArray> {
val mapType = object : TypeToken<Map<Int, IntArray>>() {}.type
return Gson().fromJson(value, mapType)
}
#TypeConverter
fun fromMap(map: Map<Int, IntArray>): String {
val gson = Gson()
return gson.toJson(map)
}
}
#Entity
data class StartGame(
#PrimaryKey(autoGenerate = true) val gameId: Int?,
val gameName: String,
val scoreList: Map<Int, IntArray>
)
#Database(
entities = [StartGame::class, PersonList::class],
version = 1,
exportSchema = false
)
#TypeConverters(MapConverters::class){
...}
I got error
I want to put the Map type variable in Room.
I've tried many ways and tried to apply a typeConvertes.
But it didn't work well.
I'd like you to help me with this.
error: Not sure how to convert a Cursor to this method's return type (java.util.Map<java.lang.Integer, int[]>).
public abstract java.util.Map<java.lang.Integer, int[]> getGameScoreBoardById(int gameId);
#Dao
interface DataDao {
...
#Query("SELECT scoreList FROM StartGame WHERE gameId=:gameId")
#TypeConverters(MapConverters::class)
fun getGameScoreById(gameId: Int): Map<Int, IntArray>
D:\myapp\app_boardgame2\app\build\tmp\kapt3\stubs\debug\com\wonddak\boardmaster\room\DataDao.java:20: error: Not sure how to convert a Cursor to this method's return type (java.util.Map<java.lang.Integer, int[]>).
public abstract java.util.Map<java.lang.Integer, int[]> getGameScoreById(int gameId);
You need to specify which fields uses typeConverters in your model like -
#Entity
data class StartGame(
#PrimaryKey(autoGenerate = true) val gameId: Int?,
val gameName: String,
#TypeConverters(MapConverters::class)
val scoreList: Map<Int, IntArray>
)
I have POJO data classes from API.
#Entity(tableName = "pokemon")
class Pokemon(
#PrimaryKey(autoGenerate = true)
var id: Int? = null,
#SerializedName("count")
#Expose
val count: Int?,
#SerializedName("next")
#Expose
val next: String?,
#SerializedName("results")
#Expose
val results: List<Result>? = null
)
And class List:
class Result(
#SerializedName("name")
#Expose
val name: String?,
#SerializedName("url")
#Expose
val url: String?
)
My database class:
#Database(entities = [Pokemon::class], version = 1, exportSchema = false)
#TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
companion object {
private var db: AppDatabase? = null
private const val DB_NAME = "main.db"
private val LOCK = Any()
fun getInstance(context: Context): AppDatabase {
synchronized(LOCK) {
db?.let { return it }
val instance = Room.databaseBuilder(
context,
AppDatabase::class.java,
DB_NAME
)
.fallbackToDestructiveMigration()
.build()
db = instance
return instance
}
}
}
abstract fun PokemonDao(): PokemonDao
}
My class Converters:
class Converters {
#TypeConverters
fun fromResultToString(result: slode.elsloude.pokemonapi.pojo.Result?): String? {
return Gson().toJson(result)
}
#TypeConverters
fun fromStringToResult(value: String?): slode.elsloude.pokemonapi.pojo.Result? {
return Gson().fromJson(value, Result::class.java)
}
}
The error I get when attempting this is:
error: Class is referenced as a converter but it does not have any converter methods. - androidx.databinding.adapters.ConvertersC:\Users\elslode\.AndroidStudio3.6\PokemonApi\app\build\tmp\kapt3\stubs\debug\slode\elsloude\pokemonapi\pojo\Pokemon.java:22: error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private final java.util.List<slode.elsloude.pokemonapi.pojo.Result> results = null;
^C:\Users\elslode\.AndroidStudio3.6\PokemonApi\app\build\tmp\kapt3\stubs\debug\slode\elsloude\pokemonapi\pojo\SinglePokemon.java:14: error: Cannot figure out how to read this field from a cursor.
The #TypeConverters annotation is used to link your converters with the database, as yuo have done here:
#TypeConverters(Converters::class)
However when declaring a converter method you should be using the #TypeConverter annotation:
class Converters {
#TypeConverter
fun fromResultToString(result: slode.elsloude.pokemonapi.pojo.Result?): String? {
return Gson().toJson(result)
}
#TypeConverter
fun fromStringToResult(value: String?): slode.elsloude.pokemonapi.pojo.Result? {
return Gson().fromJson(value, Result::class.java)
}
}
Note the plural on #TypeConverters vs #TypeConverter.
Secondly Room cannot automatically infer a list conversion for you, so the type converter functions will have to map the full List<Result> to a String and vice versa.
I have one entity "drinks" which have [id;name;thumb] and I`m using these entities for 2 response calls. One response returns me a NonAlcohol list of drinks, another AlcoholList, I'm using Room for caching the data. But when I run the app, I saw that my lists merged, after some thought, I found a solution to this problem, I added one Boolean field to my entity "alcoholStatus".But I can't understand how to set the data into this variable correctly using this AccessDataStrategy. I'm new to Android, and this is my learning project. Please give me the right way how to solve this problem.
https://github.com/YaroslavSulyma/LetsDrink/tree/master/app/src/main/java/com/example/letsdrink
Thanks a lot!
Entity
#Entity(tableName = "drinks")
data class DrinksModel(
#SerializedName("strDrink")
val strDrink: String,
#SerializedName("strDrinkThumb")
val strDrinkThumb: String?,
#SerializedName("idDrink")
#PrimaryKey
val idDrink: Int,
var alcohol: Boolean
)
DataAccessStrategyCode
fun <T, A> performGetOperation(
databaseQuery: () -> LiveData<T>,
networkCall: suspend () -> Resource<A>,
saveCallResult: suspend (A) -> Unit
): LiveData<Resource<T>> =
liveData(Dispatchers.IO) {
emit(Resource.loading())
val source = databaseQuery.invoke().map { Resource.success(it) }
emitSource(source)
val responseStatus = networkCall.invoke()
if (responseStatus.status == SUCCESS) {
saveCallResult(responseStatus.data!!)
} else if (responseStatus.status == ERROR) {
emit(Resource.error(responseStatus.message!!))
emitSource(source)
}
}
Resource
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
enum class Status {
SUCCESS,
ERROR,
LOADING
}
companion object {
fun <T> success(data: T): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(message: String, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, message)
}
fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
Repository
class CocktailsRepository #Inject constructor(
private val remoteDataSource: CocktailsRemoteDataSource,
private val localDataSource: CocktailsDao
) {
fun getAlcoholicCocktails() = performGetOperation(
databaseQuery = { localDataSource.getAlcoholicCocktails() },
networkCall = { remoteDataSource.getAllAlcoholicCocktails()},
saveCallResult = { localDataSource.insertAllDrinks(it.drinks) }
)
fun getNonAlcoholicCocktails() = performGetOperation(
databaseQuery = { localDataSource.getNonAlcoholicCocktails() },
networkCall = { remoteDataSource.getAllNonAlcoholicCocktails() },
saveCallResult = { localDataSource.insertAllDrinks(it.drinks) }
)
}
DAO
#Dao
interface CocktailsDao {
#Query("SELECT * FROM drinks WHERE alcohol = 'true'")
fun getAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Query("SELECT * FROM drinks WHERE alcohol = 'false'")
fun getNonAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllDrinks(drinks: List<DrinksModel>)
}
RemoteDataSource
class CocktailsRemoteDataSource #Inject constructor(private val iCocktailApisService: ICocktailApisService) :
BaseDataSource() {
suspend fun getAllAlcoholicCocktails() =
getResult { iCocktailApisService.allAlcoholicAndNonAlcoholicCocktails("Alcoholic") }
suspend fun getAllNonAlcoholicCocktails() =
getResult { iCocktailApisService.allAlcoholicAndNonAlcoholicCocktails("Non_Alcoholic") }
}
First: I strongly recommend that you define separate data classes for your remote and local model classes and do the mapping between them when needed, for example:
Remote data model:
data class DrinkRemoteModel(
#SerializedName("idDrink")
val idDrink: Int,
#SerializedName("strDrink")
val strDrink: String,
#SerializedName("strDrinkThumb")
val strDrinkThumb: String?,
#SerializedName("alcohol")
var alcohol: Boolean
)
Local data model:
#Entity(tableName = "drinks")
data class DrinkLocalModel(
#PrimaryKey
#ColumnInfo(name = "idDrink")
val idDrink: Int,
#ColumnInfo(name = "strDrink")
val strDrink: String,
#ColumnInfo(name = "strDrinkThumb")
val strDrinkThumb: String?,
#ColumnInfo(name = "alcohol")
var alcohol: Boolean
)
Back to your implementation: I think what causing the problem is that Room maps Boolean fields in your entity to an integer column, 1 for true, and 0 for false, so try changing your querys in your DAO like following:
#Dao
interface CocktailsDao {
#Query("SELECT * FROM drinks WHERE alcohol = 1")
fun getAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Query("SELECT * FROM drinks WHERE alcohol = 0")
fun getNonAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllDrinks(drinks: List<DrinksModel>)
}
Alternatively: you can substitute your getAlcoholicCocktails and getNonAlcoholicCocktails with one DAO function, like this:
#Query("SELECT * FROM drinks WHERE alcohol = :isAlcoholic")
fun getCocktails(isAlcoholic : Boolean = true): LiveData<List<DrinksModel>>
I'm doing weather app in Kotlin, and in Activity is method which firstly takes cache data from Room, and after one hour data is updated. But there is problem probably with saving of data in database. I checked API logs in Profiles and there is no null with Weather List, so API works fine.
I'm trying to save Weather list as an ArrayList, but the answer from logs is still null. I also tried Type Converter, but still nothing. Maybe someone will find the reason for my problem and the answer.
EDIT: I removed #Embedded(prefix = "weather_") above the ArrayList and it works.
CurrentWeather (stores Weather ArrayList):
#Entity(tableName = "current_weather")
data class CurrentWeather(
#Embedded(prefix = "weather_")
val weather: ArrayList<Weather>? = ArrayList(), //here is my problem
#SerializedName("base")
val base: String,
#Embedded(prefix = "clouds_")
val clouds: Clouds,
#SerializedName("cod")
val cod: Int,
#Embedded(prefix = "coord_")
val coord: Coord,
#SerializedName("dt")
val dt: Int,
#SerializedName("id")
val id: Int,
#Embedded(prefix = "main_")
val main: Main,
#SerializedName("name")
val name: String,
#Embedded(prefix = "sys_")
val sys: Sys,
#SerializedName("visibility")
val visibility: Int,
#Embedded(prefix = "wind_")
val wind: Wind
) {
#PrimaryKey(autoGenerate = false)
var idKey: Int = CURRENT_WEATHER_ID
}
Weather:
data class Weather(
#SerializedName("description")
val description: String,
#SerializedName("icon")
val icon: String,
#SerializedName("id")
val id: Int,
#SerializedName("main")
val main: String
)
Converter:
class Converters {
#TypeConverter
fun arrayListToJson(value: List<Weather>?): String {
return Gson().toJson(value)
}
#TypeConverter
fun jsonToArrayList(value: String): List<Weather> {
val objects = Gson().fromJson(value, Array<Weather>::class.java) as Array<Weather>
val list = objects.toList()
return list
}
Database:
#Database(entities = [CurrentWeather::class, Location::class], version = 15, exportSchema = false)
#TypeConverters(Converters::class) //converter initialization
abstract class WeatherDatabase : RoomDatabase() {
Here is the modified converter class. It might help you.
object class Converters {
val gson = Gson()
#TypeConverter
fun arrayListToJson(list: List<Weather>?): String? {
return if(list == null) null else gson.toJson(list)
}
#TypeConverter
fun jsonToArrayList(jsonData: String?): List<Weather>? {
return if (jsonData == null) null else gson.fromJson(jsonData, object : TypeToken<List<Weather>?>() {}.type)
}
}