Reputation: 736
I am using MVVM. I want to implement Firebase Auth in app. But to implement it I need an activity context in my repo class. How can I get it from ViewModel or is there any simple method available?
Here is the firebase code I need to implement:
PhoneAuthProvider.getInstance().verifyPhoneNumber("+91"+phone, // Phone number to verify
60, // Timeout duration
TimeUnit.SECONDS, // Unit of timeout
(Activity) context, // Activity (for callback binding)
new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
@Override
public void onVerificationCompleted(@NonNull PhoneAuthCredential phoneAuthCredential)
{
signInWithPhoneAuthCredential((Activity)context,phoneAuthCredential);
}
@Override
public void onVerificationFailed(@NonNull FirebaseException e) {
setLoginFailed(e);
}
@Override
public void onCodeSent(@NonNull String s, @NonNull
PhoneAuthProvider.ForceResendingToken forceResendingToken) {
super.onCodeSent(s, forceResendingToken);
loginResponse.setOnProgress(false);
loginResponse.setStatus(true);
loginResponse.setCodeVerified(false);
loginResponseLiveData.setValue(loginResponse);
verificationId =s;
}
});
Upvotes: 3
Views: 1022
Reputation: 1361
Here are some good practices that you should always keep in mind:
Activity or Application context shouldn't be passed to a ViewModel
If activity context is explicitly needed you shouldn't use application context and cast it to Activity
applicationContext
is designed to be a global context that is tied to the application's lifecycle, not to the specific lifecycle of an Activity
.applicationContext
to an Activity is inherently unsafe. Since applicationContext
is a global context and does not have the specific lifecycle and UI-related capabilities of an Activity, the cast can lead to ClassCastException
if the context is not actually an instance of Activity
. Even if the cast succeeds, it can result in undefined behavior, as the applicationContext
doesn't carry the activity's lifecycle state and UI components.Solution:
Since March 28th 2023 in Firebase Android BoM version 31.4.0
(release notes), PhoneAuthOptions.Builder
now accepts a null Activity
, but it will throw a FirebaseAuthMissingActivityForRecaptchaException
if app verification falls back to reCAPTCHA. This can happen if Play Integrity is unavailable or if the app fails Play Integrity checks. This issue has been previously raised in this Github issue.
Therefore, you may now do what the Firebase docs suggest without calling .setActivity(this)
, like this:
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phoneNumber) // Phone number to verify
.setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit
.setCallbacks(callbacks) // OnVerificationStateChangedCallbacks
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
Just make sure that you handle the possible FirebaseAuthMissingActivityForRecaptchaException
error case in the onVerificationFailed
callback as shown in the docs:
callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
// 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")
signInWithPhoneAuthCredential(credential)
}
override fun onVerificationFailed(e: FirebaseException) {
// This callback is invoked in an invalid request for verification is made,
// for instance if the the phone number format is not valid.
Log.w(TAG, "onVerificationFailed", e)
if (e is FirebaseAuthInvalidCredentialsException) {
// Invalid request
} else if (e is FirebaseTooManyRequestsException) {
// The SMS quota for the project has been exceeded
} else if (e is FirebaseAuthMissingActivityForRecaptchaException) {
// reCAPTCHA verification attempted with null Activity
}
// Show a message and update the UI
}
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")
// Save verification ID and resending token so we can use them later
storedVerificationId = verificationId
resendToken = token
}
}
Upvotes: 0
Reputation: 468
If you work not related to any UI
then you can use AndroidViewModel
instead of ViewModel
In AndroidViewModel
there in one important default parameter ApplicationContext
Or if you want any specific activity References then use LiveData<YourActivity>
and set class reference when you initialize ViewModel in class
Upvotes: 1
Reputation: 410
First of all, according to official view model guidelines:
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity content
So in accordance with your MVVM architecture, don't pass your activity context to a ViewModel.
I guess in that method you want to implement you do not specifically need an activity context. You could also use the context
of your application.
Follow these 3 steps of this answer to get access your Application context
statically:
context
on create
public class MyApplication extends Application {
private static Context context;
public void onCreate() {
super.onCreate();
MyApplication.context = getApplicationContext();
}
public static Context getAppContext() {
return MyApplication.context;
}
}
<application android:name="com.xyz.MyApplication">
</application>
MyApplication.getAppContext()
There is also the possibility to access the application context via AndroidViewModel
class. But I guess you usually do not want to initialize your repository from your ViewModel.
Upvotes: 1
Reputation: 317487
In general, ViewModel objects are not supposed to reference anything that has to do with the Android platform APIs, especially Activity objects. You would not want a ViewModel to retain (and leak) an Activity across orientation changes. Retaining Activity objects in a ViewModel is a huge anti-pattern that should be avoided.
Instead, you should use a different version of that Firebase API. Choose one of the alternatives that do not take an Activity, according to the API documentation. Once the API is complete, you can bubble a callback up to the hosting activity to start any other activities.
Upvotes: 1