'Why I am not able to use mobile authentication with firebase in jetpack compose?
I am try to learning android jetpack compose, I have simple app, and I want to use mobile authentication with firebase for my project. I am using MVVM in the project, so when I debug the project, it throw an error like
You must specify an Activity on your PhoneAuthOptions. Please call #setActivity()
for the .build()
in the view model, how can I arrange my viewmodel, I did not find any solution on internet, any idea?
Navigation:
@OptIn(ExperimentalPagerApi::class)
@Composable
fun NavScreen(
) {
val modelAuthentication =
hiltViewModel<AuthenticationViewModel>.()
val navController = rememberNavController()
NotificationMessage(viewModel = viewModel())
NavHost(navController = navController, startDestination =
"phone") {
composable("phone") {
PhoneScreen(navController = navController,
modelAuthentication = modelAuthentication) { _, _ ->
}
}
composable("phoneVerify") {
PhoneVerifyScreen(navController = navController,
modelAuthentication = modelAuthentication) { _, _ ->
}
}
}}
viewmodel:
@HiltViewModel
class AuthenticationViewModel @Inject constructor(
) : ViewModel() {
private val mAuth = FirebaseAuth.getInstance()
var verificationOtp = ""
var popNotification = mutableStateOf<Event<String>?>(null)
private lateinit var baseBuilder: PhoneAuthOptions.Builder
fun setActivity(activity: Activity) {
baseBuilder =
PhoneAuthOptions.newBuilder().setActivity(activity)
}
fun send(mobileNum: String) {
val options = baseBuilder
.setPhoneNumber("+91$mobileNum")
.setTimeout(60L, TimeUnit.SECONDS)
.setCallbacks(object :
PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(p0: PhoneAuthCredential) {
handledException(customMessage = "Verification Completed")
}
override fun onVerificationFailed(p0: FirebaseException) {
handledException(customMessage = "Verification Failed")
}
override fun onCodeSent(otp: String, p1: PhoneAuthProvider.ForceResendingToken) {
super.onCodeSent(otp, p1)
verificationOtp = otp
handledException(customMessage = "Otp Send Successfully")
}
}).build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
fun otpVerification(otp: String) {
val credential = PhoneAuthProvider.getCredential(verificationOtp, otp)
FirebaseAuth.getInstance().signInWithCredential(credential)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
handledException(customMessage = "Verification Successful")
} else {
handledException(customMessage = "Wrong Otp")
}
}
}
private fun handledException(exception: Exception? = null, customMessage: String = "") {
exception?.printStackTrace()
val errorMsg = exception?.message ?: ""
val message = if (customMessage.isEmpty()) {
errorMsg
} else {
"$customMessage: $errorMsg"
}
popNotification.value = Event(message)
}
}
screen1:
@Composable
fun PhoneScreen(
navController: NavController,
modelAuthentication: AuthenticationViewModel,
onClick: (mobileNum: String, otp: String) -> Unit
) {
val phoneNumber = remember { mutableStateOf("") }
val context = LocalContext.current
LaunchedEffect(Unit) {
println("found activity? ${context.findActivity()}")
val activity = context.findActivity() ?:
return@LaunchedEffect
modelAuthentication.setActivity(activity)
}
OutlinedTextField(
value = phoneNumber.value,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = white,
focusedIndicatorColor = Grey,
unfocusedIndicatorColor = Grey,
focusedLabelColor = Grey,
unfocusedLabelColor = Grey,
),
onValueChange = { phoneNumber.value = it },
label = { Text(text = "Phone Number") },
placeholder = { Text(text = "Phone Number") },
singleLine = true,
modifier = Modifier.fillMaxWidth(0.8f)
)
Button(
modifier = Modifier
.width(285.dp)
.height(55.dp),
onClick = {
modelAuthentication.send(phoneNumber.value)
navController.navigate("phoneVerify")
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Custom
),
shape = RoundedCornerShape(60)
) {
Text(
text = "Next",
style = TextStyle(
fontSize = 18.sp,
color = white,
)
)
}
}
screen2:
@Composable
fun PhoneVerifyScreen(
navController: NavController,
modelAuthentication: AuthenticationViewModel,
onClick: (mobileNum: String, otp: String) -> Unit
) {
val phoneNumberOtp = remember { mutableStateOf("") }
val context = LocalContext.current
LaunchedEffect(Unit) {
println("found activity? ${context.findActivity()}")
val activity = context.findActivity() ?:
return@LaunchedEffect
modelAuthentication.setActivity(activity)
}
OutlinedTextField(
value = phoneNumberOtp.value,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = white,
focusedIndicatorColor = Grey,
unfocusedIndicatorColor = Grey,
focusedLabelColor = Grey,
unfocusedLabelColor = Grey,
),
onValueChange = { phoneNumberOtp.value = it },
label = { Text(text = "code") },
placeholder = { Text(text = "code") },
singleLine = true,
modifier = Modifier.fillMaxWidth(0.8f)
)
Button(
modifier = Modifier
.width(285.dp)
.height(55.dp),
onClick = {
modelAuthentication.otpVerification(phoneNumberOtp.value)
navController.navigate("home")
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Custom
),
shape = RoundedCornerShape(60)
) {
Text(
text = "Next",
style = TextStyle(
fontSize = 18.sp,
color = white,
)
)
}
}
Solution 1:[1]
I'm pretty sure that it should be possible to inject activity context using Hilt, and hope to see the solution by @Alex Mamo or someone else.
For now here's hacky solution:
@HiltViewModel
class AuthenticationViewModel @Inject constructor(
) : ViewModel() {
private lateinit var baseBuilder: PhoneAuthOptions.Builder
fun setActivity(activity: Activity) {
baseBuilder = PhoneAuthOptions.newBuilder().setActivity(activity)
}
fun send(mobileNum: String) {
val options = baseBuilder
//.setPhoneNumber...
}
}
In your view:
val viewModel = viewModel<AuthenticationViewModel>()
val context = LocalContext.current
LaunchedEffect(Unit) {
val activity = context.findActivity() ?: return@LaunchedEffect
viewModel.setActivity(activity)
}
findActivity
:
fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
Solution 2:[2]
You need to specify Activity since it is a required parameter of the builder.
You will have to move the code of the builder to your Activity 'cause you're not allowed to have a link to Activity in a ViewModel.
Inside the verification result callback you can call ViewModel's methods to pass the result or exception.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Alex |