How to store groups of booleans in Realm for Android? - android

Let's say I have a dozen booleans to store for each entity and expect the database to store about up to, at most, a few thousand records on any given Android device. As a fictional example, perhaps a Restaurant object has booleans like wifi, valetParking, sitDown, tippingEncouraged, listedOnYelp, etc. Let's also say I want to be able to easily search by them, so it's simple for a user to find restaurants that offer wifi and do not offer valet parking, for example.
How should these be stored on Realm?
I know this question in general for databases is something people like to argue over--whether to simply use a separate int field for each boolean, whether to have a separate table to implement a many-to-many relationship between the booleans and records, or whether to use each boolean as bitshifted flags and store the combination of flags as a single value?
For Realm specifically, my hunch is that it's easiest/best to just store each boolean separately, as it supports booleans and it seems the other approaches would be more complex than they would otherwise. But I'd love for someone more knowledgeable to say the correct, best supported approach in Realm.

Option 1: One field per boolean
public class Restaurant extends RealmObject {
private boolean wifi;
private boolean listedOnYelp;
/* getter, setter, etc.*/
}
Easy to implement
Easy to understand
RealmQueries based on the booleans are possible
Option 2: Flag field
64 booleans per long, the masks go from 21, 22, 23 to 264
public class Restaurant extends RealmObject {
private long booleanFlags;
private boolean getFlag(long mask) {
return (booleanFlags & mask) == mask;
}
private void setFlag(long mask, boolean value) {
if (value) {
booleanFlags |= mask;
} else {
booleanFlags &= ~mask;
}
}
public boolean hasWifi() {
return getFlag(1);
}
public void setWifi(boolean hasWifi) {
setFlag(1, hasWifi);
}
public boolean isListedOnYelp() {
return getFlag(2);
}
public boolean isTippingEncouraged() {
return getFlag(4);
}
}
Super memory efficient (unless Realm does not do the same in its core)
Not so easy to read or understand
No RealmQueries based on the booleans
Option 3: Extra class
public class Restaurant extends RealmObject {
/*...*/
}
public class RestaurantPropertyStore extends RealmObject {
private RealmList<Restaurant> restaurantsWithWifi;
private RealmList<Restaurant> restaurantsListedOnYelp;
}
Not object-oriented
Queries on one boolean at a time are possible
Option 1 is the object-oriented, Java-like and Realm-like way
Option 2 is the C, C++ way
Option 3 is the SQL, RDBMS way

Related

How can I sort array of numbers higher to lower (reverse/decreasing order)?

How can I sort array by numbers higher to lower.
Array
ArrayList<TestClass> array1 = new ArrayList<>();
Class
public class TestClass{
public boolean type;
public int counter;
public TestClass(boolean type, int counter) {
this.type = type;
this.counter = counter;
}
}
I tried do this
Collections.sort(array1);
But I got error
reason: no instance(s) of type variable(s) T exist so that TestClass conforms to Comparable
Assuming you don't have any accessory methods, you can use
array1.sort(Comparator.comparing(a -> a.counter));
The sorting order you asked for is reverse order, there are couple of ways to achieve this.
You can simple do a reverse of the previous sort like
array1.sort(Comparator.comparing(a -> a.counter));
array1.sort(Collections.reverseOrder());
If you can't user Comparator.comparing, you can do as follows
Collections.sort(array1, (item1, item2) -> Integer.compare(item2.counter, item1.counter));
The above statement can be explained as below.
Collections.sort() is provided from Java collections framework.
First argument specifies which collection needs to be sorted.
Second argument depicts on how each object in the collection should
be evaluated with other object in comparison. So for every pair of objects, in your case integers here, the condition returns true if the second element is greater than the first one. Which will pull the entire list to appear from higher to lower
arrayList.sort(new Comparator<TestClass>() {
#Override
public int compare(TestClass testClass, TestClass t1) {
return Integer.compare(t1.counter, testClass.counter);
}
});

How to use PreferenceDataStore in async mode?

I would like to store user preferences in Firebase real time database instead of typical sharedPreferences.
As an example, my user should specify the itemId in order to access to this part of the database:
items
itemId
members
uid: true
So when the user fill in the itemId, I need to check if this itemId exists in the database and display error asking the user to fill in another itemId.
I envisage to use standard Preference screens and associate a custom PreferenceDataStore at least to handle the itemId.
When reading the documentation, I need to override getString/putString methods:
public class DataStore extends PreferenceDataStore {
#Override
public void putString(String key, #Nullable String value) {
// Save the value somewhere
}
#Override
#Nullable
public String getString(String key, #Nullable String defValue) {
// Retrieve the value
}
}
At the end, I need to manage it asynchronously in order to query my database and get some results.
So I'm wondering if using PreferenceDataStore is the good approach ... My initial idea was to avoid developping settings screens that already exist in Androidx Preference lib.
Thx for your help!

Dao method returns List<String> while I need a Map<String,Integer>

In an Android app using Architecture Components I have the following view model:
public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<List<String>> mUnchecked = new MutableLiveData<>();
private LiveData<List<String>> mChecked;
public void setUnchecked(List<String> list) {
mUnchecked.setValue(list);
}
public LiveData<List<String>> getChecked() { // OBSERVED BY A FRAGMENT
return mChecked;
}
public MainViewModel(Application app) {
super(app);
mChecked = Transformations.switchMap(mUnchecked,
list-> myDao().checkWords(list));
}
The purpose of the above switchMap is to check, which of the words passed as a list of strings, do exist in a Room table:
#Dao
public interface MyDao {
#Query("SELECT word FROM dictionary WHERE word IN (:words)")
LiveData<List<String>> checkWords(List<String> words);
The above code works well for me!
However I am stuck with wanting something slightly different -
Instead of the list of strings, I would prefer to pass a map of strings (words) -> integers (scores):
public void setUnchecked(Map<String,Integer> map) {
mUnchecked.setValue(map);
}
The integers would be word scores in my game. And once the checkWords() has returned the results, I would like to set the scores to null for the words not found in the Room table and leave the other scores as they are.
The programming code would be easy (iterate through mChecked.getValue() and set to null for the words not found in the list returned by the DAO method) - but how to "marry" it with my LiveData members?
TL;DR
I would like to change my view model to hold maps instead of the lists:
public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<>();
private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();
public void setUnchecked(Map<String,Integer> map) {
mUnchecked.setValue(map);
}
public LiveData<Map<String,Integer>> getChecked() { // OBSERVED BY A FRAGMENT
return mChecked;
}
public MainViewModel(Application app) {
super(app);
// HOW TO OBSERVE mUnchecked
// AND RUN myDao().checkWords(new ArrayList<>(mUnchecked.getValue().keys()))
// WRAPPED IN Executors.newSingleThreadScheduledExecutor().execute( ... )
// AND THEN CALL mChecked.postValue() ?
}
How to achieve that please? Should I extend MutableLiveData or maybe use MediatorLiveData or maybe use Transformations.switchMap()?
UPDATE:
I will try the following tomorrow (today is too late in the evening) -
The Dao method I will change to return a list instead of LiveData:
#Query("SELECT word FROM dictionary WHERE word IN (:words)")
List<String> checkWords(List<String> words);
And then I will try to extend the MutableLiveData:
private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();
private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<Map<String,Integer>>() {
#Override
public void setValue(Map<String,Integer> uncheckedMap) {
super.setValue(uncheckedMap);
Executors.newSingleThreadScheduledExecutor().execute(() -> {
List<String> uncheckedList = new ArrayList<>(uncheckedMap.keySet());
List<String> checkedList = WordsDatabase.getInstance(mApp).wordsDao().checkWords(uncheckedList);
Map<String,Integer> checkedMap = new HashMap<>();
for (String word: uncheckedList) {
Integer score = (checkedList.contains(word) ? uncheckedMap.get(word) : null);
checkedMap.put(word, score);
}
mChecked.postValue(checkedMap);
});
}
};
Well, what you have there in the update probably works, though I wouldn't create a new Executor for every setValue() call — create just one and hold onto it in your MutableLiveData subclass. Also, depending on your minSdkVersion, you might use some of the Java 8 stuff on HashMap (e.g., replaceAll()) to simplify the code a bit.
You could use MediatorLiveData, though in the end I think it would result in more code, not less. So, while from a purity standpoint MediatorLiveData is a better answer, that may not be a good reason for you to use it.
Frankly, this sort of thing isn't what LiveData is really set up for, IMHO. If this were my code that I were working on right now, I'd be using RxJava for the bulk of it, converting to LiveData in the end. And, I'd have as much of this as possible in a repository, rather than in a viewmodel. While your unchecked-to-checked stuff would be a tricky RxJava chain to work out, I'd still prefer it to the MutableLiveData subclass.
What EpicPandaForce suggests is an ideal sort of LiveData-only approach, though I don't think he is implementing your algorithm quite correctly, and I am skeptical that it can be adapted easily to your desired algorithm.
In the end, though, the decision kinda comes down to: who is going to see this code?
If this code is for your eyes only, or will live in a dusty GitHub repo that few are likely to look at, then if you feel that you can maintain the MutableLiveData subclass, we can't really complain.
If this code is going to be reviewed by co-workers, ask your co-workers what they think.
If this code is going to be reviewed by prospective employers... consider RxJava. Yes, it has a learning curve, but for the purposes of getting interest from employers, they will be more impressed by you knowing how to use RxJava than by you knowing how to hack LiveData to get what you want.
Tricky question!
If we check the source code for Transformations.switchMap, we see that:
1.) it wraps the provided live data with a MediatorLiveData
2.) if the wrapped live data emits an event, then it invokes a function that receives the new value of wrapped live data, and returns a "new" live data of a different type
3.) if the "new" live data of a different type differs from the previous one, then the observer of the previous one is removed, and it's added to the new one instead (so that you only observe the newest LiveData and don't accidentally end up observing an old one)
With that in mind, I think we can chain your switchMap calls and create a new LiveData whenever myDao().checkWords(words) changes.
LiveData<List<String>> foundInDb = Transformations.switchMap(mWords, words -> myDao().checkWords(words));
LiveData<Map<String, Integer>> found = Transformations.switchMap(foundInDb, (words) -> {
MutableLiveData<Map<String, Integer>> scoreMap = new MutableLiveData<>();
// calculate the score map from `words` list
scoreMap.setValue(map);
return scoreMap;
});
this.mFound = found;
Please verify if what I'm telling you is correct, though.
Also if there are a bunch of words, consider using some async mechanism and scoreMap.postValue(map).

Find all child realm objects where the parent's id is X

In this example, the docs talked about getting the parent objects while specifying queries for the child objects.
Is there a way for getting the child objects while specifying a query for the parent object?
In the given example, can I search for dogs who are of brown color with the user named John?
EDIT: Since Realm 3.5.0, you can actually use the "backlinks" mentioned in the comment section. Rejoice!
In fact, since Realm 3.0.0, bidirectional links are a performance bottleneck, so using backlinks is the preferred way.
The way it works is:
public class User extends RealmObject {
private RealmList<Dog> dogs;
}
public class Dog extends RealmObject {
#LinkingObjects("dogs")
private final RealmResults<User> owners = null;
}
Now you can do:
realm.where(Dog.class).equalTo("color", "Brown").equalTo("owners.name", "John").findAll();
OLD ANSWER:
You can only search for dogs with a given user if you have an object link to the User.
public class Dog extends RealmObject {
//...
private User user;
}
Then you could do
realm.where(Dog.class).equalTo("color", "Brown").equalTo("user.name", "John").findAll();

Practical use of #Ignore in Realm?

I've been trying to add Realm in my Android app. Their docs are pretty well explained & easy to follow. But it fails to explain this one particular area. I'm unable to figure out the practical use for the #Ignore annotation. I know that fields under this annotation are not persisted.
Can someone please share a few use cases. Also I wanted to know the scope of such fields. I mean, if I set an #Ignore field to some value, would that value be available to the other classes in my app for that particular launch session. If yes, then how do we access it? If no (which I guess is the case), then why do we need such a field anyway?
I've searched here and on web but couldn't find the relevant information. If out of my ignorance, I've missed upon some resource, please guide me to it.
Thanks.
Accordingly to the official documentation (see https://realm.io/docs/java/latest/) #Ignore is useful in two cases:
When you use GSON integration and your JSON contains more data than you want to store, but you still would like to parse it, and use right after.
You can't create custom getters and setter in classes extending RealmObject, since they are going to be overridden. But in case you want to have some custom logic anyway, ignored fields can be used as a hack to do that, because Realm doesn't override their getter & setters. Example:
package io.realm.entities;
import io.realm.RealmObject;
import io.realm.annotations.Ignore;
public class StringOnly extends RealmObject {
private String name;
#Ignore
private String kingName;
// custom setter
public void setKingName(String kingName) { setName("King " + kingName); }
// custom getter
public String getKingName() { return getName(); }
// setter and getter for 'name'
}
Ignored fields are accessible only from the object they were set in (same as with regular objects in Java).
UPDATE: As the #The-null-Pointer- pointed out in the comments the second point is out of date. Realm now allows having custom getters and setters in Realm models.
Here's a couple of real-world use cases:
1 - Get user's fullname:
public class User extends RealmObject {
private String first;
private String last;
#Ignore
private String fullName;
public String getFullName() {
return getFirst() + " " + getLast();
}
Get JSON representation of object:
public class User extends RealmObject {
private String first;
private String last;
#Ignore
private JSONObject Json;
public JSONObject getJson() {
try {
JSONObject dict = new JSONObject();
dict.put("first", getFirst());
dict.put("last", getLast());
return dict;
} catch (JSONException e) {
// log the exception
}
return null;
}
I've found it useful to define field names for when I am querying. For example
User.java
public class User extends RealmObject {
#Index
public String name;
#Ignore
public static final String NAME = "name";
}
And then later on I can do something like:
realm.where(User.class).equalTo(User.NAME, "John").findFirst();
This way if the schema changes from say name to id I don't have to hunt down every occurrence of "name".
Please see the the official documentation about #Ignore annotation:
The annotation #Ignore implies that a field should not be persisted to disk. Ignored fields are useful if your input contains more fields than your model, and you don’t wish to have many special cases for handling these unused data fields.

Categories

Resources