I've integrated Firebase into my app. I can authenticate with Firebase via email/password. Then I initialize the ChatSDK and call InterfaceManager.shared().a.startLoginActivity(this,true); From there, the app is "taken over" by the default chat user interface and the functionality works great and ChatSDK.currentUser() returns the expected User object.
I would like to do the same thing with my own UI. To authenticate a user after ChatSDK initialization, I've tried:
ChatSDK.auth().authenticateWithCachedToken();
ChatSDK.auth().authenticate(AccountDetails.signUp(email,pwd));
ChatSDK.auth().authenticate(AccountDetails.username(email,pwd));
It is my understanding that I wouldn't be able to do ChatSDK.thread().createThread(...) until I have a valid User. However, after each authentication attempt, ChatSDK.currentUser() is null.
Looking at the ChatSDK source code and documentation, it appears this is the mechanism for authentication. Is there something I'm missing?
Subscribe is necessary, even if you aren't using it.
ChatSDK.auth()
.authenticateWithCachedToken()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action() {
#Override
public void run() throws Exception {
Log.d("Success","We're in!");
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Log.d("Err",throwable.toString());
}
});
Also, here's some code to start a new chat thread with a known user id.
UserWrapper userWrapper = UserWrapper.initWithEntityId(firebaseUser.uid);
userWrapper.metaOn();
userWrapper.onlineOn();
User otherUser = userWrapper.getModel();
ProgressDialog pd = new ProgressDialog(MainActivity.this);
pd.show();
ChatSDK.thread().createThread("", otherUser, ChatSDK.currentUser())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(() -> {
pd.dismiss();
})
.subscribe(thread -> {
ChatSDK.ui().startChatActivityForID(getApplicationContext(), thread.getEntityID());
}, throwable -> {
ToastHelper.show(getApplicationContext(), throwable.getLocalizedMessage());
});
Related
Here's a real head-scratcher. I have a cloud function that uses https.onCall(). This function call is triggered from a password protected area on my Android application (I'm using Firestore as my server). In order to access this area, I force the user to re-enter their password which then calls FirebaseAuth.getInstance().getCurrentUser().reauthenticate().
I've run the code that calls the https.onCall() cloud function without reauthentication and the function retains the user's authentication credentials, so I've narrowed it down to something when we reauthenticate. Am I missing something? Do I have to do something to inform the cloud function of the authentication update?
The error message I was getting in the functions logs was:
"TypeError: Cannot read property 'uid' of unidentified object". Specifically this was happening when I was trying to get the uid of the authenticated user by the following:
const adminUID = context.auth.uid;
The assumption was that because I wasn't getting the above error, was because there was no authenticated data coming in via context.
To better clarify what's happening, here's my flow/process:
User selects "Account" from an options menu. This launches a dialogFragment that asks for the user's password:
Button code in dialogFragment:
loginButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View view) {
//execute the loginReAuth method
authenticateSession.loginAdminReAuth(adminEmailAddress, passwordField);
}
});
After the loginButton is selected and the user enter's their password, we are passing the data to the reauthenticate method:
Reauthenticate code:
public void loginAdminReAuth(final String email, TextView passwordText) {
user = FirebaseAuth.getInstance().getCurrentUser();
String password = passwordText.getText().toString();
AuthCredential credential = EmailAuthProvider.getCredential(email, password);
user.reauthenticate(credential)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Intent intent = new Intent(context, AccountSettingsActivity.class);
context.startActivity(intent);
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "re-authenticate:failure", task.getException());
Toast.makeText(MyApplication.getContext(), task.getException().getMessage(),
Toast.LENGTH_SHORT).show();
}
}
});
}
When the reauthentication is successful we run intent and take them to the AccountSettingsActivity. Inside this activity I have the method: deleteAccount() which is triggered by a buttonClick in an alertDialog confirming the action.
Here's the buttonClick that triggers the method:
#Override
public void onDialogOKPressed(DialogFragment dialog) {
dialog.dismiss();
if (buttonSelected.equals("accountUpdateButton")) {
//update code here.
}
else if (buttonSelected.equals("accountDeleteButton")) {
deleteAccount(admin);
}
}
Here's the deleteAccount() method that makes the cloud function request:
method that calls the cloud function:
private Task<String> deleteAccount(Admin selectedAdmin) {
Gson gson = new Gson();
String selectedAdminJson;
selectedAdminJson = gson.toJson(selectedAdmin);
Map<String, Object> data = new HashMap<>();
data.put("selectedAdminJson", selectedAdminJson);
return mFunctions
.getHttpsCallable("deleteAccount")
.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.
Log.d(TAG, "results from deleteAccount: " + task.getResult().getData().toString());
String result = (String) task.getResult().getData();
return result;
}
});
}
Finally, here's the cloud firestore code that executes the request.
Cloud function https.onCall():
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
import * as functions from 'firebase-functions';
// The Firebase Admin SDK to access the Firebase Realtime Database.
import * as admin from 'firebase-admin'
export = module.exports = functions.https
.onCall(async (data, context) => {
//selectedAdminJson received from app client and converted to admin object class
const selectedAdminJson = data.selectedAdminJson;
const adminUID = context.auth.uid;
//Error checking
// Checking attribute.
if (!(typeof selectedAdminJson === 'string') || selectedAdminJson.length === 0) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('invalid-argument', 'The function must be called with ' +
'one arguments "JSON object" containing selectedAdmin to add.');
}
// Checking that the user is authenticated OR if the calling adminID doesn't match the data received
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
try {
//METHODS EXECUTED HERE
} catch (error) {
console.error("Error removing adminUserGroup data: ", error);
}
// Returning result to the client.
return {
selectedAdminJson: "selectedAdminJson received and processing."
};
});
One more piece. This is part of my index.ts:
import * as admin from 'firebase-admin'
admin.initializeApp();
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'deleteAccount') {
exports.deleteAccount = require('./deleteAccount');
}
Okay, so I think I solved the problem. It looks like the issue was that I was logging the user out before the httpsCallable() could completely send the authentication information.
I was doing the following:
deleteAccount(firebaseLocalCache.thisAdminFromCache());
session.logoutUser();
Instead, I'll probably have to do some sort of continuation after the deleteAccount() method which returns a Task object. That way we logout the user ONLY after we get a response from the server.
I am trying firestore database on Android.
This is my code that inserts a document:
public Observable<Post> createPost(final Post post){
return Observable.create(new Observable.OnSubscribe<Post>() {
#Override
public void call(final Subscriber<? super Post> subscriber) {
try{
DocumentReference documentReference = getCollection().document();
post.setId(documentReference.getId());
documentReference.set(post).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
subscriber.onNext(post);
subscriber.onCompleted();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
subscriber.onError(e);
subscriber.onCompleted();
}
});
}catch (Exception ex){
subscriber.onError(ex);
Log.e(LOG_TAG, ex.getMessage(), ex);
}
}
});
}
The document gets inserted into the database but neither of the onSuccess nor the onFailure callbacks are invoked.
Update 1
The issue is not consistent sometimes it works, sometimes the callbacks are invoked after an hour, sometimes after 3 hours etc..
This is happening when there is no internet connection.
Update 2
The issue was reported here and it is closed. I am not sure how to guarantee the correctness of data created offline.
There does not seem to be anything wrong with your code, try perhaps the onCompleteListener callback. So add
.addOnCompleteListener((Task<Void> task) -> {
if(task.getException() != null) {
emitter.onError(task.getException());
}
if(task.isComplete()) { //try task.isSuccessful() if this is what you are looking for
emitter.onComplete();
}
});
If this does not solve your issue perhaps, use an emitter like so:
Completable completable$ = Completable.create((CompletableEmitter emitter) -> {
firebaseFirestore.collection(collection).document(document)
.delete()
.addOnSuccessListener((Void aVoid) -> emitter.onComplete())
.addOnFailureListener((Exception e) -> emitter.onError(e))
.addOnCompleteListener((Task<Void> task) -> {
if(task.getException() != null) {
emitter.onError(task.getException());
}
if(task.isComplete()) { //try task.isSuccessful()
emitter.onComplete();
}
});
});
return completable$;
Okay so I did a simple version of your question but instead of adding a post, it adds a User. The concept is the same.
Here is the method to add a user. It returns an Observable<DocumentReference> just to reference where the user was added.
public Observable<DocumentReference> insertToFirebaseFirestore$() {
UserEntity userEntity = new UserEntity();
userEntity.setEmail("myemail#myemail.com");
userEntity.setBio("I'm a cool cat!");
userEntity.setDisplayName("KoolKat!");
//Notice here I am using an ObservableEmitter instead of Subscriber like you did
return Observable.create((ObservableEmitter<DocumentReference> emitter) -> {
this.firebaseFirestore.collection("tempUsers")
.add(userEntity)
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
emitter.onError(e);
}
})
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
#Override
public void onSuccess(DocumentReference documentReference) {
//this gets triggered when I run
emitter.onNext(documentReference);
}
})
.addOnCompleteListener(new OnCompleteListener<DocumentReference>() {
#Override
public void onComplete(#NonNull Task<DocumentReference> task) {
//this also gets triggered when I run
emitter.onNext(task.getResult());
}
});
});
}
When I run this, and place breakpoints inside onSuccess and onComplete. Both of them are triggered and I can see the output.
I call the method from the Activity as follows.
...onCreate method
insertToFirebaseFirestore$()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io()) //observe on io thread cause I don't need it to updateUI.
.subscribe((DocumentReference val) ->{
Log.e("USERACTIVITY", "You have uploaded " + val.getId());
});
The LogcatPrints
12-13 09:47:47.942 15007-15059/com.example.debug E/USERACTIVITY: You have uploaded sFBsF4ZmwGaDdxCEKuF6
12-13 09:47:57.563 15007-15059/com.example.debug E/USERACTIVITY: You have uploaded sFBsF4ZmwGaDdxCEKuF6.
From what I have see with yours, perhaps use an emitter within
your Observable.create.
If that doesn't work try doing the firestore call without wrapping
it in an observable
If all else, might be a connection issue, since you say it happens
intermittently
I came across this with react native.
For inserts the key is to create a new document.
example:
const userRef = firebase.firestore()
.collection("users")
.doc();
userRef.set({name: someName});
This will create the document offline and sync when you come back online.
Further calls such as this will work offline
userRef.collection("Locations").add({location: "Austin,TX"});
When using the following pattern to synchronously get data from Firebase Realtime Database:
String s = Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
FirebaseDatabase.getInstance().getReference("path").orderByChild("child").equalTo("xyz").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
e.onSuccess("Got it");
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(databaseError.toException());
}
});
}
}).blockingGet();
It will hang and create an ANR error. If I use the same Firebase "innards" outside of the Single, it fires just fine. The Single without the Firebase code inside also will fire, so it seems there is some incompatibility between the two.
Any ideas?
Firebase delivers events on ui thread, waiting for result with blockingGet deadlocks it. In my opinion you should rethink app logic and subscribe without blocking with subscribe(SingleObserver)
Since you are creating your own Single, You should use DisposableSingleObserver in subscribeWith. Secondly, you shouldn't be calling blockingGet() like that. The reason is by default the Single or any observable/Processor/Flowable you create will be subscribed (run its operations on main thread) and observe on main thread. BlockingGet() causes the mainThread to pause. It's like executing Thread.sleep() on Main Thread. This always ends in a disaster.
The best option for you would be to rethink the logic you are trying to put in to the code. Since the Firebase operations are Async by nature, you should adapt your code to async pattern.
Anyways you can do something like the following to achieve what seems likes you might be trying to do. Note that I wrote the following code here so it might have syntactical errors.
Single.create(new SingleOnSubscribe<String>() {
// your firebase code
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
FirebaseDatabase.getInstance().getReference("path").orderByChild("child").equalTo("xyz").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
e.onSuccess("My String");
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(databaseError.toException());
}
});
}
})
.subscribeOn(Schedular.io())
.observeOn(AndroidThread.mainThread()) // if you aren't doing intensive/long running tasks on the data you got from firebase
.subscribeWith(new DisposableSingleObserver<String>() {
public void onSuccess(String myString) {
mMyString = myString;
}
public void onError(Throwable t) {
Timber.e("error in fetching data from firebase: %s", t);
}
});
We are trying to create a stripe token from a credit card on our android application, but when we call stripe.createToken it does nothing, it doesn't enter onSuccess nor onError methods in the listener. Our code is the following:
private void getStripeToken(Card card) {
Stripe stripe = null;
try {
stripe = new Stripe(getApplicationContext(), getString(R.string.stripe_public_key));
} catch (AuthenticationException e) {
e.printStackTrace();
return;
}
stripe.createToken(card, new TokenCallback() {
#Override
public void onError(Exception error) {
stripeError = error.getLocalizedMessage();
}
#Override
public void onSuccess(Token token) {
stripeToken = token;
}
});
}
When getStripeToken is finished, stripeError and stripeToken are null. Where are the mistake? Thanks
At the end of your getStripeToken method, the values are going to be unchanged because createToken is an asynchronous action -- that's why you have to give it a callback.
So, if you use createToken, you must be on the UI thread (because it uses a AsyncTask to make that call, and you should expect your values to be updated whenever the network call is done.
If you want the values to be updated at the end of your method call, use createTokenSynchronous, but be sure to only do so off the main thread.
I have been following https://developers.google.com/identity/smartlock-passwords/android/retrieve-credentials to try to automatically sign in a user if they have saved their credentials to the new Android Smart Lock feature in chrome. I have followed the guide exactly, but my callback that I pass into setResultCallback() is not getting called. Has anyone run into this problem before?
There is no error message or anything, it just doesn't get called.
The problem is likely that the Google API client is not connected, try calling connect() in the onStart() method of your activity, or if you are using a recent version of Play Services, we added automatic management of the API client to make this easier, really simplifying things and avoiding common problems.
Just call enableAutoManage() when building the GoogleApiClient:
// "this" is a reference to your activity
mCredentialsApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.enableAutoManage(this, this)
.addApi(Auth.CREDENTIALS_API)
.build();
Then you can make an API request without having to call mCredentialsApiClient.onConnect() at any point, the Google API client's lifecycle will be managed automatically for you. e.g.
#Override
public void onStart() {
CredentialRequest request = new CredentialRequest.Builder()
.setSupportsPasswordLogin(true)
.build();
Auth.CredentialsApi.request(mCredentialsApiClient, request).setResultCallback(
new ResultCallback<CredentialRequestResult>() {
public void onResult(CredentialRequestResult result) {
// result.getStatus(), result.getCredential() ... sign in automatically!
...
Check out a full sample app at on Github: https://github.com/googlesamples/android-credentials/blob/master/credentials-quickstart/app/src/main/java/com/google/example/credentialsbasic/MainActivity.java
I tired the official demo app here, and it worked.
Basically, the setResultCallback() will be get called when save, request and delete
For save:
Auth.CredentialsApi.save(mCredentialsApiClient, credential).setResultCallback(
new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.d(TAG, "SAVE: OK");
showToast("Credential Saved");
hideProgress();
} else {
resolveResult(status, RC_SAVE);
}
}
});
For request:
Auth.CredentialsApi.request(mCredentialsApiClient, request).setResultCallback(
new ResultCallback<CredentialRequestResult>() {
#Override
public void onResult(CredentialRequestResult credentialRequestResult) {
if (credentialRequestResult.getStatus().isSuccess()) {
// Successfully read the credential without any user interaction, this
// means there was only a single credential and the user has auto
// sign-in enabled.
processRetrievedCredential(credentialRequestResult.getCredential(), false);
hideProgress();
} else {
// Reading the credential requires a resolution, which means the user
// may be asked to pick among multiple credentials if they exist.
Status status = credentialRequestResult.getStatus();
if (status.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
// This is a "hint" credential, which will have an ID but not
// a password. This can be used to populate the username/email
// field of a sign-up form or to initialize other services.
resolveResult(status, RC_HINT);
} else {
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one
resolveResult(status, RC_READ);
}
}
}
});
For delete:
Auth.CredentialsApi.delete(mCredentialsApiClient, mCurrentCredential).setResultCallback(
new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
hideProgress();
if (status.isSuccess()) {
// Credential delete succeeded, disable the delete button because we
// cannot delete the same credential twice.
showToast("Credential Delete Success");
findViewById(R.id.button_delete_loaded_credential).setEnabled(false);
mCurrentCredential = null;
} else {
// Credential deletion either failed or was cancelled, this operation
// never gives a 'resolution' so we can display the failure message
// immediately.
Log.e(TAG, "Credential Delete: NOT OK");
showToast("Credential Delete Failed");
}
}
});
Also you can clone the project in my github here, set the SHA1 in your console here.
At this point you should be ready to go :)