I am using Firebase PhoneAuth for user registration with my app. Users are verified successfully and started using the app without any issues. But, It happened to me a couple of times that user details vanished from Firestore database, not all users details have gone though. When I check the collection users in the Firestore, the document does exist for the user but some data was gone, in fact, it's overwritten the old data as if this is new user registration (However, the User UID under the Authentication is remain same). For example, the default value is user_type = "customer", as you can see in the fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential). Later I change this value accordingly to my need and when this issue happens the changes I made to this field and other fields are changed back to the default values.
Following is the code in my SignInWithPhone which will be called from the SplashScreen
class SigninWithPhoneActivity: BaseActivity() {
private lateinit var binding: ActivitySignInWithPhoneBinding
private lateinit var mAuth: FirebaseAuth
var code = ""
var number = ""
var phoneNumber = ""
var storedOtpID = ""
private lateinit var resendToken: PhoneAuthProvider.ForceResendingToken
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySignInWithPhoneBinding.inflate(layoutInflater)
setContentView(binding.root)
}
mAuth = FirebaseAuth.getInstance()
binding.btnGetOtp.setOnClickListener {
code = binding.etCountryCode.text.toString().trim()
number = binding.etMobileNumber.text.toString().trim()
phoneNumber = code + number
if (number.isNotEmpty()) {
binding.etMobileNumber.isEnabled = false
binding.flOtp.visibility = View.VISIBLE
binding.btnGetOtp.visibility = View.GONE
binding.btnSignIn.visibility = View.VISIBLE
sendVerificationCode(phoneNumber)
} else {
Toast.makeText(this, "Please enter a valid mobile number", Toast.LENGTH_LONG).show()
}
}
binding.btnSignIn.setOnClickListener {
val otp = binding.etOtp.text.toString().trim()
if (otp.isNotEmpty() || otp.length != 6) {
verifyVerificationCode(otp)
} else {
Toast.makeText(this, "Please enter the OTP received through SMS", Toast.LENGTH_LONG)
.show()
}
}
}
private val mCallBack: PhoneAuthProvider.OnVerificationStateChangedCallbacks =
object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
val code = credential.smsCode
if (code != null) {
binding.etOtp.setText(code)
binding.pbOtp.visibility = View.GONE
}
}
override fun onVerificationFailed(p0: FirebaseException) {
binding.etMobileNumber.isEnabled = true
binding.flOtp.visibility = View.GONE
binding.btnGetOtp.visibility = View.VISIBLE
binding.btnSignIn.visibility = View.GONE
Toast.makeText(this#SigninWithPhoneActivity, "Login Failed", Toast.LENGTH_LONG)
.show()
}
override fun onCodeSent(otpID: String, token: PhoneAuthProvider.ForceResendingToken) {
super.onCodeSent(otpID, token)
Toast.makeText(
this#SigninWithPhoneActivity,
"OTP is send to your number",
Toast.LENGTH_LONG
).show()
storedOtpID = otpID
resendToken = token
}
}
private fun sendVerificationCode(phoneNumber: String) {
binding.pbOtp.visibility = View.VISIBLE
Toast.makeText(this#SigninWithPhoneActivity, "Sending OTP", Toast.LENGTH_LONG).show()
val options = PhoneAuthOptions.newBuilder(mAuth!!)
.setPhoneNumber(phoneNumber)
.setTimeout(60L, TimeUnit.SECONDS)
.setActivity(this)
.setCallbacks(mCallBack)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
private fun verifyVerificationCode(code: String) {
Toast.makeText(
this#SigninWithPhoneActivity,
"Verifying credentials",
Toast.LENGTH_LONG
).show()
val credential = PhoneAuthProvider.getCredential(storedOtpID, code)
signInWithPhoneAuthCredential(credential)
}
private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {
mAuth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
val firebaseUser: FirebaseUser = task.result!!.user!!
val userPreliminaryDetails = User(
firebaseUser.uid,
user_type = "customer",
mobile = binding.etMobileNumber.text.toString()
)
FirestoreClass().checkIfUserAlreadyExist(
this#SigninWithPhoneActivity,
firebaseUser.uid,
userPreliminaryDetails
)
} else {
showErrorSnackBar(task.exception!!.message.toString(), true)
if (task.exception is FirebaseAuthInvalidCredentialsException) {
binding.etMobileNumber.isEnabled = true
binding.flOtp.visibility = View.GONE
binding.etOtp.text?.clear()
binding.btnGetOtp.visibility = View.VISIBLE
binding.btnGetOtp.text = "Resend OTP"
binding.btnSignIn.visibility = View.GONE
Toast.makeText(this, "OTP entered is wrong", Toast.LENGTH_LONG).show()
}
}
}
}
fun userRegistrationSuccess() {
finish()
startActivity(Intent(this, ServiceAreaActivity::class.java))
Toast.makeText(this, "Signed Up successful", Toast.LENGTH_LONG).show()
}
fun userSignInSuccess() {
finish()
startActivity(Intent(this, ServiceAreaActivity::class.java))
Toast.makeText(this, "Signed in successfully", Toast.LENGTH_LONG).show()
}
}
EDIT:
The following function is called to check if the user already exists and accordingly sign in or register a new user.
fun checkIfUserAlreadyExist(
activity: SigninWithPhoneActivity, userId: String, userDetails: User
) {
mFireStore.collection("users")
.whereEqualTo(Constants.USER_ID, userId)
.get()
.addOnSuccessListener { document ->
if (document.documents.size > 0) {
activity.userSignInSuccess()
} else {
FirestoreClass().registerUser(activity, userDetails)
}
}
.addOnFailureListener { e ->
}
}
private fun registerUser(activity: SigninWithPhoneActivity, userInfo: User) {
mFireStore.collection(Constants.USERS)
.document(userInfo.user_id)
.set(userInfo, SetOptions.merge())
.addOnSuccessListener {
activity.userRegistrationSuccess()
}
.addOnFailureListener { e ->
activity.hideProgressDialog()
}
}
Related
I have used Kotlin to create a task management app using Firebase, but the problem that I am having is that, users are able to see each others tasks and their tasks are stored in the "same account". When user 1 creates a task, user 2 is able to see what user 1 has created and the task that is created by user 2 is in the same list as the one created by user 1. How do I stop/avoid this? I do not want my users to share data, I want them to have their data in their respective accounts.
This is for registration:
class RegisterPage : AppCompatActivity() {
private lateinit var binding: ActivityRegisterPageBinding
private lateinit var firebaseAuth: FirebaseAuth
private lateinit var progressDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRegisterPageBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseAuth = FirebaseAuth.getInstance()
progressDialog = ProgressDialog(this)
progressDialog.setTitle("Please wait")
progressDialog.setCanceledOnTouchOutside(false)
binding.backButton.setOnClickListener{
onBackPressed()
}
binding.regButton.setOnClickListener{
validateData()
}
}
private var name =""
private var surname = ""
private var email = ""
private var password = ""
private var confirmPassword = ""
private fun validateData() {
name = binding.regName.text.toString().trim()
surname = binding.regSurname.text.toString().trim()
email = binding.regEmail.text.toString().trim()
password = binding.regCreate.text.toString().trim()
confirmPassword = binding.regConfirm.text.toString().trim()
if (name.isNotEmpty() && surname.isNotEmpty() && email.isNotEmpty() && password.isNotEmpty() && confirmPassword.isNotEmpty()) {
if(password == confirmPassword){
createAccount()
}else{
Toast.makeText(this, "Passwords do not match", Toast.LENGTH_SHORT).show()
}
}else{
Toast.makeText(this, "Please provide missing details", Toast.LENGTH_SHORT).show()
}
}
private fun createAccount(){
progressDialog.setMessage("Creating account...")
progressDialog.show()
// Adding user to firebase
firebaseAuth.createUserWithEmailAndPassword(email, password).addOnSuccessListener() {
updateUser()
}.addOnFailureListener{e->
progressDialog.dismiss()
Toast.makeText(this, "${e.message}", Toast.LENGTH_SHORT).show()
}
}
private fun updateUser(){
progressDialog.setMessage("Saving...")
val timestamp = System.currentTimeMillis()
// get curr user id
val uid = firebaseAuth.uid
// setup data to add in db
val hashMap: HashMap<String, Any?> = HashMap()
hashMap["uid"] = uid
hashMap["name"] = name
hashMap["surname"] = surname
hashMap["email"] = email
hashMap["timestamp"] = timestamp
hashMap["profilePicture"] = ""
hashMap["userType"] = "user"
//set data to db
val ref = FirebaseDatabase.getInstance().getReference("Users")
ref.child(uid!!).setValue(hashMap).addOnSuccessListener {
//Info saved, open home screen
progressDialog.dismiss()
Toast.makeText(this, "Account successfully created!", Toast.LENGTH_SHORT).show()
startActivity(Intent(this#RegisterPage, HomeScreen::class.java))
finish()
}.addOnFailureListener{ e ->
//failed adding data to db
progressDialog.dismiss()
Toast.makeText(this, "${e.message}", Toast.LENGTH_SHORT).show()
}
}
This is for login:
class LoginPage : AppCompatActivity() {
private lateinit var binding: ActivityLoginPageBinding
private lateinit var firebaseAuth: FirebaseAuth
private lateinit var progressDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginPageBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseAuth = FirebaseAuth.getInstance()
progressDialog = ProgressDialog(this)
progressDialog.setTitle("Please wait")
progressDialog.setCanceledOnTouchOutside(false)
binding.noAcc.setOnClickListener{
startActivity(Intent(this, RegisterPage::class.java))
}
binding.loginButton.setOnClickListener{
validateUserData()
}
binding.forgotPassword.setOnClickListener{
startActivity(Intent(this, ForgotPassword::class.java))
}
}
private var email = ""
private var password = ""
private fun validateUserData(){
// Input data
email = binding.loginEmail.text.toString().trim()
password = binding.loginPassword.text.toString().trim()
// Validating data
if(!Patterns.EMAIL_ADDRESS.matcher(email).matches()){
Toast.makeText(this, "Invalid email format", Toast.LENGTH_SHORT).show()
//Toast.makeText(this, "exception!!", Toast.LENGTH_SHORT).show()
}else if(password.isEmpty()){
Toast.makeText(this, "Please enter your password", Toast.LENGTH_SHORT).show()
}else{
loginUser()
}
}
// Login firebase auth
private fun loginUser() {
progressDialog.setMessage("Logging in...")
progressDialog.show()
/*val timestamp = System.currentTimeMillis()
// get curr user id
val uid = firebaseAuth.uid
// setup data to add in db
val hashMap: HashMap<String, Any?> = HashMap()
hashMap["uid"] = uid
hashMap["email"] = email
hashMap["timestamp"] = timestamp
hashMap["userType"] = "user"
val ref = FirebaseDatabase.getInstance().getReference("Logins")*/
firebaseAuth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener {
// Successful login
checkUser()
}.addOnFailureListener { e ->
// failed to login
progressDialog.dismiss()
Toast.makeText(this, "${e.message}", Toast.LENGTH_SHORT).show()
}
}
private fun checkUser() {
/* Check user type - Firebase Auth
* user - move to user dashboard
* admin - move to admin dashboard*/
progressDialog.setMessage("Checking account...")
val firebaseUser = firebaseAuth.currentUser!!
val ref = FirebaseDatabase.getInstance().getReference("Users")
ref.child(firebaseUser.uid).addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
progressDialog.dismiss()
// Get user type - user/admin
val userType = snapshot.child("userType").value
if(userType == "user"){
startActivity(Intent(this#LoginPage, HomeScreen::class.java))
finish()
}else if (userType == "admin"){
startActivity(Intent(this#LoginPage, HomeScreen::class.java))
finish()
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
I'm trying to make work the Login Validation form, but the program stops then reaches the second if, and I have Invalid email output. Run is clear and out of mistakes. Can't figure out what I'm doing wrong and why emailList is null
private fun logIn() {
val email = binding.editEmailAddress.text.toString()
val password = binding.editPassword.text.toString()
if (inputCheck(email, password)) {
mLoginViewModel = ViewModelProvider(this)[LoginViewModel::class.java]
val emailList = mLoginViewModel.getUserEmail(email)
if (emailList != null) {
if (emailList.password == password) {
Toast.makeText(requireContext(), "Logged in as $email", Toast.LENGTH_LONG)
.show()
findNavController().navigate(R.id.action_loginFragment_to_listFragment)
} else {
Toast.makeText(requireContext(), "Invalid password", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(requireContext(), "Invalid email", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(requireContext(), "Fill out blank fields", Toast.LENGTH_LONG).show()
}
}
private fun inputCheck(email: String, password: String): Boolean {
return !(TextUtils.isEmpty(email) || TextUtils.isEmpty(password))
}
LoginViewModel
fun getUserEmail(email: String): User? {
var checker: User? = null
viewModelScope.launch(Dispatchers.IO) {
checker = repository.getUserEmail(email)
}
return checker
}
fun getUserEmail(email: String): User? {
var checker: User? = null
viewModelScope.launch(Dispatchers.IO) {
checker = repository.getUserEmail(email)
}
return checker
// Will return null always because this is not waiting to assign value by above repository method
}
Insted you can do this
suspend fun getUserEmail(email: String): User? {
return repository.getUserEmail(email)
}
And in Activity Or Fragment
mLoginViewModel = ViewModelProvider(this)[LoginViewModel::class.java]
lifecycleScope.launch {
val emailList = mLoginViewModel.getUserEmail(email)
}
Don't Know what error you are getting on above code if that not works then use below code
mLoginViewModel = ViewModelProvider(this)[LoginViewModel::class.java]
CoroutineScope(Dispatchers.IO).launch {
val emailList = mLoginViewModel.getUserEmail(email)
withContext(Dispatchers.Main){
//Do whatever with emailList
}
}
I am stuck in a strange problem that is when i save data from 1 activity (Parent Page) it saves data. and on reload app it loads the saved preferences and user don't have to sign in again and again. But when i tried to do this with child it didn't helped :(
Here is my code for Parent:
class SignIn : AppCompatActivity() {
private val sharedPrefFile = Common.APP_NAME
private var auth: FirebaseAuth = Firebase.auth
private lateinit var usertype: String
val db = Firebase.firestore
lateinit var etEmail: String
lateinit var etPassword: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sign_in)
getData()
_sign_in_as.setText("signing in as " + usertype)
//SignIn button
_btn_sign_in.setOnClickListener{
etEmail = _sign_in_email.text.toString()
etPassword =_sign_in_password.text.toString()
AuthenticateUser(etEmail,etPassword)
}
//Signup Link
_sign_in_screen_sign_up_link.setOnClickListener{
//Creating Intent
val intent = Intent(this, ParentSignup::class.java)
startActivity(intent)
}
}
private fun AuthenticateUser(email: String, password: String){
_progressBar.visibility= View.VISIBLE
_progressBar.visibility= View.INVISIBLE
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithEmail:success")
db.collection(USER_COLLECTION)
.whereEqualTo(USER_EMAIL,email)
.get()
.addOnSuccessListener { documents ->
for (document in documents){
val intent = Intent(this, TabbedActivity::class.java)
//Saving UserType in Shared Preferences
val sharedPreferences: SharedPreferences = getSharedPreferences(sharedPrefFile, Context.MODE_PRIVATE)
val sharedPref: SharedPreferences.Editor = sharedPreferences.edit()
//Email
sharedPref.putString(USER_EMAIL, document.data[USER_EMAIL].toString())
sharedPref.apply()
//username
sharedPref.putString(USER_NAME, document.data[USER_NAME].toString())
sharedPref.apply()
//number
sharedPref.putString(USER_PHONE, document.data[USER_PHONE].toString())
sharedPref.apply()
//number
sharedPref.putString(Common.LOGIN_STATUS, Common.LOGGED_IN)
sharedPref.apply()
Common.userName = document.data[USER_NAME].toString()
Common.userEmail = document.data[USER_EMAIL].toString()
Common.userPhone = document.data[USER_PHONE].toString()
startActivity(intent)
finishAffinity()
}
}
} else {
_progressBar.visibility= View.INVISIBLE
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithEmail:failure", task.exception)
Toast.makeText(baseContext, "Authentication failed." + (task.getException()?.message
?: ""),
Toast.LENGTH_SHORT).show()
}
}
}
fun getData(){
val sharedPreferences: SharedPreferences = getSharedPreferences(sharedPrefFile, Context.MODE_PRIVATE)
val sharedPref: SharedPreferences = sharedPreferences
usertype = sharedPref.getString(USER_TYPE, "").toString()
}
}
Here is code for Child:
class ChildSignup : AppCompatActivity() {
private val sharedPrefFile = Common.APP_NAME
private lateinit var auth: FirebaseAuth
val db = Firebase.firestore
private val TAG = "testTag"
private lateinit var code: String
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_child_signup)
auth = Firebase.auth
//Linking Parent Email
_btn_link_child.setOnClickListener {
verifyParentEmail()
}
//Verifying Email
_btn_sign_up_verify.setOnClickListener{
saveInSharedPreferences()
linkChild()
}
_child_sign_up_parent_link.setOnClickListener{
val intent = Intent(this, SignIn::class.java)
startActivity(intent)
finishAffinity()
}
}
private fun saveInSharedPreferences() {
//Saving UserType in Shared Preferences
val sharedPreferences: SharedPreferences = getSharedPreferences(sharedPrefFile, Context.MODE_PRIVATE)
val sharedPref: SharedPreferences.Editor = sharedPreferences.edit()
//Email
sharedPref.putString(USER_EMAIL, _link_child_email.text.toString())
sharedPref.apply()
//username
sharedPref.putString(USER_NAME, _link_child_name.text.toString())
sharedPref.apply()
//number
sharedPref.putString(USER_PHONE, "03154970584")
sharedPref.apply()
//number
sharedPref.putString(LOGIN_STATUS, LOGGED_IN)
sharedPref.apply()
//userType
sharedPref.putString(USER_TYPE, USER_TYPE_CHILD)
sharedPref.apply()
Common.userType = USER_TYPE_CHILD
Common.userEmail = _link_child_email.text.toString()
Common.userName = _link_child_name.text.toString()
Toast.makeText(this, sharedPreferences.getString(USER_EMAIL, ""), Toast.LENGTH_SHORT).show()
Toast.makeText(this, sharedPreferences.getString(USER_NAME, ""), Toast.LENGTH_SHORT).show()
Toast.makeText(this, sharedPreferences.getString(USER_TYPE, ""), Toast.LENGTH_SHORT).show()
}
private fun verifyParentEmail() {
var flag = false
code =(100000..999999).random().toString()
Log.d("TAG:", code)
Toast.makeText(this, code, Toast.LENGTH_LONG).show()
auth.fetchSignInMethodsForEmail(_link_child_email.text.toString()).addOnSuccessListener(this) { task ->
if (!task.signInMethods?.isEmpty()!!){
db.collection(LINKED_CHILDS)
.whereEqualTo(USER_EMAIL,_link_child_email.text.toString())
.get()
.addOnSuccessListener { documents ->
for (document in documents){
if (document[USER_NAME] == _link_child_name.text.toString()){
flag = true
}
}
if (flag == true){
Toast.makeText(this, "This child is already linked with this parent!\nPlease verify code to continue", Toast.LENGTH_SHORT)
.show()
}
sendEmail(_link_child_email.text.toString(),
_link_child_name.text.toString(),
code,
"Email Verification Code"
)
_child_signup_verify_layout.isVisible = true
}
}
else{
Toast.makeText(this, "No Such parent found", Toast.LENGTH_SHORT)
.show()
}
}
}
#RequiresApi(Build.VERSION_CODES.O)
private fun linkChild() {
if (_sign_up_six_digit_code.text.toString() == code){
saveToCloud()
}
}
#RequiresApi(Build.VERSION_CODES.O)
private fun saveToCloud() {
val thread = Thread {
try {
//Your code goes here
val user = hashMapOf(
USER_TYPE to USER_TYPE_CHILD,
USER_EMAIL to _link_child_email.text.toString(),
USER_NAME to _link_child_name.text.toString(),
TOKEN to "" ,
DATE to LocalDate.now().toString(),
DAY to LocalDate.now().dayOfWeek.toString()
)
db.collection(Common.LINKED_CHILDS).add(user).addOnSuccessListener { documentReference ->
Log.d(TAG, "DocumentSnapshot added with ID: ${documentReference.id}")
//Moving to Next Screen
val intent = Intent(this, TabbedActivity::class.java)
// intent.putExtra(USER_TYPE, USER_TYPE_CHILD)
startActivity(intent)
// finishAffinity()
}.addOnFailureListener { e ->
Log.w(TAG, "Error adding document", e)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
thread.start()
Log.d("Thread status: ", "Started")
}
}
And I am checking preferences here:
class MainActivity : AppCompatActivity() {
private val sharedPrefFile = Common.APP_NAME
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sharedPreferences: SharedPreferences = getSharedPreferences(sharedPrefFile, Context.MODE_PRIVATE)
//UserType
Toast.makeText(this, sharedPreferences.getString(USER_TYPE, "1"), Toast.LENGTH_SHORT).show()
Toast.makeText(this, sharedPreferences.getString(USER_EMAIL, "2"), Toast.LENGTH_SHORT).show()
Toast.makeText(this, sharedPreferences.getString(USER_NAME, "3"), Toast.LENGTH_SHORT).show()
if (sharedPreferences.getString(USER_TYPE, "") == "" || sharedPreferences.getString(
USER_EMAIL, "") == "" || sharedPreferences.getString(USER_NAME, "") == ""){
Handler(Looper.getMainLooper()).postDelayed({
val intent = Intent(this, WelcomeScreen::class.java)
startActivity(intent)
finish()
},2000)
}
else{
userType = sharedPreferences.getString(USER_TYPE, "").toString()
userEmail = sharedPreferences.getString(USER_EMAIL, "").toString()
userName = sharedPreferences.getString(USER_NAME, "").toString()
val intent = Intent(this, TabbedActivity::class.java)
startActivity(intent)
finishAffinity()
}
}
}
I had Enabled the Google Android Device Verification API.
I had added the SHA-256 onto Firebase setting and updated the GSON file.
and After adding :
Firebase.auth.firebaseAuthSettings.setAppVerificationDisabledForTesting(true)
I am getting error that SafetyNet or Captcha are not succeded (kind of error).
Can anyone tell me how can i disable the captcha check ?
Here is my code
class OTPNewActivity : AppCompatActivity(), OnKeyboardVisibilityListener, View.OnClickListener {
var TAG = "OTPNewActivity"
lateinit var binding: ActivityOtpnewBinding
val action = "android.provider.Telephony.SMS_RECEIVED"
var userEnteredCode = ""
var systemGeneratedCode = ""
var phoneNumer = ""
var phoneDigits = ""
private lateinit var auth: FirebaseAuth
private lateinit var resendToken: PhoneAuthProvider.ForceResendingToken
private var callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks =
object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
Log.d(TAG, "onVerificationCompleted: $credential")
val code = credential.smsCode
if (code != null) {
binding.otpView.setText(code)
verifyPhoneNumberWithCode(systemGeneratedCode, code!!)
}
}
override fun onVerificationFailed(e: FirebaseException) {
Log.d(TAG, "onVerificationFailed $e")
if (e is FirebaseAuthInvalidCredentialsException) {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
"Invalid request"
)
} else if (e is FirebaseTooManyRequestsException) {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
"The SMS quota for the project has been exceeded $e"
)
} else {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL, "Something wents wrong"
)
}
}
override fun onCodeSent(
verificationId: String,
token: PhoneAuthProvider.ForceResendingToken
) {
// The SMS verification code has been sent to the provided phone number, we
// now need to ask the user to enter the code and then construct a credential
// by combining the code with a verification ID.
Log.d(TAG, "onCodeSent: $verificationId")
systemGeneratedCode = verificationId
resendToken = token
countdownTimer()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_otpnew)
initListeners()
}
private fun initListeners() {
LocalSharedPreference.getInstance(this).isPhoneNumberVerified = false
// Firebase.auth.firebaseAuthSettings.setAppVerificationDisabledForTesting(true)
auth = Firebase.auth
setKeyboardVisibilityListener(this)
binding.btnNext.setOnClickListener(this)
binding.tvCount.setOnClickListener(this)
binding.icBack.setOnClickListener(this)
val intent = intent
intent?.let {
phoneNumer = intent.getStringExtra(Constants.PHONE_NUMBER).toString()
phoneDigits = intent.getStringExtra(Constants.SAVE_PHONE_DIGITS).toString()
binding.textView.text =
"${this.resources.getString(R.string.digit_code)} $phoneNumer"
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phoneNumer)
.setTimeout(15L, TimeUnit.SECONDS)
.setActivity(this)
.setCallbacks(callbacks)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
binding.otpView.setOtpCompletionListener(OnOtpCompletionListener { otp -> // do Stuff
userEnteredCode = otp
binding.icNext.visibility = View.VISIBLE
binding.pbNext.visibility = View.GONE
verifyPhoneNumberWithCode(systemGeneratedCode, userEnteredCode)
})
}
private fun verifyPhoneNumberWithCode(verificationId: String?, code: String) {
try {
val credential = PhoneAuthProvider.getCredential(verificationId!!, code)
signInWithPhoneAuthCredential(credential);
} catch (e: Exception) {
binding.otpView.setText("")
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
this#OTPNewActivity.resources.getString(R.string.wrong_Code)
)
e.printStackTrace()
}
}
private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success")
LocalSharedPreference.getInstance(this).isPhoneNumberVerified = true
if (phoneNumer.contains("+52")) {
LocalSharedPreference.getInstance(this).setSaveCountry("MX")
} else if (phoneNumer.contains("+92")) {
LocalSharedPreference.getInstance(this).setSaveCountry("PK")
} else if (phoneNumer.contains("+1")) {
LocalSharedPreference.getInstance(this).setSaveCountry("US")
}
LocalSharedPreference.getInstance(this).savePhoneNumber(phoneNumer)
LocalSharedPreference.getInstance(this).setPhoneDigits(phoneDigits)
val user = task.result?.user
val intent = Intent(this#OTPNewActivity, ProfileActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
finish()
} else {
// Sign in failed, display a message and update the UI
Log.w(TAG, "signInWithCredential:failure", task.exception)
if (task.exception is FirebaseAuthInvalidCredentialsException) {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
"${task.exception}"
)
}
// Update UI
}
}
}
private fun setKeyboardVisibilityListener(onKeyboardVisibilityListener: OnKeyboardVisibilityListener) {
val parentView: View = (findViewById<View>(android.R.id.content) as ViewGroup).getChildAt(0)
parentView.getViewTreeObserver()
.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
private var alreadyOpen = false
private val defaultKeyboardHeightDP = 100
private val EstimatedKeyboardDP =
defaultKeyboardHeightDP + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 48 else 0
private val rect: Rect = Rect()
override fun onGlobalLayout() {
val estimatedKeyboardHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
EstimatedKeyboardDP.toFloat(),
parentView.getResources().getDisplayMetrics()
)
.toInt()
parentView.getWindowVisibleDisplayFrame(rect)
val heightDiff: Int =
parentView.getRootView().getHeight() - (rect.bottom - rect.top)
val isShown = heightDiff >= estimatedKeyboardHeight
if (isShown == alreadyOpen) {
Log.d("Keyboard state", "Ignoring global layout change...")
return
}
alreadyOpen = isShown
onKeyboardVisibilityListener.onVisibilityChanged(isShown)
}
})
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Checks whether a hardware keyboard is available
if (newConfig.hardKeyboardHidden === Configuration.HARDKEYBOARDHIDDEN_NO) {
Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show()
} else if (newConfig.hardKeyboardHidden === Configuration.HARDKEYBOARDHIDDEN_YES) {
Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show()
}
}
override fun onVisibilityChanged(visible: Boolean) {
if (!visible) {
val imm: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(binding.otpView, InputMethodManager.SHOW_IMPLICIT)
}
}
override fun onResume() {
super.onResume()
binding.otpView.requestFocus()
val imm: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(binding.otpView, InputMethodManager.SHOW_IMPLICIT)
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
registerReceiver(receiver, IntentFilter(action))
}
private fun countdownTimer() {
binding.pbNext.visibility = View.VISIBLE
binding.icNext.visibility = View.GONE
object : CountDownTimer(15000, 1000) {
override fun onTick(millisUntilFinished: Long) {
binding.tvCount.setText("Resend Code in : " + millisUntilFinished / 1000)
}
override fun onFinish() {
binding.tvCount.setText("I didn`t receive a code")
binding.icNext.visibility = View.VISIBLE
binding.pbNext.visibility = View.GONE
}
}.start()
}
override fun onClick(view: View) {
when (view.id) {
R.id.btn_next -> {
if (binding.otpView.text.toString().length == 6) {
LocalSharedPreference.getInstance(this#OTPNewActivity).isPhoneNumberVerified =
true
verifyPhoneNumberWithCode(systemGeneratedCode, userEnteredCode)
}
}
R.id.tv_count -> {
if (binding.tvCount.text.equals(this#OTPNewActivity.resources.getString(R.string.i_dont_received_code)))
resendVerificationCode(phoneNumer, resendToken)
}
R.id.ic_back -> {
finish()
}
}
}
private fun resendVerificationCode(
phoneNumber: String,
token: PhoneAuthProvider.ForceResendingToken?
) {
val optionsBuilder = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phoneNumber) // Phone number to verify
.setTimeout(15L, TimeUnit.SECONDS) // Timeout and unit
.setActivity(this) // Activity (for callback binding)
.setCallbacks(callbacks) // OnVerificationStateChangedCallbacks
if (token != null) {
optionsBuilder.setForceResendingToken(token) // callback's ForceResendingToken
}
PhoneAuthProvider.verifyPhoneNumber(optionsBuilder.build())
}
var receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == "android.provider.Telephony.SMS_RECEIVED") {
val bundle = intent.extras
var msgs: Array<SmsMessage?>? = null
var msg_from: String? = ""
Log.d(TAG, "onReceive called ")
if (bundle != null) {
try {
val pdus = bundle["pdus"] as Array<Any>?
msgs = arrayOfNulls(pdus!!.size)
for (i in msgs.indices) {
msgs[i] = SmsMessage.createFromPdu(pdus[i] as ByteArray)
msg_from = msgs[i]!!.getOriginatingAddress()
val msgBody: String = msgs[i]!!.getMessageBody()
if (msgBody.contains("is your verification code for running-errands.firebaseapp.com")) {
val _1: Char = msgBody[0]
val _2: Char = msgBody[1]
val _3: Char = msgBody[2]
val _4: Char = msgBody[3]
val _5: Char = msgBody[4]
val _6: Char = msgBody[5]
val code: String =
_1.toString() + _2.toString() + _3.toString() + _4.toString() + _5.toString() + _6.toString()
// binding.otpView.text = SpannableStringBuilder(code!!)
binding.otpView.setText(code)
verifyPhoneNumberWithCode(systemGeneratedCode, code!!)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver);
}
}
In my main activity i have a function that runs once the login button it pressed. I'm calling a class that attempts to login via an API which takes a second or two to run. However, when i'm calling the login class it seems to be threaded and doesn't wait for the login to complete and returns false which is the default. Example code is as follows:
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun loginBtnClicked(view: View) {
progressBar.visibility = View.VISIBLE
// get domain info
val domain: TextView = findViewById<TextView>(R.id.loginDomain)
val domainUrl = domain.text.toString()
val url = "$domainUrl/api/"
// get username info
val username = loginUsername
// get password info
val password = loginPassword
if (ApiGet(
url = url,
username = username.text.toString(),
password = password.text.toString()
).login()) {
println("It worked")
val dashboard = Intent(this, DashboardActivity::class.java)
Consts.DOMAIN = url
Consts.USERNAME = username.text.toString()
Consts.PASSWORD = password.text.toString()
startActivity(dashboard)
} else {
println("It didn't work")
progressBar.visibility = View.INVISIBLE
runOnUiThread {
Log.i(ContentValues.TAG, "runOnUiThread")
Toast.makeText(
applicationContext,
"Please check the domain, username and password then try again.",
Toast.LENGTH_SHORT
).show()
}
}
}
}
class ApiGet(val url: String, val username: String = Consts.USERNAME, val password: String = Consts.PASSWORD) {
fun login(): Boolean {
var loginAttempt: Boolean = false
var apiData: ApiLogin
val creds = Credentials.basic(username, password)
val request = Request.Builder().url(url).header("Authorization", creds).build()
val client = OkHttpClient()
client.newCall(request).enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
val body: String? = response.body()?.string()
val gson: Gson = GsonBuilder().create()
apiData = gson.fromJson(body, ApiLogin::class.java)
if (apiData.detail == "Invalid username/password.") {
loginAttempt = false
println(loginAttempt)
} else {
loginAttempt = true
println(loginAttempt)
}
}
override fun onFailure(call: Call, e: IOException) {
Toast.makeText(
MainActivity().getApplicationContext(),
"Please check your connection and try again.",
Toast.LENGTH_SHORT
).show()
loginAttempt = false
}
})
return loginAttempt
}
class ApiLogin(val detail: String)
}
It is because your newCall method runs async to the main thread meaning the rest of your code keeps running after you call it while it waits on another thread. To fix this rather than returning your result you can handle it in a callback like so:
class ApiGet(val url: String, val username: String = Consts.USERNAME, val password: String = Consts.PASSWORD) {
fun login(completion: (Boolean)->Unit) {
var loginAttempt: Boolean = false
var apiData: ApiLogin
val creds = Credentials.basic(username, password)
val request = Request.Builder().url(url).header("Authorization", creds).build()
val client = OkHttpClient()
client.newCall(request).enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
val body: String? = response.body()?.string()
val gson: Gson = GsonBuilder().create()
apiData = gson.fromJson(body, ApiLogin::class.java)
if (apiData.detail == "Invalid username/password.") {
println(loginAttempt)
completion(False)
} else {
print(loginAttempt)
completion(True)
}
}
override fun onFailure(call: Call, e: IOException) {
Toast.makeText(
MainActivity().getApplicationContext(),
"Please check your connection and try again.",
Toast.LENGTH_SHORT
).show()
completion(False) }
})
}
You can call the login function like so:
ApiGet(url = url,
username = username.text.toString(),
password = password.text.toString()).login { result ->
if (result) {
// success
} else {
// failure
}
}