Gathering data from Firebase asynchronously: when is the data-set complete? - asynchronous

in a Firebase Android app that I'm currently developing I would like to provide an export feature. This feature should allow the user to export a set of data that is stored in Firebase.
My plan is to gather all required data into a intermediate object (datastructure) that can be (re-)used for multiple export types.
I am running into the issue that because of the flat Firebase data structure that I am using (as explained in https://www.firebase.com/docs/android/guide/structuring-data.html), it's difficult to know when all the data required for the export has been collected.
Example: when retrieving all objects that are referenced using 'indices' (name: key, value true), for each of these I set an addListenerForSingleValueEvent listener, but because this returns asynchronous, it's impossible to determine when all the indices are retrieved. This way it's not possible to determine the correct moment to start the export.
Who has best practices for coping with this?

Posting this to show a worked out example of the comment of #FrankvanPuffelen that seems to do the job quite well:
#Override
public void onDataChange(DataSnapshot indexListDataSnapshot) {
final long participantsRequired = indexListDataSnapshot.getChildrenCount();
for (DataSnapshot ds : indexListDataSnapshot.getChildren()) {
DataUtil.getParticipantByKey( mEventKey, ds.getKey() ).addListenerForSingleValueEvent( new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Participant p = dataSnapshot.getValue(Participant.class);
mParticipants.add( p );
if (participantsRequired == mParticipants.size()){
executeExport();
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
mListener.onDataLoadFailure( firebaseError.toException() );
}
});
}
}

Related

How to check existing data when using push ID using Firebase?

I have some problem on checking existing data on firebase. i want to check data date , timeIn and timeOut before inserting data into firebase. The code that i make doesnt work because its check data by push id.
private void addCourtBooking(){
CourtBookingDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if ((dataSnapshot.child(dateinput.getText().toString()).exists() && dataSnapshot.child(timeStartSpinner.getSelectedItem().toString()).exists()) && dataSnapshot.child(timeEndSpinner.getSelectedItem().toString()).exists()) {
Toast.makeText(getApplicationContext(), "Exist", Toast.LENGTH_LONG).show();
}else {
UCourtType = txtCourtType.getText().toString();
UPurpose = sp.getSelectedItem().toString();
UDate = dateinput.getText().toString();
UTimeStart = timeStartSpinner.getSelectedItem().toString();
UTimeEnd = timeEndSpinner.getSelectedItem().toString();
Map CourtBookinginformation = new HashMap();
CourtBookinginformation.put("courtType",UCourtType);
CourtBookinginformation.put("purpose",UPurpose);
CourtBookinginformation.put("date",UDate);
CourtBookinginformation.put("timeIn",UTimeStart);
CourtBookinginformation.put("timeOut",UTimeEnd);
CourtBookingDatabase.setValue(CourtBookinginformation);
Toast.makeText(getApplicationContext(),"Booking Successfull", Toast.LENGTH_LONG).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
If I'm not wrong I understand that you wanna update that field just if the data didn't exist, and if exists, do nothing.
In that case you should use an update call to update the database, in case that your data exists and equals the same data that you wanna push, the database will do nothing:
In case that you wanna avoid the writing in any case if the data already exist,, you should use a transaction. From the documentation:
When working with data that could be corrupted by concurrent modifications, such as incremental counters, you can use a transaction operation. You give this operation two arguments: an update function and an optional completion callback. The update function takes the current state of the data as an argument and returns the new desired state you would like to write. If another client writes to the location before your new value is successfully written, your update function is called again with the new current value, and the write is retried.
Using this, you will be able to read the data, check if it exists and do whatever you need depending if it exists or not.
You can read more about read and write data in Firebase in the next link from the official documentation:
https://firebase.google.com/docs/database/android/read-and-write

Query Data from Another Backendless App

Does backendless support querying data from another Backendless App Table? If yes how.
If I have Backendless App called Music with Table songs and I have another App called Movie. I want to query the song table from the Movie app.
You gonna need to call Backendless.initApp(context, {another_app_id}, {another_app_secret_key}, v1) before each call to a different application. This way you'll be able to talk to any number of apps.
#Save
useres useres = new useres();
useres.setName("Amin");
useres.setId("DOREH");
useres.setScore("20");
useres.setData("2016.12.24");
Backendless.Persistence.save(useres, new AsyncCallback<com.example.amin.bback.useres>() {
#Override
public void handleResponse(useres response)
{
Log.d( "Register",response.toString());
}
#Override
public void handleFault(BackendlessFault fault)
{
Log.d( "Register faild ",fault.toString());
}
});

Joining flattened data from Firebase Realtime Database using RxJava

Joining flattened data is a common use case also described in the documentation. But the documentation shows a simple example which is not real-time, it doesn't react to changes. I'm looking for a more robust implementation. I think RxJava is ideal for this.
Consider following Firebase structure:
{
"messages": {
"group_id_1": {
"message_id_1": {
"text": "Hello",
"author": "uid_1"
}
}
},
"users": {
"uid_1": {
"name": "David"
}
},
"rooms": {
"room_id_1": {
"name": "General",
"members": {
"uid_1": true
}
}
}
}
I see two use-cases here:
Get list of messages in a group with author names
I imagine I would get Observable<Message> and when I subscribe to it, dependencies (users for those messages) will be subscribed as well in some cache. When I'm showing the messages, I can get author's names from the cache.
It's also real-time - if author name changes, the observable emits changed Message.
When I unsubscribe to the observable, also dependencies unsubscribes.
Get a list of room members with their names
I imagine I would get Observable<User> and when I subscribe to it, it will first subscribe to room's members and then to individual users.
It's real-time - if room members change, I get notified about that.
When I unsubscribe to the observable, also dependency unsubscribes.
Do you know about library/solution which could do that?
Or would you use it if I created one?
I was going to pose a variation of this question but seemed like it might be better to build on top of this one...I'll describe what is hopefully at least partially the answer to above question but also then a shortcoming I'm seeing.
Using above data model we might have something like following to create RxJava wrapper around firebase query to get list of member keys for particular room and for getting details for particular member (note use of onCompleted() in subscriber.getMemberInfo...more on that later!).
public Observable<String> getRoomMembers(String roomId) {
return Observable.create(subscriber -> {
databaseReference.child(roomId + "/members").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot childSnapshot: dataSnapshot.getChildren()) {
String userId = childSnapshot.getKey()
subscriber.onNext(userId);
}
subscriber.onCompleted();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
});
}
public Observable<Member> getMemberInfo(String memberId) {
return Observable.create(subscriber -> {
databaseReference.child(memberId).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Member member = dataSnapshot.getValue(Member.class);
subscriber.onNext(member);
subscriber.onCompleted();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
});
}
What we can then do use something like following to get list of Members for particular room (have added isActive property to Member to show how we also filter results we get).
getRoomMembers(roomId)
.flatMap(memberId -> getMemberInfo(memberId))
.filter(Member::isActive)
.toList()
.subscribe(members -> {
});
So, the above works up to a point. The issue is that I had to call subscriber.onCompleted() in getMemberInfo for the above call to flatMap to work....which then means that any subsequent changes to Member data isn't triggering update in the above subscription. Am relatively new to RxJava and Firebase so might be missing something obvious.
I eventually solved it with these two methods (in Kotlin, Java is similar, just more verbose):
fun <A, B> Observable<List<A>>.mapSubQueries(subQuery: (A) -> Observable<B>): Observable<List<Observable<B>>> {
return this.flatMap {
if (it.isNotEmpty()) {
return#flatMap Observable.from(it).map { subQuery(it) }.toList()
} else {
return#flatMap Observable.just(listOf<Observable<B>>())
}
}
}
#Suppress("UNCHECKED_CAST")
fun <T> Observable<List<Observable<T>>>.joinSubQueries(): Observable<List<T>> {
return this.flatMap {
if (it.isNotEmpty()) {
return#flatMap Observable.combineLatest(it, {
val list = mutableListOf<T>()
it.forEach {
list.add(it as T)
}
list
})
} else {
return#flatMap Observable.just(listOf<T>())
}
}
}
To get users in all messages, I can use it like this:
fun usersInMessages(roomId): Observable<List<User>> {
return DatabaseRead.messages(roomId)
.mapSubQueries { DatabaseRead.user(it.getAuthor()) }
.joinSubQueries()
}
I decided that it's better to keep this code in my codebase and modify it slightly for various use-cases. Making it a library would make it less flexible. The main point is always use Observable.combineLatest(). Many other Rx parameters are useless, because they require onComplete() call and here I deal with infinite Observables.

RxJava one observable, multiple subscribers, one execution

I create an Observable from a long running operation + callback like this:
public Observable<API> login(){
return Observable.create(new Observable.OnSubscribe<API>() {
#Override
public void call(final Subscriber<? super API> subscriber) {
API.login(new SimpleLoginListener() {
#Override
public void onLoginSuccess(String token) {
subscriber.onNext(API.from(token));
subscriber.onCompleted();
}
#Override
public void onLoginFailed(String reason) {
subscriber.onNext(API.error());
subscriber.onCompleted();
}
});
}
})
}
A successfully logged-in api is the pre-condition for multiple other operations like api.getX(), api.getY() so I thought I could chain these operation with RxJava and flatMap like this (simplified): login().getX() or login().getY().
My biggest problem is now, that I don't have control over when login(callback) is executed. However I want to be able to reuse the login result for all calls.
This means: the wrapped login(callback) call should be executed only once. The result should then be used for all following calls.
It seems the result would be similar to a queue that aggregates subscribers and then shares the result of the first execution.
What is the best way to achieve this? Am I missing a simpler alternative?
I tried code from this question and experiemented with cache(), share(), publish(), refCount() etc. but the wrapped function is called 3x when I do this for all of the mentioned operators:
apiWrapper.getX();
apiWrapper.getX();
apiWrapper.getY();
Is there something like autoConnect(time window) that aggregates multiple successive subscribers?
Applying cache() should make sure login is only called once.
public Observable<API> login() {
return Observable.create(s -> {
API.login(new SimpleLoginListener() {
#Override
public void onLoginSuccess(String token) {
s.setProducer(new SingleProducer<>(s, API.from(token)));
}
#Override
public void onLoginFailed(String reason) {
s.setProducer(new SingleProducer<>(s, API.error()));
}
});
}).cache();
}
If, for some reason you want to "clear" the cache, you can do the following trick:
AtomicReference<Observable<API>> loginCache = new AtomicReference<>(login());
public Observable<API> cachedLogin() {
return Observable.defer(() -> loginCache.get());
}
public void clearLoginCache() {
loginCache.set(login());
}
Ok I think I found one major problem in my approach:
Observable.create() is a factory method so even if every single observable was working as intented, I created many of them. One way to avoid this mistake is to create a single instance:
if(instance==null){ instance = Observable.create(...) }
return instance

How to prevent static variable became null after application holds for long time

I have developed and released one application in market long ago. Now some some users pointed crashes when holding application for long time. Now I identified the reason for the crash, that is I am using a class with static variable and methods to store data (getters and setters). Now I want to replace the static way with any other ways.From my study I got the following suggestions:
shared preferences: I have to store more than 40 variable (strings, int and json arrays and objects), So I think using shared preferences is not a good idea.
SQLite: more than 40 fields are there and I don't need to keep more than one value at a time.I am getting values for fields from different activities. I mean name from one activity , age from another activity, etc So using SQLite also not a good Idea I think.
Application classes: Now I am thinking about using application classes to store these data. Will it loss the data like static variable after hold the app for long time?
Now I replace the static variable with application class . Please let me know that application data also became null after long time?
It may useful to somebody.
Even though I didn't get a solution for my problem, I got the reason for why shouldn't we use application objects to hold the data. Please check the below link
Don't use application object to store data
Normally if you have to keep something in case your Activity gets destroyed you save all these things in onSaveInstanceState and restore them in onCreate or in onRestoreInstanceState
public class MyActivity extends Activity {
int myVariable;
final String ARG_MY_VAR="myvar";
public void onCreate(Bundle savedState) {
if(savedState != null {
myVariable = savedState.getInt(ARG_MY_VAR);
} else {
myVariable = someDefaultValue;
}
public void onSaveInstanceState(Bundle outState) {
outState.putInt(ARG_MY_VAR, myVariable);
super.onSaveInstanceState(outState);
}
}
Here if Android OS destroys your Activity onSaveInstanceState will be called and your important variable will be saved. Then when the user returns to your app again Android OS restores the activity and your variable will be correctly initialized
This does not happen when you call finish() yourself though, it happens only when Android destroys your activity for some reasons (which is quite likely to happen anytime while your app is in background).
First you should overwrite the onSaveInstanceState and onRestoreInstanceState methods in you activity:
#Override
protected void onSaveInstanceState (Bundle outState){
outState.putString("myVariable", myVariable);
// Store all you data inside the bundle
}
#Override
protected void onRestoreInstanceState (Bundle savedInstanceState){
if(savedInstanceState != null){
myVariable = savedInstanceState.getString("myVariable");
// Restore all the variables
}
}
May be try use static variable inside Application space?
public class YourApplication extends Application
{
private static ApplicationBubblick singleton;
public String str;
public static YourApplication getInstance()
{
return singleton;
}
}
And use variable via:
YourApplication.getInstance().str = ...; // set variable
... = YourApplication.getInstance().str; // get variable
This variable will be live until your app will start and stop all services or activities of your app. This is not work when your app crash.

Resources