I've integrated the Smooch/Sunshine Conversations SDK into our app.
On the most part, it works. However I've got a bit of an issue in a failure scenario:
User is logged in (both to our service, and smooch)
Our serverside dies for whatever reason, meaning JWT temporarily can't be fetched
Conversation view shows "Cannot connect to server" (as expected)
Our serverside recovers ... valid JWT's returned on request
User tries to trigger a conversation in the app, and they continue to see "Cannot connect to server" indefinitely (even after moving back from the conversation activity and back into it).
The Smooch SDK never recovers from this. The only way to solve it is to kill and restart the app.
I'm using the latest SDK version 7.0.3, and the vanilla ConversationActivity (I've not subclassed this or anything)
I've tried the following:
Re initialising Smooch immediately before moving into the ConversationActivity
Calling login immediately before moving into the ConversationActivity
Any ideas?
Code:
// This is in the Application class, as recommended
fun initialiseSmooch(application: Application) {
GlobalScope.launch {
Log.i(TAG, "Initialising Smooch")
val settings = Settings("INTEGRATION_ID")
settings.authenticationDelegate = getAuthenticationDelegate()
Smooch.init(application, settings, getInitialisationCallback())
}
}
private fun getInitialisationCallback(): (SmoochCallback.Response<InitializationStatus>) -> Unit {
return { response ->
if (response.data === InitializationStatus.SUCCESS) {
Log.i(TAG, "Smooch initialised successfully")
} else {
Log.e(TAG, "Smooch initialization failed: ${response.error}")
}
}
}
/**
* This basically tells the Smooch SDK what to do if the JWT is rejected. Basically it goes
* and fetches a new token from our API.
*/
private fun getAuthenticationDelegate(): AuthenticationDelegate {
return AuthenticationDelegate(function = { authenticationError, authenticationCallback ->
if (authenticationError != null && authenticationError.data != null) {
Log.w(TAG, authenticationError.data)
}
if(AppResources.repository.getUserId() == null){
Log.i(TAG, "Authentication error. User isn't logged in, so shouldn't be logged in to Smooch either.")
logoutSmoochUser()
} else {
Log.i(TAG, "Authentication error. Getting new Smooch token.")
getSmoochToken { token -> authenticationCallback.updateToken(token) }
}
})
}
private fun getSmoochToken(callback: (String) -> Unit) {
// Fetches token from API. If successful, callback is called
// If unsuccessful, callback isn't called. This won't hang forever, it has a timeout.
}
// And to start the conversation
private fun proceedToConversation() {
ConversationActivity.builder().show(this)
}
Please ensure you've implemented authentication delegates to automatically handle cases where JWTs are expired: https://docs.smooch.io/guide/authenticating-users/#expiring-jwts-on-sdks
Once your backend issuing the JWTs comes back up, I'd start recovery attempts with a login() call.
Finally, 10s JWT expiry is very short indeed.
I have the below defined:
I first launch the app with the device's language (from Settings) set to English and it loads the lang-EN json.
If I go in the Settings of the device, change the language to French and restart the app, .fetch() will still return the lang-EN value. If I clear cache the app and restart it, it then loads the lang-FR value.
Doesn't .fetch() get ALL the remote config params and locally decide which to show the user? From the functionality i'm getting it seems that .fetch() only gets the parameter values only pertaining to the device at the moment of the call. Either that or there's a bug in the firebase code ... OR ... and i'm hoping this is the case, i'm doing something wrong.
This is how i'm initializing the singleton. Perhaps i'm missing something?
public void initialize(Context applicationContext) {
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
.setMinimumFetchIntervalInSeconds(5 * 60 * 60)//5 hours
.build();
getConfig().setConfigSettingsAsync(configSettings);
}
And yes, i have the latest version of the remote config sdk in the dependencies 19.2.0
fetch() is not enough.
To fetch parameter values from the Remote Config backend, call the fetch() method. Any values that you set in the backend are fetched and stored in the Remote Config object.
To make fetched parameter values available to your app, call the activate() method.
For cases where you want to fetch and activate values in one call, you can use a fetchAndActivate() request to fetch values from the Remote Config backend and make them available to the app:
mFirebaseRemoteConfig.fetchAndActivate()
.addOnCompleteListener(this, new OnCompleteListener<Boolean>() {
#Override
public void onComplete(#NonNull Task<Boolean> task) {
if (task.isSuccessful()) {
boolean updated = task.getResult();
Log.d(TAG, "Config params updated: " + updated);
Toast.makeText(MainActivity.this, "Fetch and activate succeeded",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "Fetch failed",
Toast.LENGTH_SHORT).show();
}
displayWelcomeMessage();
}
});
Because these updated parameter values affect the behavior and appearance of your app, you should activate the fetched values at a time that ensures a smooth experience for your user, such as the next time that the user opens your app
I'm trying to have a remote config parameter using the new Remote Config feature of Firebase, and I'm having an issue.
Here's my Remote Config console:
I'm doing a fetch and update in my Application's onCreate():
final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance();
remoteConfig.fetch().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
remoteConfig.activateFetched();
}
}
});
And here's how I'm reading it:
FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance();
String value = remoteConfig.getString("active_subscriptions");
Value is returning null.
If I call remoteConfig.getInfo().getLastFetchStatus(), it returns LAST_FETCH_STATUS_SUCCESS, so it seems the fetch is going through successfully.
Any idea why my value is blank?
Workaround found! See below
I'm running into the "silent completion" thing - I call "fetch" but onComplete, onSuccess, or onFailure listeners never fire. I tried moving it to an activity onCreate, and still nothing happened, and therefore, the config items never get loaded from the server. I've got Developer Mode enabled, and am calling fetch with a cache value of 0.
I was able to (once) put a breakpoint on the line "public void onComplete(#NonNull Task task) {", which got hit, and then I was able to step through and the onComplete fired. I was then unable to reproduce this same result any other way, including doing the same thing (I think) a second time.
Seems like a timing or concurrency issue, but that makes little sense, given this is an asynchronous call.
Workaround
If you fetch from Activity#onResume (or, I presume, Activity#onStart), it works perfectly. Calling fetch from Activity#onCreate or Application#onCreate results in a call that seemingly never gets handled, and in fact, performance of the app degrades noticeably after the fetch begins, so I think there's a looper running or something.*
Workaround #2
If you really want this to run from Application#onCreate (which I do), this seems to work as well:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// Run mFirebaseRemoteConfig.fetch(timeout) here, and it works
}
}, 0);
You're likely hitting the caching in Remote Config. The way it works is that Config will cache incoming items locally, and return them. So your last (cached) fetch status was probably before the value was defined, and we get a cached blank value.
You can control the cache expiry, but if you fetch too often you risk getting throttled.
Because this is a common development problem though, there is a developer mode that lets you request more rapidly (for small groups of users):
FirebaseRemoteConfigSettings configSettings =
new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(BuildConfig.DEBUG)
.build();
FirebaseRemoteConfig.getInstance().setConfigSettings(configSettings);
When you call fetch you can then pass a short cache expiration time
long cacheExpiration = 3600;
FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
cacheExpiration = 0;
}
mFirebaseRemoteConfig.fetch(cacheExpiration)
.addOnCompleteListener(new OnCompleteListener<Void>() {
// ...
});
That's how its done in the quickstart sample if you want a full reference.
Found the problem.
After adding some logging, I found that the fetch job's onComplete() was never being called. I moved the fetch from my Application's onCreate to a fragment's, and now it works properly!
(Ian Barber, this might be something to look into or clarify, as the logs indicated that Firebase was initialized without an issue when it was in the Application, and the fetches were silent failures.)
I also encountered this problem. Turns out I hadn't seen the 'Publish' button in the the Firebase console. :facepalm:
I had the same problem and no workarounds were helpful in my case. The problem was in the testing device. I used emulator without installing Google Mobile Services, because of this the Complete event was not fired. I tried my phone with GMS and everything worked great. Good luck.
First thing in such case is check if you have the correct firebase config and you are connected to firebase .If you have android studio 2.2 got to Tools->Firebase->RemoteConfig - Connect to Firebase and see if you get a notification saying connected.Once Connected do the following in your code:
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
/** NOTE: At this point, your app can use in-app default parameter values.To use in-app
* default values,skip the next section. You can deploy your app without setting
* parameter values on the server,and then later set values on the server to
* override the default behavior and appearance of your app.
*/
mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(true)
.build();
mFirebaseRemoteConfig.setConfigSettings(configSettings);
And then for fetching config do the following
long cacheExpiration = 2000; // Can increase this usually 12hrs is what is recommended
/** If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
* the server.*/
if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
cacheExpiration = 0;
}
/** cacheExpirationSeconds is set to cacheExpiration here, indicating that any previously
* fetched and cached config would be considered expired because it would have been fetched
* more than cacheExpiration seconds ago. Thus the next fetch would go to the server unless
* throttling is in progress. The default expiration duration is 43200 (12 hours).
*/
mFirebaseRemoteConfig.fetch(cacheExpiration)//TODO Bring this from a config file
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "Firebase Remote config Fetch Succeeded");
// Once the config is successfully fetched it must be activated before newly fetched
// values are returned.
mFirebaseRemoteConfig.activateFetched();
} else {
Log.d(TAG, "Firebase Remote config Fetch failed");
}
showRemoteConfig();
}
});
Run your App and check in logs " Firebase Remote config Fetch Succeeded ". If you see the same your remote configs are loaded and activated.
I've used a similar code like #Ian Barber (copy):
FirebaseRemoteConfigSettings configSettings =
new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(BuildConfig.DEBUG)
.build();
FirebaseRemoteConfig.getInstance().setConfigSettings(configSettings);
My problem was the "BuildConfig.DEBUG", it returns false. So it takes the value 1h in cache until it was fetched again!
I had a problem that Firebase Remote Config didn't fire OnCompleteListener with fetch(0), but with fetch() did.
Looking at FirebaseRemoteConfig.fetch() does not trigger OnCompleteListener every time, I found that the first answer was working sometimes even with fetch(0). Then I again set 3600 seconds for interval, as errors continued to appear:
override fun onPostResume() {
super.onPostResume()
// Initialize FirebaseRemoteConfig here.
...
firebaseRemoteConfig.fetch(3600).addOnCompleteListener { task ->
if (task.isSuccessful) {
firebaseRemoteConfig.activateFetched()
//calling function to check if new version is available or not
checkForUpdate(currentVersionCode, firebaseRemoteConfig.getString(VERSION_CODE_KEY))
} else
Toast.makeText(this#MainActivity, "Someting went wrong please try again",
Toast.LENGTH_SHORT).show()
}
}
Well in my case, I am able to receive control in addOnCompleteListener for fetch method but I have fetched values firebaseRemoteConfig just after I called firebaseRemoteConfig.activate(), so when I have tried to get the values from firebaseRemoteConfig it returns me previously saved values because firebaseRemoteConfig.activate() runs asynchronously and new values didn't saved before I am getting them from firebaseRemoteConfig, so I have added complete listener for activate() method also, Here:
firebaseRemoteConfig.fetch()
.addOnCompleteListener(activity, OnCompleteListener {
if (it.isSuccessful)
{
Log.d("task","success")
firebaseRemoteConfig.activate().addOnCompleteListener { // here I have added a listener
val base_url=firebaseRemoteConfig.getString("base_url")
Log.d("base url",base_url)
Toast.makeText(activity, "Base url: $base_url",Toast.LENGTH_SHORT).show()
}
}
else
{
Log.d("task","failure")
}
})
Does Android Studio's Build Variant match your intended Firebase project?
I work on a big project and the problem was buried in an unexpected place.
Long story short: the firebase application id(normally set through google-services.json) was changed through code:
FirebaseOptions.Builder builder = new FirebaseOptions.Builder();
builder.setApplicationId(applicationId);
builder.setApiKey(apiKey);
FirebaseOptions options = builder.build();
FirebaseApp.initializeApp(context, options);
The solution was to remove that code and let firebase use the info from "google-services.json".
Use fetchAndActivate instead of fetch
I was facing the same problem. After fetching, no listener get call for first time only. I try fetchAndActivate in single line and it works for me. Use below code
mFirebaseRemoteConfig.fetchAndActivate()
.addOnCompleteListener(this, new OnCompleteListener<Boolean>() {
#Override
public void onComplete(#NonNull Task<Boolean> task) {
if (task.isSuccessful()) {
boolean updated = task.getResult();
Log.d(TAG, "Config params updated: " + updated);
Toast.makeText(MainActivity.this, "Fetch and activate succeeded",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "Fetch failed",
Toast.LENGTH_SHORT).show();
}
displayWelcomeMessage();
}
});
It will fetch and activate immediately. You can find this way in official documentation here
I'm starting to use Backendless.com mBaaS on Android
I sign in user via Google and I got token and everything is OK, but the logged in user isn't created an Users table, so I can not use it to store user specific data.
So I tried to combine user login from here with user creation from documentation:
if (result.isSuccess()) {
logined = true;
loginInBackendless(result.getSignInAccount());
BackendlessUser user = new BackendlessUser();
user.setProperty("email", result.getSignInAccount().getEmail().toString());
user.setProperty("name", result.getSignInAccount().getDisplayName().toString());
user.setPassword(UUID.randomUUID().toString());
Backendless.UserService.register(user, new AsyncCallback<BackendlessUser>() {
public void handleResponse(BackendlessUser registeredUser) {
// user has been registered and now can login
}
public void handleFault(BackendlessFault fault) {
// an error has occurred, the error code can be retrieved with fault.getCode()
}
});
the question is:
Is it right way to create user? it seems not OK, because every time google user is logged in, a new Backendless user is created (or his record in Users table is updated).
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 :)