I want to implement kind of a proxy for some boolean values in my app. The logic would be as follows:
I receive a set of values from my back-end
I have some of these values set in Firebase as well
When using a value in the app, I first check if it exists in Firebase
3.1. If it exists, take the Firebase value
3.2. If it does not exist, take the backend value
The question is - how can I check if the value exists in Firebase Remote Config?
I found another way, maybe useful for someone else landing here:
val rawValue = remoteConfig.getValue(key)
val exists = rawValue.source == FirebaseRemoteConfig.VALUE_SOURCE_REMOTE
In this case exists will be true only if the value is returned from remote (and and has not been set as default or static provided value). The accepted answer is error prone as does not consider the case where the empty String is a valid String returned from remote
Here the docs for FirebaseRemoteConfigValue
I have found the solution:
Firebase Remote Config fetches ALL values as Strings and only then maps them to other types in convenience methods such as getBoolean(), getLong() etc.
Therefore, a boolean config value existence can be checked as follows:
String value = firebaseRemoteConfig.getString("someKey");
if(value.equals("true")){
//The value exists and the value is true
} else if(value.equals("false")) {
//The value exists and the value is false
} else if(value.equals("")) {
//The value is not set in Firebase
}
Same goes for other types, i.e. a long value set to 64 on firebase will be returned from getString() as "64".
Firebase Remote Config (FRC), actually provides 3 constants to know from where is the value retrieved with getValue(forKey) (documentation)
VALUE_SOURCE_DEFAULT --> value from default set by user
VALUE_SOURCE_REMOTE --> value from remote server
VALUE_SOURCE_STATIC --> value returned is default (FRC don't have the key)
Knowing this, you can do a "wrapper" like this:
class FirebaseRemoteConfigManager {
.....
override fun getBoolean(forKey: String): Boolean? = getRawValue(forKey)?.asBoolean()
override fun getString(forKey: String): String? = getRawValue(forKey)?.asString()
override fun getDouble(forKey: String): Double? = getRawValue(forKey)?.asDouble()
override fun getLong(forKey: String): Long? = getRawValue(forKey)?.asLong()
private fun getRawValue(forKey: String): FirebaseRemoteConfigValue? {
val rawValue = remoteConfig.getValue(forKey)
return if (rawValue.source == FirebaseRemoteConfig.VALUE_SOURCE_STATIC) null else rawValue
}
...
}
If you get a null, you know the key don't exist in FRC.
Take care of using default values as "not exist", because maybe in your FRC you want to set this value, and you will have a false positive
Remote Config already does this, as described in the documentation. You're obliged to provide default values for parameters that haven't been defined in the console. They work exactly as you describe, without having to do any extra work. These defaults will be used until you perform a fetch. If the value is defined in the console, then it will be used instead of the default.
Related
ok so I have an android app and have followed androids room with a view tutorial. I have managed to get it working as expected with my recyclerview to show a history of all games played. I am now working on an achievements style page and want to check for specific scores achieved.
In my DAO file I have the following;
#Query("SELECT COUNT(*) from SavedScores WHERE difficulty = :Difficulty AND questioncount = :QuestionCount AND answeredcorrectly =:QuestionCount")
fun CheckRecordsForTrophy(Difficulty: String,QuestionCount:Int):Flow<Int>
Then in my room repository I have this;
val easy5: Flow<Int> = savedScoresDao.CheckRecordsForTrophy("Easy",5)
In my view model;
val easy5: LiveData<Int> = repository.easy5.asLiveData()
and then in an activity I have the following;
Before the oncreate method:
private val savedScoresViewModel: SavedScoresViewModel by viewModels {
SavedScoresViewModelFactory((application as ScoreApplication).repository)
}
Within the oncreate method:
var easy5var = savedScoresViewModel.easy5
savedScoresViewModel.easy5.observe(this) {
if(easy5var==0){}
}
I am not 100% sure if I should be following all these steps like I did to get all data into my recycler view but I have effectively followed the same steps with exception of adapters etc as I am simply trying to understand if they have met the criteria for a given achievement.
I have a hard coded elements in my repo at the moment for the function i.e CheckRecordsForTrophy("Easy",5) which I will figure how to set from the activity later
The issue I appear to be facing is with:
if(easy5var==0){}
The error I get is Operator '==' cannot be applied to 'LiveData' and 'Int'.
Goal: Check if within my score table is there a record where the score is equal to the number of questions asked, if so I will mark an achievement as complete. I have read that using count* in the query returns the number of records found so I can use that to work out if they should get the achievement or not. In other words, if no records, no achievement.
You are comparing an Int to a LiveData, when you probably wanted to compare the int to the emitted value of the live data.
var easy5var = savedScoresViewModel.easy5
savedScoresViewModel.easy5.observe(this) { newValue -> // this is what you've missed
// if(easy5var==0) {} <-- you've made the wrong equality check here
if (newValue == 0) {} // <-- probably this is what you've meant to do.
}
this function below
in goes the dictWord
if it exists it increases the amount property by one updates in the db and returns out of the uiscope - out of the function
and when the getDictWord return a null it goes to creation a newDictWord and adds that into the db and leaves the coroutine scope and then the function
no?
because i do get " android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: DictWord.name (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)"
from time to time from that
fun addOrIncreaseDictWordAmount(dictWord: String) {
uiScope.launch {
userDatabaseDao.getDictWord(dictWord)?.let {
it.amount += 1
userDatabaseDao.updateDictWord(it)
return#launch
}
// adds the strain to the AddNew dropDown list
val newDictWord = DictWord(name = dictWord)
userDatabaseDao.insertDictWord(newDictWord)
}
In regards to your initial question regarding the ?.let syntax...
This syntax will work with a nullable value (in your case the return of getDictWord(dictWord).
If that value is not null you will be passed into the attached block, and the value will be passed into the block as it, and guaranteed not-null for the rest of the closure.
If the value is null, it will skip that closure completely.
As for the intermittent error you are receiving, and based on your provided information, I am thinking your solution to that would be involved with the answer given here: UNIQUE constraint failed
If you are able to provide more involved logs and information I would be happy to edit and improve this answer to get your issue fixed.
I'm querying Realm using the snippet below where I'm providing hardcoded fieldName which I believe is quite error-prone. I've checked realm documentation (to no avail) but in the official example (https://github.com/realm/realm-java/blob/master/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/entity/NYTimesStory.java), they're using static fields to access from queries. Which, again, I think is quite "dangerous" considering future field name changes and etc. So, what I wonder is whether there is a solution that I'm missing out?
Real example:
override fun getSection(section: Section): RealmResults<Article> {
return realm.where(Article::class.java)
.equalTo("section",
,section.section).findAllAsync()
}
What I'm trying to achieve:
override fun getSection(section: Section): RealmResults<Article> {
return realm.where(Article::class.java)
.equalTo(Article.SECTION, // or ArticleX.SECTION, X being name extension
,section.section).findAllAsync()
}
I think the is a semantic issue in Room while working in Kotlin.
Simple DAO query in Room can be obtained by
#Query("SELECT * FROM Users WHERE id = :id")
fun getUser(id: Int): User
Although, I defined return type as User, not User?, this query can still returns null, when there is no User with the given id.
So whenever you call this function, you definitely need to check for null return, as below
val user = userDao.getUser("someid")
if (user != null){
return user
}
else {
return DEFAULT_USER
}
But since we defined return type of getUser as User, #kotlin compiler suggest that null check is redundant.
Am I missing something? Can anyone give some feedback on this?
There is no semantic issue. If Your function can return a null, it should be defined in the function return type. So, your function signature should be like this
fun getUser(id: Int): User?
This means the function can return null and now your null checks should work fine.
Apparently it's already tracked issue in Google Issue Tracker
And as a respond they developers say
Status: Won't Fix (Intended Behavior)
this is not about generated code
though, you are the person who creates that Query method so you should
declare it as nullable.
So, if we know that our code can return null, we should declare it as Nullable as
#Query("SELECT * FROM Users WHERE id = :id")
fun getUser(id: Int): User?
I'm using Kotlin objects to work with my Firebase Database models, as described in the guide. I have many fields that are stored as strings, but really are enums, so to be type-safe I have enum fields in the models, plus a string delegated property that returns the firebase stored value (as suggested in a question I asked some time ago). Now, these fields work if I get/set the string delegate in code, but firebase libs seem to skip them when converting to/from database's json format.
A simple example:
abstract class BaseModel {
#Exclude
open var path: String? = null // fails even if I delete this field!
}
class Weight() : BaseModel() {
constructor(v: Double, u: WeightUnit) : this() {
value = v
unitEnum = u
}
var value: Double = 0.0
#Exclude
var unitEnum: WeightUnit = WeightUnit.KG
var unit: String by EnumStringLowercaseConverter(WeightUnit::class.java).getDelegate(Weight::unitEnum)
}
[...]
val testWeight = Weight(7.0, "kg")
db.getReference("/valid/path/to/save/testWeight").setValue(testWeight)
.addOnSuccessListener { r -> Log.d(LOG_TAG, "set successful") }
.addOnFailureListener { e -> Log.e(LOG_TAG, "set error", e) }
The setValue always gives a Permission Denied error, but works, if I delete unitEnum field and make unit a normal String property.
It's similar for reading: Firebase gives no errors when getting a Weight object, but the weightUnit field is never set to anything else than the default. But, if I manually do weight.unit = "lb", the unitEnum field properly returns WeightUnit.LB.
I'm using firebase libs v10.0.1
Now, the questions:
What can I do to make the delegated properties work correctly with firebase? I can try a different approach to the delegated enum fields, as long as the points from my original question are satisfied (readable, concise and type-safe code).
is there any way to see how exactly do firebase libs convert objects to/from json? Or at least see the converted json? Maybe then I could tweak things myself. Unfortunately, everything firebase-related shows as /* compiled code */ in AndroidStudio.
UPDATE: I could of course add a toMap() method to each model, where I would construct a map containing all the properties needed in firebase, but it would be tiresome to do this for every model, and it solves the saving issue only, the enum fields still wouldn't be set when getting.
The delegated props are also skipped when serializing with GSON. So maybe is there a generic way to make the delegated properties look like regular fields?
Try this code, it should work.
#get:Exclude #set:Exclude
var unitEnum: WeightUnit = WeightUnit.KG
var unit: String
get() = unitEnum.name
set(v) { unitEnum = WeightUnit.valueOf(v) }