How to clear/ set a value to null/undefined in parse - android

I have 3 columns in my Parse class, FOOD1, FOOD2 and FOOD3.
it is optional for the user if they choose 0,1,2 or 3 Foods they like, i save the foods in these columns. This process will be repeated.
For every loop i want to set all these fields on null/undefined so i can simply see if they choose something after selection.
But when one of the fields eg. FOOD1 is filled in the database, i cant clear the value of it.
i tried:
final ParseQuery queryusername = ParseQuery.getQuery("Usernames");
queryusername.whereContains("names",db.getUsername());
try {
ParseObject p = queryusername.getFirst();
System.out.println("parseobject:"+p);
//make fields empty
p.put("Food1",null); //Error
p.put("Food2",null);
p.put("Food3",null);
}catch(){}
the error is:
java.lang.IllegalArgumentException: value may not be null.
how can i clear the field?

Parse object provides remove(). Pass it the key to remove, e.g.
p.remove("Food1");
p.remove("Food2");
p.remove("Food3");

It seems that Parse SDK has checks for null.
From the source
/**
* Add a key-value pair to this object. It is recommended to name keys in
* <code>camelCaseLikeThis</code>.
*
* #param key
* Keys must be alphanumerical plus underscore, and start with a letter.
* #param value
* Values may be numerical, {#link String}, {#link JSONObject}, {#link JSONArray},
* {#link JSONObject#NULL}, or other {#code ParseObject}s. value may not be {#code null}.
*/
Why do you need to set it to NULL? You can always use empty String or some constant integer or finally JSONObject.NULL.
Or use p.remove as in the answer by #danh

The answer of https://stackoverflow.com/users/294949/danh works. You must save the object:
p.remove("Food1");
p.remove("Food2");
p.remove("Food3");
try {
p.save();
}
catch (ParseException e) {
}

Related

Type mismatch: inferred type is kotlin.collections.ArrayList<LiveData<List<a>>> but Collection<LiveData<List<TypeVariable(Source)>?>> was expected

I tried to use flattenLiveData() but it is exspecting
kotlin.collections.ArrayList<LiveData<List<TabDefinition>>>
but I am passing in Collection<LiveData<List<TypeVariable(Source)>?>>
Type mismatch: inferred type is
kotlin.collections.ArrayList<LiveData<List<TabDefinition>>> /* =
java.util.ArrayList<LiveData<List<TabDefinition>>> */ but
Collection<LiveData<List<TypeVariable(Source)>?>> was expected
flattenLiveData() is an inbuilt function.
/**
* A helper function to convert a Collection<LiveData<List<Source>>> into a single
LiveData<List<Source>>.
*
* Ex. We have a list of TabProviders, each provider outputs a live data containing a
list of all of it's available tabs
* we want to merge all of those into a single live data containing a single list.
*
* #param Source The inner data type
* #param input A Collection of LiveData containing a List of source data
* #return A LiveData containing a List of all source data from every element in
[input]
*/
#JvmOverloads
fun <Source> flattenLiveData(input: Collection<LiveData<List<Source>?>>,
waitForAllToBeNonNull: Boolean = false): LiveData<List<Source>> =
CollectionMergerLiveData<List<Source>, List<Source>>(input, waitForAllToBeNonNull)
{
it.flatten()
}
You get this error because of generic types mismatch. Your original generic type List<TabDefinition> is not nullable:
val array: kotlin.collections.ArrayList<LiveData<List<TabDefinition>>> = getArray()
flattenLiveData(array) // Error "Type mismatch"
but flattenLiveData requires it to be nullable: List<Source>?. So changing this generic type to nullable (applying nullable operator ?) will solve the problem:
val array: kotlin.collections.ArrayList<LiveData<List<TabDefinition>?>> = getArray()
flattenLiveData(array)

Supplement a Records Columns in Room

I am fairly new to Android Room and SQLite in general, so sorry if this is a simple question.
I am getting data from a API that I'd like to insert into a database so it's accessible when the device is offline.
Depending on the endpoint of the API, some fields of my Data objects may be null (Think a summary with just the basic fields versus a fully detailed object with all fields)
To keep the database clean, I'd like to update the entries, but only the columns that are not null (eg. that I have new values for) and keep the rest of the columns untouched.
Here are some example classes to clarify:
Person
#Entity(tableName = "person", indices = {
#Index(value = "id", unique = true)
})
public class Person {
#PrimaryKey
public int id;
public String name;
public String description;
}
Example:
// create db
RoomDB db = RoomDB.create(ctx);
// create some sample objects
final Person p2 = new Person(2, "Peter", null);
// insert them into the db
db.personDao().insert(p2);
// create a updated peter that likes spiders
// but has no name (as a example)
final Person newPeter = new Person(2, null, "Peter likes spiders");
// and update him
db.personDao().updateNonNull(newPeter);
// now we read him back
final Person peter = db.personDao().getById(2);
In this example, the desired values of 'peter' would be:
id = 2
name = "Peter"
description = "Peter likes spiders"
However, using Room's #Update or #Insert i can only get this:
id = 2
name = null
description = "Peter likes spiders"
The only way i found to achive this would be to manuall get the object and supplement the values like so:
#Transaction
public void updateNonNull(Person newPerson) {
final Person oldPerson = getById(newPerson.id);
if (oldPerson == null) {
insert(newPerson);
return;
}
if (newPerson.name == null)
newPerson.name = oldPerson.name;
if (newPerson.description == null)
newPerson.description = oldPerson.description;
update(newPerson);
}
However, that would result in quite a bit of code with bigger objects...
So my question, is there a better way to do this?
Edit:
After some Testing with the SQL by #Priyansh Kedia, i found that those functions indeed work as intended and do so at a higher performance than java.
However, as a SQL statement would have required me to write huge queries, i decided to use a Reflection based solution, as can be seen below.
I only did so because the function isn't called regularly, so the lower performance won't matter too much.
/**
* merge two objects fields using reflection.
* replaces null value fields in newObj with the value of that field in oldObj
* <p>
* assuming the following values:
* oldObj: {name: null, desc: "bar"}
* newObj: {name: "foo", desc: null}
* <p>
* results in the "sum" of both objects: {name: "foo", desc: "bar"}
*
* #param type the type of the two objects to merge
* #param oldObj the old object
* #param newObj the new object. after the function, this is the merged object
* #param <T> the type
* #implNote This function uses reflection, and thus is quite slow.
* The fastest way of doing this would be to use SQLs' ifnull or coalesce (about 35% faster), but that would involve manually writing a expression for EVERY field.
* That is a lot of extra code which i'm not willing to write...
* Besides, as long as this function isn't called too often, it doesn't really matter anyway
*/
public static <T> void merge(#NonNull Class<T> type, #NonNull T oldObj, #NonNull T newObj) {
// loop through each field that is accessible in the target type
for (Field f : type.getFields()) {
// get field modifiers
final int mod = f.getModifiers();
// check this field is not status and not final
if (!Modifier.isStatic(mod)
&& !Modifier.isFinal(mod)) {
// try to merge
// get values of both the old and new object
// if the new object has a null value, set the value of the new object to that of the old object
// otherwise, keep the new value
try {
final Object oldVal = f.get(oldObj);
final Object newVal = f.get(newObj);
if (newVal == null)
f.set(newObj, oldVal);
} catch (IllegalAccessException e) {
Log.e("Tenshi", "IllegalAccess in merge: " + e.toString());
e.printStackTrace();
}
}
}
}
There is no in-built method in room to do this
What you can do is, put check in the query for your update method.
#Query("UPDATE person SET name = (CASE WHEN :name IS NOT NULL THEN :name ELSE name END), description = (CASE WHEN :description IS NOT NULL THEN :description ELSE description END) WHERE id = :id")
Person update(id: Int, name: String, description: String)
We have written the update query for SQL which checks if the inserted values are null or not, and if they are null, then the previous values are retained.

Android Room: How to model relationships?

I have just started working with Room and although everything seems to be pretty intuitive I currently don't really understand how exactly I could handle relationships.
Because SQLite is a relational database, you can specify relationships between objects. Even though most ORM libraries allow entity objects to reference each other, Room explicitly forbids this. Even though you cannot use direct relationships, Room still allows you to define Foreign Key constraints between entities.(Source: https://developer.android.com/topic/libraries/architecture/room.html#no-object-references)
How should you model a Many to Many or One to Many Relationship?
What would this look like in practice (example DAOs + Entities)?
You can use #Relation annotation to handle relations at Room.
A convenience annotation which can be used in a Pojo to automatically
fetch relation entities. When the Pojo is returned from a query, all
of its relations are also fetched by Room.
See document.
(Google's document has confusing examples. I have written the steps and some basic explanation at my another answer. You can check it out)
I created a simple Convenience Method that populates manually a one to many relationship.
So for example if you have a one to many between Country and City , you can use the method to manually populate the cityList property in Country.
/**
* #param tableOne The table that contains the PK. We are not using annotations right now so the pk should be exposed via a getter getId();
* #param tableTwo The table that contains the FK. We are not using annotations right now so the Fk should be exposed via a getter get{TableOneName}Id(); eg. getCountryId();
* #param <T1> Table One Type
* #param <T2> Table Two Type
* #throws NoSuchFieldException
* #throws IllegalAccessException
* #throws NoSuchMethodException
* #throws InvocationTargetException
*/
private static <T1, T2> void oneToMany(List<T1> tableOne, List<T2> tableTwo) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
String tableOneName = tableOne.get(0).getClass().getSimpleName();
String tableTwoName = tableTwo.get(0).getClass().getSimpleName();
for (T1 t1 :
tableOne) {
Method method = t1.getClass().getMethod("getId");
Integer pkId = (Integer) method.invoke(t1);
List<T2> listForCurrentId = new ArrayList<>();
for (T2 t2 : tableTwo) {
Method fkMethod = t2.getClass().getDeclaredMethod("get".concat(tableOneName).concat("Id"));
Integer fkId = (Integer) fkMethod.invoke(t2);
if (pkId == fkId) {
listForCurrentId.add(t2);
}
}
Method tableTwoList = t1.getClass().getMethod("set".concat(tableTwoName).concat("List"), List.class);
tableTwoList.invoke(t1, listForCurrentId);
}
}
This is how I use it .
SystemDefaults systemDefaults = new SystemDefaults();
return Single.zip(systemDao.getRoles(), systemDao.getCountries(), systemDao.getCities(), (roles, countries, cities) -> {
systemDefaults.setRoles(roles);
*ConvenienceMethods.oneToMany(countries,cities);*
systemDefaults.setCountries(countries);
return systemDefaults;
});

why there is default value argument in preference

when we want to get value of a key we should do something like this.
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "defVal");
in getString document wrote.
/**
* Retrieve a set of String values from the preferences.
*
* <p>Note that you <em>must not</em> modify the set instance returned
* by this call. The consistency of the stored data is not guaranteed
* if you do, nor is your ability to modify the instance at all.
*
* #param key The name of the preference to retrieve.
* **#param defValues Values to return if this preference does not** exist.
*
* #return Returns the preference values if they exist, or defValues.
* Throws ClassCastException if there is a preference with this name
* that is not a Set.
*
* #throws ClassCastException
*/
now i have a question why default value argument must exist!?
Because the preference may not have been saved yet. It's easier to deal with this than to write:
String value;
if (sharedPreferences.contains(PREFS_KEY)) {
value = sharedPreferences.getString(PREFS_KEY);
} else {
value = "defaultValue";
}
The answer is in the documentation. What if you call
String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "defVal");
before saving any value? It will have to return null, which in turn can create problems like NullPointerException. So, default value is used as a precaution.

Android Realm Migration: Adding new Realm list column

Im using Realm v0.80.1 and I am trying to write migration code for a new property I added. The property is a RealmList. Im not sure how to properly add the new column or set a a value.
What I have:
customRealmTable.addColumn(, "list");
Once the column is properly added how would I go about setting an initial value for the list property? I would like to do something like:
customRealmTable.setRealmList(newColumnIndex, rowIndex, new RealmList<>());
As of Realm v1.0.0 (and maybe before), you can simply call RealmObjectSchema#addRealmListField(String, RealmObjectSchema) (link to javadoc) to achieve this. For example, if you're trying to add a permissions field of type RealmList<Permission> to your User class, you'd write:
if (!schema.get("User").hasField("permissions")) {
schema.get("User").addRealmListField("permissions", schema.get("Permission"));
}
There is also an example in Realm's migration docs here. And here is the full javadoc for addRealmListField, for convenience:
/**
* Adds a new field that references a {#link RealmList}.
*
* #param fieldName name of the field to add.
* #param objectSchema schema for the Realm type being referenced.
* #return the updated schema.
* #throws IllegalArgumentException if the field name is illegal or a field with that name already exists.
*/
You can see an example of adding a RealmList attribute in the examples here: https://github.com/realm/realm-java/blob/master/examples/migrationExample/src/main/java/io/realm/examples/realmmigrationexample/model/Migration.java#L78-L78
The relevant code is this section:
if (version == 1) {
Table personTable = realm.getTable(Person.class);
Table petTable = realm.getTable(Pet.class);
petTable.addColumn(ColumnType.STRING, "name");
petTable.addColumn(ColumnType.STRING, "type");
long petsIndex = personTable.addColumnLink(ColumnType.LINK_LIST, "pets", petTable);
long fullNameIndex = getIndexForProperty(personTable, "fullName");
for (int i = 0; i < personTable.size(); i++) {
if (personTable.getString(fullNameIndex, i).equals("JP McDonald")) {
personTable.getRow(i).getLinkList(petsIndex).add(petTable.add("Jimbo", "dog"));
}
}
version++;
}

Categories

Resources