1

I am making an app with firebase authentication that uses signInWithEmailAndPassword() to authenticate the users. Now I also need the activity context with signInWithEmailAndPassword(). So, how do I pass the activity context in my viewmodel class?

class LoginViewModel: ViewModel() {
var auth: FirebaseAuth = FirebaseAuth.getInstance()

fun signIn(email: String, password: String) {
    auth.signInWithEmailAndPassword(email, password)
        .addOnCompleteListener(..context required..) { task ->

        }
    }
} 

I have already gone through almost all questions related to my query and everyone in the answers come to the conclusion that using ActivityViewModel() instead of ViewModel() and getting application context is the way to go. But as I understand ApplicationContext won’t respond to configuration changes, so I don't think this is the way to go. The most popular question on SO with the same query is this. But they also said to use application context. So I thought of using a dependency injection framework like Dagger/HILT to inject the context that I need. Now my question is, will the DI framework take care of the activity context once the activity is destroyed and avoid any memory leaks?

Aman Grover
  • 1,621
  • 1
  • 21
  • 41
  • 1
    "Now my question is, will the DI framework take care of the activity context once the activity is destroyed and avoid any memory leaks?" - no, what you are trying to do is *always* a memory leak. – ianhanniballake May 21 '23 at 04:09
  • @ianhanniballake so what's the best way to use an activity context in viewmodel? – Aman Grover May 21 '23 at 04:58
  • 1
    Have your `signInWithEmailAndPassword()` call and the `addOnCompleteListener()` call be in an activity or fragment (or, at least in theory, composable). The sign-in flow involves navigation to an externally-supplied UI, and IMHO navigation is processed by the UI layer, not the viewmodel layer. – CommonsWare May 21 '23 at 10:45
  • @CommonsWare what I ended up doing was, make an inner sealed class for success and failed signin states, and pass a callback parameter of type `(result: SignInResult) -> Unit` to my `signInWithEmailAndPassword()` function, where `SignInResult` is the sealed class. I came to know I don't require a context in `addOnCompletionListener {}`. – Aman Grover May 21 '23 at 14:16
  • but then I have another question, should we _never_ pass an activity context in the view models? – Aman Grover May 21 '23 at 14:20
  • "should we never pass an activity context in the view models?" -- "never" is a strong word. I would describe a `ViewModel` having access to its hosting activity as being a strong code smell and the sort of thing to attempt to avoid in general, due to the potential of memory leaks as Ian indicated. It also becomes tricky to deal with configuration changes, as you might be referencing the old, destroyed activity rather than its replacement. – CommonsWare May 21 '23 at 16:44

2 Answers2

1

Ideally, your viewmodel does not have a reference to its hosting activity, as that invites memory leaks and configuration change problems.

Also, bear in mind that the sign-in flow involves navigation to an externally-supplied UI. Usually, navigation is managed by the UI layer (activity, fragment, or composable), perhaps acting on instructions from a viewmodel.

So, I would have your signInWithEmailAndPassword() call and the addOnCompleteListener() call be made in the UI layer. The on-complete listener could call a function on the viewmodel to inform it of the successful result, if desired.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • I have asked another question regarding this. What I did was, make an inner sealed class SignInResult and use that with a mutable livedata object, so as soon as I get a result, I change the mutable value, and observe the changes in my activity. Is this more efficient than having completion listener in the activity? – Aman Grover May 22 '23 at 05:39
  • @AmanGrover: If by "efficient" you mean "uses the least CPU instructions", then no, that is less efficient than just having the completion listener in the activity. However, for something like this, efficiency should be fairly low on your list of priorities. Users will not be signing in a million times in a tight loop. – CommonsWare May 22 '23 at 10:28
0

I think you can use @ActivityContext same as @ApplicationContext. Both are provided by hilt bydefault so you don't need to do much work on it

class LoginViewModel: ViewModel() {
var auth: FirebaseAuth = FirebaseAuth.getInstance()

@ActivityContext 
var context: Context

fun signIn(email: String, password: String) {
    auth.signInWithEmailAndPassword(email, password)
        .addOnCompleteListener(..context required..) { task ->

        }
    }
}

For more information , you can check this point

https://developer.android.com/training/dependency-injection/hilt-android#component-default

jayesh gurudayalani
  • 1,368
  • 1
  • 9
  • 14