I've been playing with the Firebase Android Api and discovered this feature (that appears to me as inconsistency). I would like to solicit an opinion and/or a solution for a problem it causes in my scenario.
In the Android app, I establish a standard 'ChildEventListener' in this form:
new Firebase("https://[MYTEST].firebaseio.com/").addChildEventListener(
new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot data, String prev) {
Log.i("_", "add " + data);
}
#Override
public void onChildChanged(DataSnapshot data, String prev) {
Log.i("_", "chg " + data);
}
#Override
public void onChildRemoved(DataSnapshot data) {
Log.i("_", "del " + data);
}
#Override
public void onChildMoved(DataSnapshot data, String prev) {
Log.i("_", "mov " + data);
}
#Override
public void onCancelled(FirebaseError err) {
Log.i("_", "err " + err.getMessage());
}
}
);
Works like a charm, catching all events I can throw on it from the Firebase Dashboard.
Next:
Stop&kill my Android app and add a key (a few keys) through the dashboard.
Start the app and the 'onChildAdded()' catches up with everything that has been added. (actually gives me the full set - as specked and expected). I can easily figure out what's been added. So far so good.
I try the same with deletion. Stop&kill the app and delete a key (many keys) through the dashboard.
Start the app again and get the 'onChildAdded()' again with real amount of nodes left, OR NO EVENT IF THERE IS NO NODE LEFT.
This time, I RECEIVE NO 'onChildRemoved()' EVENT that would reflect what happened while the app was dead.
I understand that this behavior may be by design, but it presents a problem even if I keep a local list of nodes and simple differential may give me the set of deleted nodes. But the fact, that an empty list of nodes generates no 'onChildAdded()' event makes it difficult to place the code that would calculate deleted nodes.
I assume there is another way to get around this 'deletion while dead' problem, please nudge me in the right direction.
Summary: the Firebase database synchronizes state. Unlike message passing mechanisms it does not synchronize state changes.
When you start an app that has no data from the Firebase database cached, the Firebase API synchronizes the minimal data that your app needs to get synchronized with the stored information. So you'll get a child_added event for any data that exists on the server at that time.
If your app already has previous information from the database when it connects (such as when you've used Firebase's disk persistence or when your connection is restored without an app restart), the API will fire the events that are necessary to get the local version of the data up to date with the server. So in that case, you may see child_added, child_changed, child_moved and child_removed events.
There is no guarantee that state changes when your client was not connected are transmitted.
State synchronization in practice
An example of this:
go online
get all data
go offline
another user adds value A
another user removes value A
go online again
Your app will now not receive any indication that value A ever existed. This is simply the way state synchronization works.
Storing state changes, instead of state
If your app requires that each client knows of every state change, you should store the actual state changes in the database.
go online
get all data
go offline
another user writes record "add value A"
another user writes record "remove value A"
go online again
The client will now receive all state changes, because you stored those in the database.
Typically you'll also want to run a trusted process that aggregates all the state change records into a single "current state" again for faster access.
Related
I had a firebaseAuth and FirebaseDatabse using Android App. I found out some issues with the FirebaseAuth.getInstance().getCurrentUser() returning a previously logged user after I uninstall and re install the app. I posted this issue here
However I also noticed that call to FirebaseDatabse addListenerForSingleValueEvent() callback listener changes some of the data in my database and it only happens when I uninstall and reinstall the app again. shown below is the code I am using to get the current user from the FirebaseDB.
final FirebaseAuth auth = FirebaseAuth.getInstance();
if (auth.getCurrentUser() != null) {
// already signed in
mDatabase.getReference("users").child(auth.getCurrentUser().getUid()).addListenerForSingleValueEvent(
new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// I commented the code here to check whether its coming from this part but it continued to occur even after commenting.
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "getUser:onCancelled", databaseError.toException());
}
});
}
As you can see I am accessing a node called users in the database by using user ID as the key and I am successfully getting the user data. However there is another node named "posts" in my database and I am using the same User IDs as the key. But the users and posts are two different nodes in the database.
So what happens is when the above addListenerForSingleValueEvent() is been called, some of the data under the corresponding user ID in the posts node changes. Right after a few seconds of the callback I can see some new data been removed from users post in red color and some been changed in yellow color.
When I commented the entire callback it was OK nothing changed or removed in posts.
AS I mentioned this only happens when I uninstall and reinstall again. I tried clearing cache without uninstalling and it was OK, I also tried clearing data and it was still OK.
I should also mention that I am using mDatabase.setPersistenceEnabled(true); and this issue only occurs when this is set to true. I tried commenting this line and everything was working fine.
Can anyone please help me as I am lost at this stage.
I'm an early and very happy adopter of both Flux and React so much so that, recently, I ported Fluxxor into Android and it's been okay so far.
The issue I am having with it Flux is dealing with data for a Single Item or Details Page. Bear with me below. I will try to be as clear as I can.
The pattern I am using is.
On page load(componentWillMount/componentWillReceiveProps and onStart), I check if the id passed to the page (via url or bundle) matches the id of the item currently in the store and the page if the store is in a processing or success state.
If yes, I do nothing, else, I dispatch an action to load the data for that item.
componentWillMount: function () {
id = this.props.params.path.split("-")[0];
var artistData = this.props.state.artistData;
if(artistData.id != id)
this.getFlux().actions.artistActions.loadArtist(id);
else if (!artistData.artist && !artistData.loading)
this.getFlux().actions.artistActions.loadArtist(id);
this.getFlux().actions.userActions.fetchSuggestions();
}
protected void onStart() {
GenreSongsStore.State state = App.getFlux().getStore(GenreSongsStore.class).getState();
if(mId == state.Genre.getId()) {
if (state.HasMore)
App.getFlux().getActions().Genres.songs(mId, state.Page + 1);
}
else
App.getFlux().getActions().Genres.songs(mId, 1);
super.onStart();
}
In React this is fine since you use a single state on the root. I didn't bother too much until I started working with Android.
Here, I don't use a single state but query the relevant store and it totally smells
If you are not using that page, the data is still held in memory
Since the data is not shared it seems there is no benefit to doing it like this
Won't it simply be easier to load the data in the component/activity/fragment?
However, I get the benefit of maintaining the currently loading state. So the user can minimize and reopen the app and we continue (no need for saving an instance bundle).
I know by doing it like this, I lose the benefit of unidirectional data flow. But it seems to make more sense in the context of Android (pun intended).
Can I have your views on how you do this and if I'm simply worried about nothing.
NB: The data is not shared by any other stores at.
My current Android application uses Firebase to store user data.
My data has the following "structure" (and/or levels)
my-android-app.firebaseio.com/level0/level1/UUID1/UUID2
data objects are stored at UUID2 that resemble this
{
"Students":[
{
"Name":"Amit Goenka",
"Major":"Physics"
},
{
"Name":"Smita Pallod",
"Major":"Chemistry"
},
{
"Name":"Rajeev Sen",
"Major":"Mathematics"
}
]
}
I need to be able to detect when child data is added, removed or changed etc
below (at) UUID2, however I only want to set up my ChildEventListener using this path
android-app.firebaseio.com/level0/level1
is this possible?
During my test so far I have had to specify the full path of:-
my-android-app.firebaseio.com/level0/level1/UUID1/UUID2
Firebase will fire a child_* event for any change under the level where you register the correct listener. But it will only fire child_added/child_removed for nodes added/removed directly under the level where you registered. For other changes, it will fire child_changed and possibly child_moved.
Your options:
flatten the tree
listen to value events
listen to all child_ events and figure out the changes
I was so excited to hear about the turn-based match in the new google game services but at the same time a bit disappointed to not see the "flow" of turn based game especially cards games will have hard time to fit the design into the "expected flow" by google turn base. One of the issues I found and I really hope that I missunderstood it in the documentation is updating the game state
According to the documentation, if a user takes a turn (lets say throw a an Ace of Heart) then this will information will be rendered only on the device of the next player. Is there no way to update this information on all the participants devices at the same time? Otherwise the 6th player will have to wait for 5 turns before seeing a movement on his screen!
Any idea?
From the Saving Game State guide:
Call takeTurn() and pass in your game state data as the matchData parameter.
If the call is successful, Play Games services notifies other participants in the match about the update and makes the match data available on all participant devices.
Your game can then call getData() to retrieve the updated game state.
So it appears all participants get the updated state.
I know this thread is old but anyway I'll put my two cents.
If you want be notified whenever any participant in the match takes a turn, attach a OnTurnBasedMatchUpdateReceivedListener to your activity. Whenever the match is updated following a player's turn, your listener is notified via the onTurnBasedMatchedReceived() callback.
You can attach a OnTurnBasedMatchUpdateReceivedListener like this.
public class TurnBasedActivity extends BaseGameActivity implements OnTurnBasedMatchUpdateReceivedListener{
#Override
public void onSignInSucceeded() {
Games.TurnBasedMultiplayer.registerMatchUpdateListener(getApiClient(), this);
}
#Override
public void onTurnBasedMatchReceived(TurnBasedMatch match) {
Toast.makeText(this, "A match was updated.", TOAST_DELAY).show();
}
#Override
public void onTurnBasedMatchRemoved(String matchId) {
Toast.makeText(this, "A match was removed.", TOAST_DELAY).show();
}
}
}
I took the information from here https://developers.google.com/games/services/android/turnbasedMultiplayer#taking_the_first_turn
Hope it helps somebody else.
I'd like to change the messages in the list "Reject call with message" according to the calling number (whether I have it in my contacts or if it has a certain carrier, etc) or even hide this option completely for some numbers.
What I'm asking for is a starting point since I couldn't find anything on developer.android.com nor on the internet.
Note: I don't want to reject or mute a call, no, I just want to modify the "Reject call with message" list on the fly depending on the caller or even disable it completely for some numbers.
Quick response messages can only be altered through Call settings UI as the values are stored in Phone app's shared preference, respond_via_sms_prefs.xml. See RespondViaSmsManager.java:
/** SharedPreferences file name for our persistent settings. */
private static final String SHARED_PREFERENCES_NAME = "respond_via_sms_prefs";
public void setInCallScreenInstance(InCallScreen inCallScreen) {
mInCallScreen = inCallScreen;
if (mInCallScreen != null) {
// Prefetch shared preferences to make the first canned response lookup faster
// (and to prevent StrictMode violation)
mInCallScreen.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
}
Only Phone app can read/write from/to the file.
Default values are set in respond_via_sms_settings.xml.
As far as I can tell, what you are trying to achieve is not feasible at this moment.