I'm trying to build an app with biometric authentication (Fingerprint) and I'm having some troubles with the negative button.
The button works, but for some reason is completely invisible.
This is how the app shows
And this is how it sees when you prees the button. As you can see, it exist, but i don't know how to make it visible
I'm using BiometricPrompt and BiometricManager in Java.
Edit: It seems that the button shows normally in any other phone that isn't mine
I'm using a Xiaomi Redmi Note 8.
However this is the code that I'm using:
private void initViews()
{
biometricManager = BiometricManager.from(this);
passwordEditText=findViewById(R.id.passwordText);
loginButton=findViewById(R.id.loginButton);
switch (biometricManager.canAuthenticate()) {
case BiometricManager.BIOMETRIC_SUCCESS:
Log.d("MY_APP_TAG", "App can authenticate using biometrics.");
break;
case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
Log.e("MY_APP_TAG", "No biometric features available on this device.");
break;
case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
Log.e("MY_APP_TAG", "Biometric features are currently unavailable.");
break;
case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
Log.e("MY_APP_TAG", "The user hasn't associated " +
"any biometric credentials with their account.");
break;
}
executor = ContextCompat.getMainExecutor(this);
biometricPrompt = new BiometricPrompt(EnterYourPassActivity.this,
executor, new BiometricPrompt.AuthenticationCallback() {
#Override
public void onAuthenticationError(int errorCode,
#NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
if(errString.equals("Use account password"))
{
passwordEditText.setVisibility(View.VISIBLE);
}
else
{
Log.d("MY_APP_TAG",""+errString);
Toast.makeText(getApplicationContext(),
"Authentication error: " + errString, Toast.LENGTH_SHORT)
.show();
}
}
#Override
public void onAuthenticationSucceeded(
#NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
Toast.makeText(getApplicationContext(),
"Authentication succeeded!", Toast.LENGTH_SHORT).show();
Intent seeingFiles = new Intent(EnterYourPassActivity.this, SeeingFilesActivity.class);
startActivity(seeingFiles);
}
#Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Toast.makeText(getApplicationContext(), "Authentication failed",
Toast.LENGTH_SHORT)
.show();
}
});
promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("Biometric login for my app")
.setSubtitle("Log in using your biometric credential")
.setNegativeButtonText("Use account password")
.build();
loginButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
biometricPrompt.authenticate(promptInfo);
}
});
}
Try to change negativeButton text in this way:
Important - put string into resources:
<string name="negative_button_text"><![CDATA[<font color=\'#48a134\'>Your text at given color</font>]]></string>
As you can see, you can set text color in hex.
Now put negativeText into BiometricPrompt as follows:
val negativeButtonText = getString(R.string.negative_button_text)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("title")
.setDescription("description")
.setNegativeButtonText(fromHtml(negativeButtonText))
.build()
fun fromHtml(html: String): Spanned {
return HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
In given example negativeText is green and prompt looks like THIS.
Related
I am working on a simple app that should work like uber, I want each driver to upload the image of his/her car while setting up his/her profile. I have successfully picked the two images and was able to save them to the firebaseStorage, I want to retrieve the link to the downloaded car Image and save it to the driver database reference along with his profile picture and other information but I am unable to do that. I create a method that returns a String so that I can use the String returned by that method as the link to the car Image but it's showing null in my database.
The method that should return the link to the downloaded car image:
private Uri profileImageUri, carImageUri;
private StorageReference storageProfilePicsRef, storageCarImageRef;
private StorageTask uploadProfileTask, uploadCarImageTask;
private String myProfileImaeUri = "", myCarImageUri = "";
private String uploadCarImage() {
if (carImageUri != null) {
final StorageReference storageReference = storageCarImageRef.child(mAuth.getCurrentUser().getUid() + ".jpg");
uploadCarImageTask = storageReference.putFile(carImageUri);
uploadCarImageTask.continueWithTask(new Continuation() {
#Override
public Object then(#NonNull Task task) throws Exception {
if (!task.isSuccessful()) {
throw task.getException();
}
return storageReference.getDownloadUrl();
}
}).addOnCompleteListener(new OnCompleteListener<Uri>() {
#Override
public void onComplete(#NonNull Task<Uri> task) {
if (task.isSuccessful()) {
Uri downloadCarUri = task.getResult();
myCarImageUri = downloadCarUri.toString();
}
}
});
} else {
Toast.makeText(SettingsActivity.this, "Car Image Not Selected", Toast.LENGTH_SHORT).show();
}
return myCarImageUri;
}
This is the method that upload both the profile image and other information of the driver:
private void uploadProfilePicture() {
String carImageUriFromTheMethod = uploadCarImage();//I was trying to assigned the method that returns the carImage link as a String to this String type variable so that I can use it
final ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setTitle("Updating Profile");
progressDialog.setMessage("Please wait while we are updating your account information");
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.show();
if (profileImageUri != null) {
final StorageReference fileRef = storageProfilePicsRef.child(mAuth.getCurrentUser().getUid() + ".jpg");
uploadProfileTask = fileRef.putFile(profileImageUri);
uploadProfileTask.continueWithTask(new Continuation() {
#Override
public Object then(#NonNull Task task) throws Exception {
if (!task.isSuccessful()) {
throw task.getException();
}
return fileRef.getDownloadUrl();
}
}).addOnCompleteListener(new OnCompleteListener<Uri>() {
#Override
public void onComplete(#NonNull Task<Uri> task) {
if (task.isSuccessful()) {
Uri downloadUri = task.getResult();
myProfileImaeUri = downloadUri.toString();
int selectedID = radioGroup.getCheckedRadioButtonId();
male = (RadioButton)findViewById(selectedID);
female = (RadioButton) findViewById(selectedID);
if (male.isChecked()){
selectedGender = male.getText().toString();
}else if (female.isChecked()){
selectedGender = female.getText().toString();
}else {
selectedGender = "Not specify";
}
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("ProfileImage", myProfileImaeUri);
hashMap.put("name", name.getText().toString());
hashMap.put("city", city.getText().toString());
hashMap.put("age", age.getText().toString());
hashMap.put("maritalStatus", maritalStatus.getText().toString());
hashMap.put("phoneNumber", phoneNumber.getText().toString());
hashMap.put("country", country.getText().toString());
hashMap.put("address", address.getText().toString());
hashMap.put("gender", selectedGender);
hashMap.put("carName",carName.getText().toString());
hashMap.put("carColour", carColor.getText().toString());
hashMap.put("carPlateNumber", carPlateNumber.getText().toString());
hashMap.put("carImage", carImageUriFromTheMethod);//I now call the string here
}
databaseReference.child(mAuth.getCurrentUser().getUid()).updateChildren(hashMap);
progressDialog.dismiss();
if (getType.equalsIgnoreCase("Driver")) {
startActivity(new Intent(SettingsActivity.this, DriversMapsActivity.class));
Toast.makeText(SettingsActivity.this, "Profile Information Updated Successfully", Toast.LENGTH_SHORT).show();
finish();
} else {
startActivity(new Intent(SettingsActivity.this, CustomerMapsActivity.class));
Toast.makeText(SettingsActivity.this, "Profile Information Updated Successfully", Toast.LENGTH_SHORT).show();
finish();
}
} else {
Toast.makeText(SettingsActivity.this, "Error Occured", Toast.LENGTH_SHORT).show();
progressDialog.dismiss();
}
}
});
} else {
Toast.makeText(SettingsActivity.this, "Profile Image Not Selected", Toast.LENGTH_SHORT).show();
}
}
Here is my database which shows that the carImage is null, but there is a car Image in the firebaseStorage:
"Driver": {
"2oKrtyUa3FX1wyHAhplsxbVaPZU2": {
"ProfileImage": "https://firebasestorage.googleapis.com/v0/b/ride-booking-app-e8a95.appspot.com/o/Profile%20Images%2F2oKrtyUa3FX1wyHAhplsxbVaPZU2.jpg?alt=media&token=442b14b3-ffb8-4356-b0c0-4c77021fe391",
"address": "Ikot Obong",
"age": "28",
"carColour": "black",
"carImage": "",//this is where the link supposed to appear as it's seen in the profile image
"carName": "Range Rover sport",
"carPlateNumber": "aa377-ktm",
"city": "Ikot Abasi",
"country": "Nigeria",
"gender": "Male",
"maritalStatus": "single",
"name": "Captain Elijah",
"phoneNumber": "08168989070"
}
}
Thanks for help
All the operations you are doing (uploading the image, getting its download URL, and writing to the database) are asynchronous, which your code ignores.
If you log the myCarImageUri in uploadCarImage right before you return it, you'll see it's null, which is the case because your onComplete for the upload hasn't run yet (another log would show that too).
The correct way to handle the uploads is as you already do for the myProfileImaeUri in your uploadProfilePicture method: nesting the callbacks, so that they execute in the correct order. You'll need to do the same for the car image.
To learn more about this problem, I recommend checking out some of the top answers on the asynchronous behavior of getDownloadUrl(), such as my answers to:
Can someone help me with logic of the firebase on success listener
How do I get files synchronized from Firebase in Android?
Why can't this retieve the download URL of an image on Firebase Storage?
I'd also recommend reading Doug's excellent series of blog posts on becoming a Firebase task master.
I've followed the instructions exactly, in fact, I even used the code from the Firebase helper in android studio. My issue is as such, nor login or failure to login is occurring with my code! What am I missing?
public void toSubscribe(View v) {
Log.d("OK", "this part first");
String strUsername, strPassword;
strUsername = ((EditText) findViewById(R.id.username)).getText().toString();
strPassword = ((EditText) findViewById(R.id.password)).getText().toString();
if (strUsername.matches("")) {
Toast.makeText(MainActivity.this, "You did not enter a username.", Toast.LENGTH_SHORT).show();
return;
}
if (strPassword.matches("")) {
Toast.makeText(MainActivity.this, "You did not enter a password.", Toast.LENGTH_SHORT).show();
return;
}
mAuth.signInWithEmailAndPassword(strUsername, strPassword)
.addOnCompleteListener(MainActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
Log.d("TAG", "signInWithEmail:onComplete:" + task.isSuccessful());
// If sign in fails, display a message to the user. If sign in succeeds
// the auth state listener will be notified and logic to handle the
// signed in user can be handled in the listener.
if (!task.isSuccessful()) {
Log.w("TAG", "signInWithEmail", task.getException());
Toast.makeText(MainActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
}
}
});
}
Not much else to say... I tried using a new JSON file and it still doesn't work. I've Googled tons of stuff but nothing really worked.
please check the following:
1-you install and add Gson file correctly from firebase console
2- you enable firebase authentication from firebase console
3- you mAuth initialized correctly
I create sandbox account in paytm.I am using github code and pass merchant id and all parameter to paytm.please check my below code
public void onStartTransaction(View view) {
PaytmPGService Service = PaytmPGService.getStagingService();
Map<String, String> paramMap = new HashMap<String, String>();
// these are mandatory parameters
paramMap.put("ORDER_ID", ((EditText) findViewById(R.id.order_id)).getText().toString());
paramMap.put("MID", ((EditText) findViewById(R.id.merchant_id)).getText().toString());
paramMap.put("CUST_ID", ((EditText) findViewById(R.id.customer_id)).getText().toString());
paramMap.put("CHANNEL_ID", ((EditText) findViewById(R.id.channel_id)).getText().toString());
paramMap.put("INDUSTRY_TYPE_ID", ((EditText) findViewById(R.id.industry_type_id)).getText().toString());
paramMap.put("WEBSITE", ((EditText) findViewById(R.id.website)).getText().toString());
paramMap.put("TXN_AMOUNT", ((EditText) findViewById(R.id.transaction_amount)).getText().toString());
paramMap.put("THEME", ((EditText) findViewById(R.id.theme)).getText().toString());
paramMap.put("EMAIL", ((EditText) findViewById(R.id.cust_email_id)).getText().toString());
paramMap.put("MOBILE_NO", ((EditText) findViewById(R.id.cust_mobile_no)).getText().toString());
PaytmOrder Order = new PaytmOrder(paramMap);
PaytmMerchant Merchant = new PaytmMerchant(
"https://pguat.paytm.com/paytmchecksum/paytmCheckSumGenerator.jsp",
"https://pguat.paytm.com/paytmchecksum/paytmCheckSumVerify.jsp");
Service.initialize(Order, Merchant, null);
Service.startPaymentTransaction(this, true, true,
new PaytmPaymentTransactionCallback() {
#Override
public void someUIErrorOccurred(String inErrorMessage) {
Toast.makeText(getApplicationContext(), "Ui/Webview error occured.", Toast.LENGTH_LONG).show();
}
#Override
public void onTransactionSuccess(Bundle inResponse) {
Log.d("LOG", "Payment Transaction is successful " + inResponse);
Toast.makeText(getApplicationContext(), "Payment Transaction is successful ", Toast.LENGTH_LONG).show();
}
#Override
public void onTransactionFailure(String inErrorMessage,
Bundle inResponse) {
Log.d("LOG", "Payment Transaction Failed " + inErrorMessage);
Toast.makeText(getBaseContext(), "Payment Transaction Failed ", Toast.LENGTH_LONG).show();
recreate();
}
#Override
public void networkNotAvailable() {
Toast.makeText(getBaseContext(), "No Internet connection.", Toast.LENGTH_LONG).show();
}
#Override
public void clientAuthenticationFailed(String inErrorMessage) {
Toast.makeText(getBaseContext(), "Client Authentication Failed.", Toast.LENGTH_LONG).show();
}
#Override
public void onErrorLoadingWebPage(int iniErrorCode,
String inErrorMessage, String inFailingUrl) {
}
#Override
public void onBackPressedCancelTransaction() {
// TODO Auto-generated method stub
}
});
}
below attachment I pass parameter to PaytmPGService.please check attachment
after click on confirm order I got this screen please check attachment
so please check and help me...
there is also some server side implementation, make sure your chechksum is generated from server.
Several things to take care:
Confirm you supply all required arguments for checksum generation to your server.
Make sure when paytm hits your server url, it generates same checksum as it was returned back to your app.
Your credentials are configured in their staging server while testing on staging, and same on their production server.
Make sure from their side if they have enabled all required options like cc/dc etc.
I'm writing this question and answer because I haven't seen a full solution to the integration of Google sign in on Android using Facebook's Parse SDK (or Sashido in my case) as a back-end without cloud code.
Related Questions:
How to link Google + signed in users on Parse backend on Android?
Google Plus Login issues - Parse.com
How would one go about integrating Google Sign in with Parse back-end without Cloud Code?
First of all, follow the steps provided by Android Developers on starting and implementing the integration.
Start Integrating Google Sign-In into Your Android App
Integrating Google Sign-In into Your Android App
In the onCreate your activity you need to build the GoogleSignInOptions
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build();
You can get your web_client_id when you add Google Services to your project to your Google Developers account. Find out more at: Creating a Google API Console project and client ID
Build your GoogleApiClient (make it a global instance private GoogleApiClient mGoogleApiClient;)
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
Log.e("Failed", "failed" + connectionResult.getErrorMessage());
}
})
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
Listen out for the click on the dedicated button for your Google sign in and then start a Auth.GoogleSignInApi.getSignIntent(mGoogleApiClient);
case R.id.btn_google:
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
startActivityForResult(signInIntent, RC_SIGN_IN);
break;
Make sure you've assigned a value to RC_SIGN_IN (I've done 1000)
Now start adding implementation to your onActivityResult method
// Result returned from launching the Intent from
// GoogleSignInApi.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
handleSignInResult(result);
} else {
ParseFacebookUtils.onActivityResult(requestCode, resultCode, data);
}
Now to handle the sign in request:
`private void handleSignInResult(GoogleSignInResult result) {
Log.e("handleSignIn", "handleSignInResult:" + result.isSuccess());
if (result.isSuccess()) {
// Signed in successfully, show authenticated UI.
final GoogleSignInAccount acct = result.getSignInAccount();
if (acct != null) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("_User");
query.setLimit(10000);
query.whereEqualTo("email", acct.getEmail());
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> objects, ParseException e) {
if (e == null) {
if (objects.size() == 0) {
saveNewUserGoogle(acct);
} else {
loginGoogleUser(objects.get(0), acct);
}
} else {
saveNewUserGoogle(acct);
}
}
});
}
} else {
Log.e("failed", "failed to sign in");
// Signed out, show unauthenticated UI.
}
}`
So what this method does is if the request to the GoogleSignIn Request is successful, get the account details, query the _User table in your database and to see if the email with the account matches. If it does, Log the user in.
private void loginGoogleUser(ParseObject j, GoogleSignInAccount acct) {
ParseUser.logInInBackground(j.getString("username"), String.valueOf(acct.getId()), new LogInCallback() {
public void done(ParseUser user, ParseException e) {
if (user != null) {
Intent i = new Intent(AllLoginActivity.this, MainActivity.class);
startActivity(i);
finish();
} else {
Log.e("failed", "could not be validated");
}
}
});
}
else sign the user up:
private void saveNewUserGoogle(GoogleSignInAccount acct) {
google = true;
final ParseUser user = new ParseUser();
String mFullName = acct.getDisplayName();
String mEmail = acct.getEmail();
String mProfilePic = String.valueOf(acct.getPhotoUrl());
String mUsername = acct.getId();
String password = acct.getId();
user.setUsername(mUsername);
user.setEmail(mEmail);
user.setPassword(password);
user.put("userEmail", mEmail);
user.put("uniqueID", mUsername);
user.put("name", mFullName);
user.put("loginMethod", "Google");
user.put("profilePicture", mProfilePic);
user.signUpInBackground(new SignUpCallback() {
public void done(ParseException e) {
if (e == null) {
Log.e("SaveTest", "Successful");
//sign user up
} else {
switch (e.getCode()) {
case ParseException.USERNAME_TAKEN:
Toast.makeText(context, "Sorry, this username has already been taken.", Toast.LENGTH_SHORT).show();
break;
case ParseException.USERNAME_MISSING:
Toast.makeText(context, "Sorry, a username is needed", Toast.LENGTH_SHORT).show();
break;
case ParseException.PASSWORD_MISSING:
Toast.makeText(context, "Sorry, a password is needed.", Toast.LENGTH_SHORT).show();
break;
case ParseException.OBJECT_NOT_FOUND:
Toast.makeText(context, "invalid credentials", Toast.LENGTH_SHORT).show();
break;
case ParseException.CONNECTION_FAILED:
Toast.makeText(context, "Sorry, internet is needed.", Toast.LENGTH_SHORT).show();
break;
default:
Log.d("Testing", e.getLocalizedMessage());
break;
}
}
}
});
}
So for this if you set the password on Parse as the clientID it'll be unique to that user and can be read by Parse and given by Google.
Note: I'm setting the username as the Google Identifier and then when they have successfully connected and signed up I display a username dialog box where they enter a username, so it can be displayed as something in plain text rather than numerics.
I'm trying to test out the logic for users who haven't got google play services on an emulator that I setup (without using Google APIs). As expected, attempting to signin with Google causes a "get google play services" dialog to popup, but pressing on the button in the dialog just causes an error in the logcat of:
SettingsRedirect: Can't redirect to app settings for Google Play services
I want to know if this is only because I'm using an emulator or if it could indicate a bug in my code that could affect users? (i.e. if there is a way of making this button work on an emulator)
Edit: code for my checking gps method:
public static boolean checkPlayServices(Activity activity, String actionWeArePerforming) {
Timber.i("checkPlayServices: called by %s because of %s", activity.getClass().getSimpleName(), actionWeArePerforming);
GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
int resultCode = googleApiAvailability.isGooglePlayServicesAvailable(activity);
int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
if (resultCode != ConnectionResult.SUCCESS) {
Timber.d("checkPlayservices: resultcode was not success");
if (googleApiAvailability.isUserResolvableError(resultCode)) {
Timber.d("checkPlayServices: update available - resolving");
// "user resolvable" means Google Play is available to download the last version of Play Services APK
// This will open Google dialog fragment displaying the proper message depending on "resultCode"
googleApiAvailability.showErrorDialogFragment(activity, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST);
} else {
Timber.d("checkPlayServices: update not available");
final ViewGroup viewGroup = (ViewGroup) ((ViewGroup) activity
.findViewById(android.R.id.content)).getChildAt(0);
// Should not happen. This device does not support Play Services.
// Let's show an ultimate warning.
Snackbar.make(viewGroup, activity.getString(R.string.cannot_download_google_play_services), Snackbar.LENGTH_LONG);
}
Timber.i("checkPlayServices: returning false");
return false;
}
Timber.i("checkPlayServices: returning true");
return true;
}
I ended doing this :
private boolean checkPlayServices() {
// Get the availability :
GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
int result = googleApiAvailability.isGooglePlayServicesAvailable(this);
if(result != ConnectionResult.SUCCESS) {
// Build a popup :
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.main_activity_google_play_services_problem_alert_title);
builder.setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
finish();
}
});
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
final String appPackageName = "com.google.android.gms";
try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName))); }
catch (android.content.ActivityNotFoundException e) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName))); }
finish();
}
};
// Set the messages according to the most common errors and show the popup :
switch(result) {
case ConnectionResult.SERVICE_MISSING : {
builder.setMessage(R.string.main_activity_google_play_services_missing_alert_message);
builder.setPositiveButton(R.string.main_activity_google_play_services_missing_alert_positive_button, listener);
builder.show();
return false;
}
case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED : {
builder.setMessage(R.string.main_activity_google_play_services_needs_update_alert_message);
builder.setPositiveButton(R.string.main_activity_google_play_services_needs_update_alert_positive_button, listener);
builder.show();
return false;
}
default: {
builder.setMessage(getString(R.string.main_activity_google_play_services_problem_alert_message).replaceAll("\\$1", googleApiAvailability.getErrorString(result)));
builder.setPositiveButton(R.string.main_activity_google_play_services_problem_alert_positive_button, listener);
builder.show();
return false;
}
}
}
return true;
}
This way :
if the availability is OK, the method returns true
if the services are not installed / need an update / have a problem, the method opens a popup
if the Play Store is not installed, the user will still be able to open the link in its browser
That should be OK, I guess... It's a shame that the out of the box methods provided by Google get the job only half done :/
Of course, you can do even better by starting the Play Store activity with a result and check again in onActivityResult, but I've been lazy on this one :)
It's because of the emulator, see this issue:
https://github.com/googlesamples/google-services/issues/32