I want to return an object with some things in them.
Here is the declaration;
Object user_det = get_user_det();
Here is the function code:
private Object get_user_det() {
Firebase f_user = new Firebase("https://myapp.firebaseio.com/User/");
f_user.addListenerForSingleValueEvent(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot snap_user) {
// TODO Auto-generated method stub
Iterable<DataSnapshot> rs = snap_user.getChildren();
Iterator<DataSnapshot> irs = rs.iterator();
long allNum2 = snap_user.getChildrenCount();
int maxNum2 = (int)allNum2;
int count_user = 1;
while(irs.hasNext())
{
if(count_user <= maxNum2)
{
Firebase user_data = new Firebase("https://myapp.firebaseio.com/");
AuthData authData = user_data.getAuth();
Map<String, Object> nPost = (Map<String, Object>) irs.next().getValue();
String db_email = nPost.get("email_addr").toString();
if (authData != null) {
String usr_email = authData.getProviderData().get("email").toString();
if(usr_email.equals(db_email))
{
//NB: I WANT TO ADD THE FOLLOWING INTO THE OBJECT
String disp_name = nPost.get("disp_name").toString();
String real_name = nPost.get("real_name").toString();
}
} else {
System.out.println("Failed");
}
}
count_user++;
}
}
#Override
public void onCancelled(FirebaseError arg0) {
// TODO Auto-generated method stub
}
});
return null; //NB: I NEED TO RETURN THE OBJECT HERE.
}
I want to return the string disp_name and real_name but they are inside the addListenerForSingleValueEvent, so how do I get them out and return it to the function.
I have wrote "NB" in the code where I need help with.
Thanks for your time.
If you want to return an object from your method in java, do it like this:
The Object class:
This contains the structure of your Object, and defines what data will be in it. Also includes methods to easily get the data.
private class myObject {
private String name;
private String realName;
//The constructor, so you can set the data when creating the Object.
public myObject (String disp_name, String real_name) {
name = disp_name;
realName = real_name;
}
//Getter methods, to get the data.
public String getRealName() {return realName;}
public String getDisplayName() {return name;}
}
Your code:
private Object get_user_det() {
myObject o; //Declare it, so it can be returned.
...
String disp_name = nPost.get("disp_name").toString();
String real_name = nPost.get("real_name").toString();
o = new myObject(disp_name, real_name); //create it and set the data.
...
return myobject; //return the new Object with the data.
}
To get the data from the Object:
myObject o = get_user_det(); //Call the metod which return our Object.
String realName = o.getRealName(); //Get the data from the Object.
String displayName = o.getDisplayName;
In your case, it would be much easier to use a String array.
Hope this helps.
It's probably easiest to see what's going on, if you add some printlns to your code:
private Object get_user_det() {
Firebase f_user = new Firebase("https://myapp.firebaseio.com/User/");
System.out.println("Adding listener");
f_user.addListenerForSingleValueEvent(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot snap_user) {
System.out.println("Data received");
}
#Override
public void onCancelled(FirebaseError arg0) {
// TODO Auto-generated method stub
}
});
System.out.println("Returning");
return null; //NB: I NEED TO RETURN THE OBJECT HERE.
}
If you execute this code, you will see that it logs:
Adding listener
Returning
Data received
Most likely, this is not what you expected. But hopefully, it makes sense if you read my explanation below.
Asynchronous loading
When you register your listener:
f_user.addListenerForSingleValueEvent(new ValueEventListener(){
You tell Firebase to start listening for events. It goes off and starts retrieving the data from the server.
Since retrieving the data may take some time, it does this retrieval asynchronously so that your thread isn't blocked. Once the data is completely retrieved, Firebase calls the onDataChange method in your listener.
Between the time you start listening and the time onDataChange is called, your code continues executing. So there is no way to return data that is loaded asynchronously, because by the time your function returns, the data isn't loaded yet.
Solutions
Disclaimer: I am not an expert at solving this problem in Java, so there may be problems with my solutions. If^H^HWhen you find any, please report them in the comments.
I know of three possible solutions to the problem:
force the code to wait for the data to be returned
return a Future that at some point will contain the data
pass a callback into get_user_det and call that function once the data is available
You will probably be tempted to selected option 1, since it matches most closely with your mental modal of loading data. While this is not necessarily wrong, keep in mind that there is a good reason that the loading is done asynchronously. It might be worth taking the "learning how to deal with asynchronicity" penalty now.
Instead of writing up examples for all solutions, I'll instead refer to some relevant questions:
Retrieving data from firebase returning NULL (an answer that uses approach 3)
Is waiting for return, ok?
Java wait() & notify() vs Android wait() & notify() (a question from a user taking approach 1)
How it works:
Firebase uses reflection to build a JSON tree object to save to the database. When you retrieve this JSON tree, you can cast it back to your original object. Just like serializing and deserializing. This means you do not need to handle the keys and values when trying to "rebuild" your object like you are. It can all be done like so:
YourObject object = (YourObject) dataSnapshot.getValue(YourObject.class);
Notice the YourObject.class in the getValue(). This tells firebase to reflect through this class and find the appropriate accessors with the dataSnapshot.
How to do it
Be sure that your object has:
Accessors Appropriate getters and setters for ALL fields - (or annotated with #JsonIgnore if you wish to not save a particular field)
Empty constructor. Your object must provide a constructor that does not modify itself at all.
What your object should look like:
public class YourObject {
private String displayName;
private String realName;
public YourObject() { /*Empty constructor needed for Firebase */ }
// Accessors
public void setRealName(String realName){
this.realName = realName;
}
public String getRealName(){
return this.realName;
}
public String getDisplayName(){
return this.displayName;
}
public void setDisplayName(String displayName){
this.displayName = displayName;
}
}
Then, in any of the firebase callbacks, you can just cast your DataSnapshot in to your object:
public void onDataChange(DataSnapshot snap_user) {
YourObject object = new Object;
if(snap_user.getValue() != null) {
try {
object = (YourObject) snap_user.getValue(YourObject.class); <-- Improtant!!!
} catch(ClassCastException ex) {
ex.printStackTrace();
}
}
return object;
}
Also
It seems you are retrieving many objects. When doing this, I find it best to use the onChildEventListener then for each of the YourObjects in that node, onChildAdded(DataSnapshot ds, String previousChild); will be called.
Related
In my app I am trying to use MediatorLiveData to listen to the changes to a livedata. Since DB operations are involved I use an executor service like this.
MediatorLiveData<Content> mediatorLiveData = new MediatorLiveData<>();
appExecutors.diskIO().execute(() -> {
long id = contentDao.insert(content);
Log.i("LIVE", id + "");
LiveData<Content> content = contentDao.getContentById(id);
mediatorLiveData.addSource(content, new Observer<Content>() {
#Override
public void onChanged(#Nullable Content content) {
Log.i("LIVE", "FIRED");
}
});
});
First I try to insert a new content object into the db. I get the id of the inserted object which I log in the next line. I get some id which is good. After this, I use the id to query for the same object. The query returns a LiveData. (If I use content.getValue() at this time, I get null.)
Then I listen to changes in this liveData using a MediatorLiveData. Unfortunately, the onChange method of the mediatorLiveData is never fired. Thus the Log is not printed too.
This is my content dao class
#Dao
public interface ContentDao {
#Insert
long insert(Content content);
#Query("SELECT * FROM my_table WHERE id = :id")
LiveData<Content> getContentById(long id);
}
I can't understand what I am doing wrong. Can someone please help. Thanks!!
Edit: To clarify, this is how the code looks.
return new NetworkBoundResource<Content, CreateContent>(appExecutors) {
#Override
protected void saveCallResult(#NonNull CreateContent item) {
//Something
}
#Override
protected boolean shouldCall(#Nullable Content data) {
//Something;
}
#Override
protected LiveData<Content> createDbCall() {
MediatorLiveData<Content> mediatorLiveData = new MediatorLiveData<>();
appExecutors.diskIO().execute(() -> {
long id = contentDao.insert(content);
Log.i("LIVE", id + "");
LiveData<Content> content = contentDao.getContentById(id);
mediatorLiveData.addSource(content, new Observer<Content>() {
#Override
public void onChanged(#Nullable Content c) {
Log.i("LIVE", "FIRED");
mediatorLiveData.removeSource(content);
mediatorLiveData.postValue(c);
}
});
});
return mediatorLiveData;
}
#NonNull
#Override
protected LiveData<ApiResponse<CreateContent>> createCall() {
//Something
}
}.asLiveData();
The value is returned to the constructor.
#MainThread
public NetworkBoundResource(AppExecutors appExecutors) {
this.appExecutors = appExecutors;
result.setValue(Resource.loading(null));
//TODO:: Add method to check if data should be saved. This should apply for search data.
LiveData<ResultType> dbSource = createDbCall();
result.addSource(dbSource, data -> {
result.removeSource(dbSource);
if (shouldCall(data)) {
fetchFromNetwork(dbSource);
} else {
result.addSource(dbSource, newData -> setValue(Resource.success(newData)));
}
});
}
As discussed you need to make sure the mediatorLiveData has an active observer attached.
If you take a look at the addSource method it checks whether any active observers are attached before subscribing to the source.
https://github.com/aosp-mirror/platform_frameworks_support/blob/d79202da157cdd94c2d0c0b6ee57170a97d12c93/lifecycle/livedata/src/main/java/androidx/lifecycle/MediatorLiveData.java#L95
In case anyone is re initializing a mediator live data, the old object only will be observed, new object will not be observed.
That is , dont do this:
Observe
myViewModel.observe(....)
Trying to allocate new memory to mediator
myMediatorObj = new MediatorLiveData<>(); //this can be the issue. Try removing if you have any lines like this.
//after this point,anything set to the object myMediatorObj will not be observed
In case you are trying to reset the data, pass in some data that signals null/empty/rest.
This question is a follow-up question from: Organize Android Realm data in lists
Due to the data returned by the API we use, it's slightly impossible to do an actual query on the realm database. Instead I'm wrapping my ordered data in a RealmList and adding a #PrimaryKey public String id; to it.
So our realm data looks like:
public class ListPhoto extends RealmObject {
#PrimaryKey public String id;
public RealmList<Photo> list; // Photo contains String/int/boolean
}
which makes easy to write to and read from the Realm DB by simply using the API endpoint as the id.
So a typical query on it looks like:
realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
This creates a slightly overhead of listening/subscribing to data because now I need to check listUser.isLoaded() use ListUser to addChangeListener/removeChangeListener and ListUser.list as an actual data on my adapter.
So my question is:
Is there a way I can query this realm to receive a RealmResults<Photo>. That way I could easily use this data in RealmRecyclerViewAdapter and use listeners directly on it.
Edit: to further clarify, I would like something like the following (I know this doesn't compile, it's just a pseudo-code on what I would like to achieve).
realm
.where(ListPhoto.class)
.equalTo("id", id)
.findFirstAsync() // get a results of that photo list
.where(Photo.class)
.getField("list")
.findAllAsync(); // get the field "list" into a `RealmResults<Photo>`
edit final code: considering it's not possible ATM to do it directly on queries, my final solution was to simply have an adapter that checks data and subscribe if needed. Code below:
public abstract class RealmAdapter
<T extends RealmModel,
VH extends RecyclerView.ViewHolder>
extends RealmRecyclerViewAdapter<T, VH>
implements RealmChangeListener<RealmModel> {
public RealmAdapter(Context context, OrderedRealmCollection data, RealmObject realmObject) {
super(context, data, true);
if (data == null) {
realmObject.addChangeListener(this);
}
}
#Override public void onChange(RealmModel element) {
RealmList list = null;
try {
// accessing the `getter` from the generated class
// because it can be list of Photo, User, Album, Comment, etc
// but the field name will always be `list` so the generated will always be realmGet$list
list = (RealmList) element.getClass().getMethod("realmGet$list").invoke(element);
} catch (Exception e) {
e.printStackTrace();
}
if (list != null) {
((RealmObject) element).removeChangeListener(this);
updateData(list);
}
}
}
First you query the ListPhoto, because it's async you have to register a listener for the results. Then in that listener you can query the result to get a RealmResult.
Something like this
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<RealmModel>() {
#Override
public void onChange(RealmModel element) {
RealmResults<Photo> photos = listPhoto.getList().where().findAll();
// do stuff with your photo results here.
// unregister the listener.
listPhoto.removeChangeListeners();
}
});
Note that you can actually query a RealmList. That's why we can call listPhoto.getList().where(). The where() just means "return all".
I cannot test it because I don't have your code. You may need to cast the element with ((ListPhoto) element).
I know you said you're not considering the option of using the synchronous API, but I still think it's worth noting that your problem would be solved like so:
RealmResults<Photo> results = realm.where(ListPhoto.class).equalTo("id", id).findFirst()
.getList().where().findAll();
EDIT: To be completely informative though, I cite the docs:
findFirstAsync
public E findFirstAsync()
Similar to findFirst() but runs asynchronously on a worker thread This method is only available from a Looper thread.
Returns: immediately an empty RealmObject.
Trying to access any field on the returned object before it is loaded
will throw an IllegalStateException.
Use RealmObject.isLoaded() to check if the object is fully loaded
or register a listener RealmObject.addChangeListener(io.realm.RealmChangeListener<E>) to be
notified when the query completes.
If no RealmObject was found after
the query completed, the returned RealmObject will have
RealmObject.isLoaded() set to true and RealmObject.isValid() set to
false.
So technically yes, you need to do the following:
private OrderedRealmCollection<Photo> photos = null;
//...
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<ListPhoto>() {
#Override
public void onChange(ListPhoto element) {
if(element.isValid()) {
realmRecyclerViewAdapter.updateData(element.list);
}
listPhoto.removeChangeListeners();
}
}
In my android app, I persist a workout object to realm. In one of my activities, I create an object with this code:
realm.beginTransaction();
Workout w = realm.createObject(Workout.class);
w.setmWorkoutId(UUID.randomUUID().toString());
realm.commitTransaction();
Here is my workout class:
public class Workout extends RealmObject{
private String mWorkoutId;
private int restSecsLeft;
private boolean prevSetOver = true;
private boolean workoutOver = false;
public Workout(){}
public String getmWorkoutId() {
return mWorkoutId;
}
public void setmWorkoutId(String mWorkoutId) {
this.mWorkoutId = mWorkoutId;
}
public int getRestSecsLeft() {
return restSecsLeft;
}
public void setRestSecsLeft(int restSecsLeft) {
this.restSecsLeft = restSecsLeft;
}
public boolean getPrevSetOver() {
return prevSetOver;
}
public void setPrevSetOver(boolean prevSetOver) {
this.prevSetOver = prevSetOver;
}
public boolean getWorkoutOver() {
return workoutOver;
}
public void setWorkoutOver(boolean workoutOver) {
this.workoutOver = workoutOver;
}
}
I have a service that runs after a workout is created, and after debugging odd behavior, have found an instance where the value of prevSetOver that is saved in a workout RealmObject is different than the value returned from w.getPrevSetOver(). I am not sure how this is happening--I do not change the value of the variable prevSetOver after an object is instantiated. I am a new realm user and do not understand how this is happening. The picture I have attatched is a screenshot of the w.prevSetOver() method and the RealmObject having different values.
There are more variables in the debugger in this screen, I left most of them out in my post for simplicity's sake.
It is the right behaviour of Realm.
Realm generates Proxy object which inherit from your Workout when compiling. And read/write data from/to Realm is actually implemented by the Proxy Object through overriding getters/setters. The original Object's member field won't be changed by Realm.
When Realm.createObject() get called, it does return a Proxy object, whose member fields are not what you expected.
You still can create a instance of the original model object which we call it standalone object (means it is not managed by Realm) by calling Workout w = new Workout(). This would act just like normal Java object. And you still can copy it to Realm by calling w = realm.copyToRealmOrUpdate(w). Notice we changed the w's value to the return value. The function will return a Proxy object which is managed by Realm now.
realm.beginTransaction();
Workout w = realm.createObject(Workout.class);
w.setmWorkoutId(UUID.randomUUID().toString());
realm.copyToRealm(w); //<-- u need
realm.commitTransaction();
is better add Primarykey, changed u String variable to long
#PrimaryKey
private long mWorkoutId;
I'm trying to setup an Android Service in my app that listens for new Childs in a Firebase Ref, and throws a Notification when that happens.
I'm having issues because addChildEventListener onChildAdded apparently is called one time for every existent record and only then actually listens for new childs..
In this answer #kato states that if addChildEventListener is called like ref.endAt().limit(1).addChildEventListener(...) it would get only the newly added records.
It actually only gets one record at a time (I suppose with limit(1)) but it still gets an existant record before listening for added records.
Here's some code:
Initializing the Listener in onCreate():
#Override
public void onCreate() {
super.onCreate();
this.handler = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
AllowedGroup ag = dataSnapshot.getValue(AllowedGroup.class);
postNotif("Group Added!", ag.getName());
}
...rest of needed overrides, not used...
I'm using the AllowedGroup.class to store the records, and postNotif to build and post the notification. This part is working as intended.
Then, onStartCommand():
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
this.f = new Firebase(FIREBASE_URL).child("users").child(this.currentUserUid).child("allowedGroups");
f.endAt().limit(1).addChildEventListener(handler);
return START_STICKY;
}
It still returns one existant record before actually listening for newly added childs.
I've also tried querying by timestamp, like so:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
this.f = new Firebase(FIREBASE_URL).child("users").child(this.currentUserUid).child("allowedGroups");
f.startAt(System.currentTimeMillis()).addChildEventListener(handler);
return START_STICKY;
}
Hoping that it would only get records set after the service was started. It doesn't get existant records, but doesn't even get newly added childs.
EDIT:
I've also thought of something like getting into memory first all of the existant records, and conditionally post a notification if the record brought by onChildAdded does not exist on the previously gathered list, but that seems a bit like overkill, and thought that could be an easier (more API-friendly) way of doing this, am I right ?
Can anyone provide me with some insight on this ? I can't really find anything on the official docs or in any StackOverflow question or tutorial.
Thanks.
If you have a field modifiedOn, you can index that and setup a query like:
Query byModified = firebaseEndpoint.orderByChild("modifiedOn").startAt(lastInAppTime);
For me the logic was is to have value -"status" for example- which needs to be validated before deciding whether it is really new or was an old record then I set the "status" to a different value so I don't get it next time:
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildKey) {
if(dataSnapshot.hasChildren()) {
String Id = (String) dataSnapshot.child("user_id").getValue();
String status = (String) dataSnapshot.child("status").getValue();
if (Id != null && Id.equals(storedId) && status != null && status.equals("created")) {
Log.d("INCOMING_REQUEST", "This is for you!");
sessionsRef.child(dataSnapshot.getKey()).child("status").setValue("received");
}
}
}
I overcome this problem by keeping items of firebase database reference node in a List; and whenever onChildAdded(...) method is triggered, check if the incoming datasnapshot is in your list or not; if not, then it's new data
In order to achieve this you must meet below conditions:
Have a unique value that must not repeated from item to item. (this can be easily achieved when you add items into Firebase using .push() method.
Override .equals() method of your model class in order to fulfill the comparison based on this unique value.
Here code snippets based on your inputs
Model class
public class AllowedGroup {
private String id;
public static List<AllowedGroup> sGroups;
// reset of fields ...
public AllowedGroup() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#Override
public boolean equals(#Nullable Object o) {
// If the object is compared with itself then return true
if (o == this) {
return true;
}
// Check if o is an instance of AllowedGroup or not
if (!(o instanceof AllowedGroup)) {
return false;
}
// typecast o to AllowedGroup so that we can compare data members
AllowedGroup group = (AllowedGroup) o;
// Compare data based on the unique id
return (group.id).equals(id);
}
}
Listening to firebase added nodes
#Override
public void onCreate() {
super.onCreate();
this.handler = new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
AllowedGroup group = dataSnapshot.getValue(AllowedGroup.class);
if (!AllowedGroup.sGroups.contains(group)) {
// Here you'll receive only the new added values
}
}
// ...rest of needed overrides, not used...
}
}
I finally managed to get an object out of an AsyncTask (DogAsyncTask) with an interface/listener (DogListener):
public String fout(String url) {
Dog d_in = new Dog("DogName");
DogAsyncTask task = new DogAsyncTask(d_in);
final String out = ""; <---Have tried but error for out becomes "The final local variable out cannot be assigned, since it is defined in an enclosing type"
task.setDogListener(new DogListener()
{
#SuppressWarnings("unchecked")
#Override
public void DogSuccessfully(String data) { <---The string I want to get
Log.e("DogListened", data);
out = data;
}
#Override
public void DogFailed() {}
});
task.execute(url);
return out;
}
My main activity calls this function (fout) and is supposed to get a String out of it. String data is already there and Log.e("DogListened", data); records it too. How can I return that outwards to my main activity? I have tried setting out = data and made out a final String on the outside but the "final local variable out cannot be assigned, since it is defined in an enclosing type" error comes up. How can I get around this?
Thanks
I guess you cannot access to out because it is out of the listener's scope. You can maybe pass your out as a reference parameter to the constructor of your DogListener.
final String out = "";
task.setDogListener(new DogListener( **out** )
{
#SuppressWarnings("unchecked")
#Override
public void DogSuccessfully(String data) {
Log.e("DogListened", data);
out = data;
}
#Override
public void DogFailed() {}
});
BUT honestly I donT know how to pass parameters as a reference in Java like in C#..
EDIT:
This can help you too: Can I pass parameters by reference in Java?
Use a value holder, which basically is an object which stores some value in a field. The value holder can be assigned as final, but you can change the value. Follow the link for more info on this pattern: http://martinfowler.com/eaaCatalog/lazyLoad.html
However you can use your string as value holder: Just append to the empty string assigned to out. Or better use a StringBuffer object.