Firebase persisted write failing after being offline - java

I have an Android application that uses Firebase as backend. Now that Firebase has support for local persistence I decided to get rid of my own local storage solution and use the built-in instead.
Everything is working, except for one thing. Making changes while being offline, closing the app, connect, and start the app.
The code that I use to update my data on the server is something like this:
public void saveDataToServer(String id, Boolean isHandled) {
Firebase firebase = new Firebase("https://example.firebaseio.com/item_data/items/1234/data").child(id);
if(firebase == null)
return;
firebase.authWithCustomToken(mToken, mAuthResultHandler);
Map<String, Object> children = new HashMap<>();
children.put("updated_at", FireBaseHelper.getFormattedTimestamp());
children.put("is_handled", isHandled);
firebase.updateChildren(children);
}
mMainFirebaseInstance is some Firebase object that is on the root level of where this data is saved. And this all runs in a Service that is connected to my Activities/Fragments
Sidenote:
I get the authentication token mToken from some REST API that someone else made for me to use.
When I am connected, have the app connected and make changes: everything works
When I am not connected, open the app, make changes, close the app, open the app and connect: everything works
When I am not connected, open the app, make changes, close the app, connect and open the app:
The following error is logged:
06-22 17:51:52.343 28073-28395/? W/RepoOperation﹕ Persisted write at /item_data/items/7454/data/7454141033945571998119 failed: FirebaseError: Invalid token in path
I've searched in Firebase's documentation and can't figure out where the problem is. I would say that this has something to do with the authentication, but I don't know anymore where to look.
So my question is: What is the problem here? What am I doing wrong?
EDIT:
The FireBaseHelper looks like this:
class FireBaseHelper {
public static Firebase getItemsBase(String itemId) {
Firebase fb = new Firebase(Constants.FIREBASE_URL + "item_data/items/" + itemId + "/data");
return fb;
}
public static String getFormattedTimestamp() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
return simpleDateFormat.format(Calendar.getInstance().getTime());
}
}
It can create the main Firebase instance and return a timestamp in a specific format. Constants.FIREBASE_URL is just https://example.firebaseio.com/
EDIT2:
mMainFirebaseInstance = FireBaseHelper.getItemsBase("1234");
which would be replaceable by
mMainFirebaseInstance = new Firebase("https://example.firebaseio.com/item_data/items/1234/data");
A possible timestamp is:
2015-06-22 23:12:24
The id that is used in the saveDataToServer is retrieved from a snapshot that is given to me in a ValueEventListener. For example:
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
HashMap data = dataSnapshot.getValue(HashMap.class);
Set keySet = data.keySet();
String id = keySet.get(0);
}
...
}
EDIT3:
Android: 5.0
Firebase: 2.3.0+
Java: 7

This turned out to be a bug in the Firebase Android SDK. It has been fixed in version 2.3.1 that was just released. See https://www.firebase.com/docs/android/changelog.html
I can reproduce the behavior you're seeing with this Activity.onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Firebase.setAndroidContext(this);
Firebase.getDefaultConfig().setPersistenceEnabled(true);
Firebase.getDefaultConfig().setLogLevel(Logger.Level.DEBUG);
Firebase ref = new Firebase("https://stackoverflow.firebaseio.com/30987733");
Map<String, Object> children = new HashMap<>();
children.put("updated_at", new Date().toString());
children.put("is_handled", true);
ref.updateChildren(children);
}
The steps to reproduce that go with the above code:
Go into airplane mode
Start the app (so that it calls updateChildren
Kill the app
Go online
Start the app
In my tests, the second update does get written to Firebase. But there is a warning written in the Android logs:
W/RepoOperation﹕ Persisted write at /30987733 failed: FirebaseError: Invalid token in path

Related

How to upload an object (hashMap with image and other data) into Firebase Realtime Database and Firebase Storage without internet connection?

Let me explain my situation. I have an Activity in Android Studio where I fill some stuff like; names, descriptions,... and an image. The data goes to the Firebase Realtime Database with the image reference from the Firebase Storage as well like this:
HashMap boulderHash = new HashMap();
boulderHash.put("boulderkey", latlong);
boulderHash.put("userid", currentUserID);
boulderHash.put("username", dataSnapshot.child("username").getValue().toString());
boulderHash.put("profileimage", downloadURL2);
...
final HashMap boulderImageHash = new HashMap();
boulderImageHash.put("images", downloadURL1);
boulderImageHash.put("profilestorage", storage);
boulderImageHash.put("counter", counter);
boulderImageHash.put("userid", currentUserID);
BoulderRef.child(latlong).updateChildren(boulderHash).addOnCompleteListener(new OnCompleteListener() {
#Override
public void onComplete(#NonNull final Task task) {
if (task.isSuccessful()) {
DatabaseReference key = BoulderRef.child(finalLatlong).child("BoulderImages").push();
imageKey = key.getKey();
BoulderRef.child(finalLatlong).child("BoulderImages").child(imageKey).updateChildren(boulderImageHash
All this happens when I click a button and if I have no internet connection I use the FirebaseDatabase.getInstance().setPersistenceEnabled(true); so the operation is waiting for connection to upload this. Everything is fine with this 'cause if I don't close the app eventually my phone gets the connection back and it uploads the data.
The problem comes when I close the app from the background, is there any option to save this data or the operation into localstorage or cache and automatically send this like it does without closing the app?

How do I authenticate with my Google Cloud Function when using my Firebase App with Google Sign In?

I am a newbie (6 months going or so) and creating an app on Android (Java) that utilizes FireBase Auth with Google Sign In. (with only a few days of NodeJS exposure now) In other words my end user signs in with the Google Account. That part (I think) works pretty well so far. I use the Firestore Database heavily for a lot of things in the app.
So now I've gotten to the point where I want to use (Callable) Cloud Functions with HTTP Triggers. (never having done any of this before) I'm trying to get a proof of concept working at this time. The actual function works and I can trigger it from my app.
It appears that I cannot figure out how to make the function "private" though; as in "adding proper Members" to the Cloud function who have the right to invoke the function.
I have tried a few different things by trial error, but first let me show what I have.
This is the Cloud Function and I'm passing in an arbitrary String as a test, works nicely: (as long as "allUsers" have the role/right to invoke the function; in other words when the function is public.
exports.createTest = functions.https.onCall((data, context) => {
const text = data.text;
const uid = context.auth.uid;
const name = context.auth.token.name || null;
const email = context.auth.token.email || null;
console.log('UID: ', uid);
console.log('Name: ', name);
console.log('Email: ', email);
console.log('Message: ', text);
});
The above function gets triggered in my Android/Java code like this: (I think this code came from Google Doc/Sample/Example
private FirebaseFunctions mFunctions;
...
private void testing() {
mFunctions = FirebaseFunctions.getInstance();
Log.e(LOG_TAG, "Testing executed!");
String testMessage = "Hello Hello Testing 123 Mic Check";
createTest(testMessage)
.addOnCompleteListener(new OnCompleteListener<String>() {
#Override
public void onComplete(#NonNull Task<String> task) {
if (!task.isSuccessful()) {
Exception e = task.getException();
if (e instanceof FirebaseFunctionsException) {
FirebaseFunctionsException ffe = (FirebaseFunctionsException) e;
FirebaseFunctionsException.Code code = ffe.getCode();
Object details = ffe.getDetails();
Log.e(LOG_TAG, "FFE: " + ffe.getMessage() );
Log.e(LOG_TAG, "Code: " + code);
Log.e(LOG_TAG, "Details:" + details);
}
// ...
}
// ...
}
});
}
private Task<String> createTest(String text) {
// Create the arguments to the callable function.
Map<String, Object> data = new HashMap<>();
data.put("text", text);
data.put("push", true);
return mFunctions
.getHttpsCallable("createTest") //this is the function name
.call(data)
.continueWith(new Continuation<HttpsCallableResult, String>() {
#Override
public String then(#NonNull Task<HttpsCallableResult> task) throws Exception {
// This continuation runs on either success or failure, but if the task
// has failed then getResult() will throw an Exception which will be
// propagated down.
String result = (String) task.getResult().getData();
if (result != null) {
Log.e(LOG_TAG, "Result: " + result);
}
return result;
}
});
}
Only when I have "allUsers" added with the role/right to "invoke Cloud Function" then I get this working. My understanding of HTTP Requests and such is pretty limited, which is not making things easier.
I tried using the "allAuthenticatedUsers" options, which I figured would do the trick, because I actually authenticate my Users in the app through Firebase/Google Sign In. This Cloud Function shall only be available to either a) authenticated users or b) users of a specific domain. (I have a domain, let's say #testorganization.com) Or if I can identify my particular app (api key?) then that would work, too.
The moment I add a member "allAuthenticatedUsers" with role to invoke the function (and remove "allUsers) nothing happens. I also tried adding the entire domain, but that wouldn't work. (duh) Also tried adding my service account (trial and error at this point) and didn't seem to work.
In my Node JS code I am actually receiving the UID of the authenticated user, so it appears that some kind of user authentication information is already being exchanged.
With that knowledge, I can (successfully tried this) get the UID and cross check that against my database and verify a user that way, but seems unnecessary and I should be able to make the permissions work. (lock the function down entirely) Plus this took a really long time just finish this cross check. Or is this pretty standard procedure to do?
Like this-->
const usersRef = admin.firestore().collection('users').doc(uid)
usersRef.get()
.then((docSnapshot) => {
if (docSnapshot.exists) {
usersRef.onSnapshot((doc) => {
console.log('User Type logged in: ', doc.data().userCategory)
console.log('User Title: ', doc.data().userTitle)
});
} else {
console.log('User does not exist')
}
});
Edit:
So while not having figured out how to shut down the function entirely, I did discover that instead of cross checking my users, I can simple check for auth like this:
if (context.auth){
//user is auth'd
} else {
//no auth
}
That's a little bit better, I guess. (but still doesn't technically prevent access to the function?!)
Thank you so much for any help. Much appreciated.
Edit2:
Here is a screensshot of the area in the cloud console (for cloud function roles/privileges) that I am referring to:
https://imgur.com/cBsjaaL
With a Callable Cloud Function, if you want to ensure that only authenticated users can trigger it's business logic, you actually don't need to configure any extra "cloud function roles/privileges" as shown at the bottom of your question.
By default, with a Callable Cloud Function you will get, when available, "Firebase Authentication and FCM tokens automatically included in requests" and it will "automatically deserializes the request body and validates auth tokens", as explained in the doc.
So you just have to follow the doc and use the context parameter. As you have mentioned in your question, you can check the user is authenticated by doing:
if (context.auth) {
//...
}
If you want to verify the user email, you would do:
exports.addMessage = functions.https.onCall((data, context) => {
const uid = context.auth.uid;
return admin.auth().getUser(uid)
.then(userRecord => {
const userEmail = userRecord.email;
//....
})
.catch(function(error) {
console.log('Error fetching user data:', error);
// Send back an error to the front end
// See https://firebase.google.com/docs/functions/callable#handle_errors
});
});
You will find more examples on how to "work with" users with the Admin SDK here in the doc.

Apply Deep linking in flutter app for android and ios

Apply Deep Linking in flutter app to open
a specific page in another app or same app
i want to know to how implement deep links in flutter
or open channel with android native and ios native ?
?
I think it would be the same as in a normal android app. Deeplinking is a configuration thing rather than code.
You need to write some things in your android manifest.
Have a look at the firebase manual for deep linking:
Firebase deeplinking manual
You can use firebase dynamic links for deep linking in a flutter. Refer this link for full implement steps and create and receive a link, https://medium.com/better-programming/deep-linking-in-flutter-with-firebase-dynamic-links-8a4b1981e1eb.
Here is a sample code for receiving a link inside the app and open a new screen.
class MainWidgetState extends State<MainWidget> {
#override
void initState() {
super.initState();
this.initDynamicLinks();
}
initDynamicLinks(BuildContext context) async {
await Future.delayed(Duration(seconds: 3));
var data = await FirebaseDynamicLinks.instance.getInitialLink();
var deepLink = data?.link;
final queryParams = deepLink.queryParameters;
if (queryParams.length > 0) {
var userName = queryParams['userId'];
openNewScreen(userName);
}
FirebaseDynamicLinks.instance.onLink(onSuccess: (dynamicLink)
async {
var deepLink = dynamicLink?.link;
final queryParams = deepLink.queryParameters;
if (queryParams.length > 0) {
var userName = queryParams['userId'];
openNewScreen(userName);
}
debugPrint('DynamicLinks onLink $deepLink');
}, onError: (e) async {
debugPrint('DynamicLinks onError $e');
});
}
openNewScreen(String userName){
Navigator.of(context).pushNamed("routeFormScreen", arguments: {"name": userName});
}
}

I get chatId in tdlib but i can not get message (tdAPI or telegram database library for java)

I am trying to use telegram database library for java (tdlib or tdapi) but when i get chatId of a channel by SearchPublicChat and try to get messages or view messages i get an error.
Error{code=6 message= Chat not found}
I can not understand why the chatId i receive above why when i pass it to another method i get that error
Please help me about that problem and that library.
Thank you.
example for getting last 15 messages from chat
String username = "any_chat_public_link";
TdApi.SearchPublicChat searchPublicChat=new TdApi.SearchPublicChat(username);
TG.getClientInstance().send(searchPublicChat, new Client.ResultHandler() {
#Override
public void onResult(TdApi.TLObject object) {
TdApi.Chat chat = (TdApi.Chat) object;
TdApi.Message topMessage = chat.topMessage;
long chatId = chat.id;
TdApi.GetChatHistory getChatHistory = new TdApi.GetChatHistory(chatId, topMessage.id, 0, 15);
TG.getClientInstance().send(getChatHistory, new Client.ResultHandler() {
#Override
public void onResult(TdApi.TLObject object) {
TdApi.Messages messages = (TdApi.Messages) object;
}
});
}
});
Before requesting chat by id the TdLib must know about this chat in current session. You need search this chat by #mention_link if it public, or getting whole your chat list. Also, the library will be know about chat if some action happens with this chat (like new message from chat, chat updated...)
And this applies also to messages, users and etc. You can request it by id only when TdLib know about this entity.
Before receiving the message history, you need to subscribe to the chat by sending TdApi.JoinChat. The procedure is as follows:
1) TdApi.SearchPublicChat
2) TdApi.JoinChat
3) TdApi.GetChatHistory
TdApi.GetChatHistory requires the id of the last chat message. It can be obtained using the TdApi.GetChat method.
I used tdlib/example. Information about chats is updated automatically by the getMainChatList method, then it can be obtained from chats.get(chatId)

Java - How to Retrieve and use a single value from azure mobile services in Android

I am new to azure but i know certain things like how to retrieve and store data to azure , i followed azure official documentation for this purpose.
Link is Here - https://azure.microsoft.com/en-in/documentation/articles/mobile-services-android-get-started-data/
But the problem is, this tutorial is only showing How to retrieve and use Data from azure using Adapters and Lists . I want to know , How can i retrieve a single value from azure mobile services and how to use it in android.
Plzz provide me both backend code (if there is any) and java code for this . THANKS in advance
I got it solved. No need to create a custom API.
Just follow the basics , Here is the code :-
final String[] design = new String[1];
private MobileServiceTable<User> mUser;
mUser = mClient.getTable(User.class);
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
try {
final MobileServiceList<User> result =
mUser.where().field("name").eq(x).execute().get();
for (User item : result) {
// Log.i(TAG, "Read object with ID " + item.id);
desig[0] = item.getDesignation(); //getDesignation() is a function in User class ie- they are getters and setters
Log.v("FINALLY DESIGNATION IS", desig[0]);
}
} catch (Exception exception) {
exception.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
designation.setText(desig[0]);
}
}.execute();
DON'T forget to create a class User for serialization and all. Also you should define the array .
FEEL FREE to write if you got it not working.
EDIT :-
design[0] is an array with size 1.
eq(x) is equal to x where , x variable contains username for which i want designation from database (azure).
You can do this with a custom API. See this link: https://azure.microsoft.com/en-us/documentation/articles/mobile-services-how-to-use-server-scripts/#custom-api
Code looks like this:
exports.post = function(request, response) {
response.send(200, "{ message: 'Hello, world!' }");
}
It's then reachable at https://todolist.azure-mobile.net/api/APIFILENAME.
If you want to access a table you can do something like:
exports.post = function(request, response) {
var userTable = tables.getTable('users');
permissionsTable
.where({ userId: user.userId})
.read({ success: sendUser });
}
function sendUser(results){
if(results.length <= 0) {
res.send(200, {});
} else {
res.send(200, {result: results[0]});
}
}
You can then follow the instructions for using the API on your Android client here: https://azure.microsoft.com/en-us/documentation/articles/mobile-services-android-call-custom-api/
How your app is written will change how this code works/looks, but it looks something like:
ListenableFuture<MarkAllResult> result = mClient.invokeApi( "UsersAPI", MarkAllResult.class );
That invokes the API. You need to write the class and Future to handle the results. The above page explains this in great detail.
The most optimal solution would be to create an api on your server which accepts an ID to return an single object/tablerow.
In your android app, you only have to call:
MobileServiceTable<YourClass> mYourTable;
mClient = new MobileServiceClient(
"https://yoursite.azurewebsites.net/",
mContext);
mYourTable = mClient.getTable(YourClass.class);
YourClass request = mYourTable.lookUp(someId).get();
// request -> https://yoursite.azurewebsites.net/tables/yourclass/someId
YourClass should have the same properties as the object on the server.

Categories