私のアプリでは、Firebaseを使用してユーザーの電話番号を確認しています。ただし、検証システムに一貫性がなく、OTPを初めて送信するだけです。たとえば、初めてサインインするときにOTPを取得しますが、サインアウトして再度サインインしようとすると、OTPを取得できません。
これは、ユーザーにOTPの入力を求められるアクティビティです。
import Android.app.ProgressDialog;
import Android.content.Intent;
import Android.os.Bundle;
import Android.os.CountDownTimer;
import Android.view.View;
import Android.widget.EditText;
import Android.widget.TextView;
import Android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.google.Android.gms.tasks.OnCompleteListener;
import com.google.Android.gms.tasks.Task;
import com.google.Android.material.floatingactionbutton.FloatingActionButton;
import com.google.Android.material.snackbar.Snackbar;
import com.google.firebase.FirebaseException;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.PhoneAuthCredential;
import com.google.firebase.auth.PhoneAuthProvider;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.google.firebase.firestore.QuerySnapshot;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import Java.util.concurrent.TimeUnit;
public class EnterOTPActivity extends AppCompatActivity {
String userPhoneNumber;
private int autoResendCount=0, resumeCount=0;
private final String ctryCode = "+91";
private String verificationId;
private FirebaseAuth mAuth;
private TextView resendOtp, enterOtpMessage, timerTV;
private ProgressDialog progressDialog;
private EditText otpField;
private boolean firstSend=true,timerOn=true;
FirebaseFirestore db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_enter_otp);
mAuth = FirebaseAuth.getInstance();
otpField = findViewById(R.id.editTextOtp);
resendOtp = findViewById(R.id.resendOTPButton);
timerTV = findViewById(R.id.resendTimer);
userPhoneNumber = getIntent().getStringExtra("USER_MOB").trim();
sendVerificationCode(userPhoneNumber);
startResendTimer(15);
getSupportActionBar().setTitle("Verification");
resendOtp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!timerOn) {
startResendTimer(30);
firstSend = false;
sendVerificationCode(userPhoneNumber);
}
}
});
FloatingActionButton next = findViewById(R.id.fab2);
enterOtpMessage = findViewById(R.id.aboutotpverif);
enterOtpMessage.setText(enterOtpMessage.getText().toString() + " " + ctryCode + userPhoneNumber);
progressDialog = new ProgressDialog(this);
progressDialog.setMessage("Please wait while we check your verification code");
progressDialog.setTitle("Verification");
progressDialog.setCancelable(false);
next.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//check if otp is correct here
if (otpField.getText().toString().length() == 6) {
verifyCode(otpField.getText().toString().trim());
if(!progressDialog.isShowing())
progressDialog.show();
} else {
View parentLayout = findViewById(Android.R.id.content);
Snackbar.make(parentLayout, "A valid verification code has 6 digits", Snackbar.LENGTH_SHORT)
.setAction("OKAY", new View.OnClickListener() {
@Override
public void onClick(View view) {
}
})
.setActionTextColor(getResources().getColor(Android.R.color.holo_red_light))
.show();
}
}
});
}
private void verifyCode(String code) {
try {
PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, code);
signInwithCredential(credential);
}
catch (Exception e) {
// code here is executed when we don't get the OTP and enter a wrong one
progressDialog.dismiss();
progressDialog.setCancelable(true);
View parentLayout = findViewById(Android.R.id.content);
Snackbar.make(parentLayout, getString(R.string.incorrect_code_t1), Snackbar.LENGTH_LONG)
.setAction("OKAY", new View.OnClickListener() {
@Override
public void onClick(View view) {
}
})
.setActionTextColor(getResources().getColor(Android.R.color.holo_red_light))
.show();
if(progressDialog.isShowing())
progressDialog.dismiss();
}
}
private void signInwithCredential(PhoneAuthCredential credential) {
mAuth.signInWithCredential(credential).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
isRegisteredUser();
}
else {
if(progressDialog.isShowing())
progressDialog.dismiss();
View parentLayout = findViewById(Android.R.id.content);
Snackbar.make(parentLayout, "Incorrect verification code", Snackbar.LENGTH_SHORT)
.setAction("OKAY", new View.OnClickListener() {
@Override
public void onClick(View view) {
}
})
.setActionTextColor(getResources().getColor(Android.R.color.holo_red_light))
.show();
if(progressDialog.isShowing())
progressDialog.dismiss();
}
}
});
}
private void sendVerificationCode(String number) {
final String phoneNumber = ctryCode + number;
PhoneAuthProvider.getInstance().verifyPhoneNumber(
phoneNumber, // Phone number to verify
120, // Timeout duration
TimeUnit.SECONDS, // Unit of timeout
this, // Activity (for callback binding)
mCallbacks); // OnVerificationStateChangedCallbacks
}
private PhoneAuthProvider.OnVerificationStateChangedCallbacks mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
@Override
public void onCodeSent(@NonNull String s, @NonNull PhoneAuthProvider.ForceResendingToken forceResendingToken) {
super.onCodeSent(s, forceResendingToken);
verificationId = s;
}
@Override
public void onVerificationCompleted(@NonNull PhoneAuthCredential phoneAuthCredential) {
String code = phoneAuthCredential.getSmsCode();
if (code != null) {
verifyCode(code);
otpField.setText(code);
if(!progressDialog.isShowing())
progressDialog.show();
}
}
@Override
public void onVerificationFailed(@NonNull FirebaseException e) {
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
};
private void isRegisteredUser() {
final String userId = ctryCode + userPhoneNumber;
db = FirebaseFirestore.getInstance();
db.collection("users")
.whereEqualTo("phone", userId)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
try{
if (task.isSuccessful()) {
saveToken(userId);
for (QueryDocumentSnapshot document : task.getResult()) {
if (document.getId().equalsIgnoreCase(userId)) {
Intent startmainact = new Intent(EnterOTPActivity.this, MainActivity.class);
startmainact.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(startmainact);
finish();
return;
}
//Log.d(TAG, document.getId() + " => " + document.getData());
}
Intent startposact = new Intent(EnterOTPActivity.this, ParOrStudActivity.class);
startposact.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(startposact);
finish();
} else {
View parentLayout = findViewById(Android.R.id.content);
Snackbar.make(parentLayout, "Database error: please try again", Snackbar.LENGTH_SHORT)
.setAction("OKAY", new View.OnClickListener() {
@Override
public void onClick(View view) {
}
})
.setActionTextColor(getResources().getColor(Android.R.color.holo_red_light))
.show();
}
}
catch(Exception e){
View parentLayout = findViewById(Android.R.id.content);
Snackbar.make(parentLayout, "Database error: please try again", Snackbar.LENGTH_SHORT)
.setAction("OKAY", new View.OnClickListener() {
@Override
public void onClick(View view) {
}
})
.setActionTextColor(getResources().getColor(Android.R.color.holo_red_light))
.show();
}
}
});
}
private void saveToken(final String userId) {
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(@NonNull Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
return;
}
// Get new Instance ID token
String token = task.getResult().getToken();
FirebaseFirestore.getInstance().collection("users").document(userId).update("token",token);
}
});
}
@Override
protected void onResume() {
super.onResume();
if (autoResendCount<2 && resumeCount>0) {
sendVerificationCode(userPhoneNumber);
autoResendCount=autoResendCount+1;
resumeCount=resumeCount+1;
}
}
@Override
protected void onStop() {
super.onStop();
progressDialog.dismiss();
}
public void startResendTimer(int seconds) {
timerTV.setVisibility(View.VISIBLE);
resendOtp.setEnabled(false);
new CountDownTimer(seconds*1000, 1000) {
public void onTick(long millisUntilFinished) {
String secondsString = Long.toString(millisUntilFinished/1000);
if (millisUntilFinished<10000) {
secondsString = "0"+secondsString;
}
timerTV.setText(" (0:"+ secondsString+")");
}
public void onFinish() {
resendOtp.setEnabled(true);
timerTV.setVisibility(View.GONE);
timerOn=false;
}
}.start();
}
@Override
public void onBackPressed() {
// do not allow user to go back to the screen before
}
}
この不安定な検証システムは、無能なUXを提供します。それを回避する方法はありますか?
@Override
public void onVerificationCompleted(PhoneAuthCredential credential) {
// This callback will be invoked in two situations:
// 1 - Instant verification. In some cases the phone number can be instantly
// verified without needing to send or enter a verification code.
// 2 - Auto-retrieval. On some devices Google Play services can automatically
// detect the incoming verification SMS and perform verification without
// user action.
Log.d(TAG, "onVerificationCompleted:" + credential);
String code = phoneAuthCredential.getSmsCode();
Log.d("REGISTER_USER", "code generated first " + code);
if (code != null) {
//verifying the code like in normal flow
verifyUserAuthCode(code);
} else {
//you dont get any code, it is instant verification
showProgressBar(WAIT_TITLE, VERIFYING_MESSAGE);
signInWithPhoneAuthCredential(phoneAuthCredential);
}
}
private void signInWithPhoneAuthCredential(PhoneAuthCredential credential) {
auth.signInWithCredential(credential)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
final FirebaseUser user = auth.getCurrentUser();
if (task.isSuccessful()) {//user verified}
});
}
上記のコメントを公式のGoogleドキュメントで注意深く読んでください。firebaseは電話番号を送信せずにインスタント検証を使用します。これには、「資格情報」オブジェクトを検証する必要があるだけです。ログを書き込むと、検証コードリクエストを送信するたびにこのメソッドが実行されることがわかります
アプリからログアウトしようとするだけで、アプリのデータを消去したり、再インストールしたりできます。