0

I am writing an Android app that makes use of Firebase Auth and DI.

In this app, I regularly need to use the user's UID to perform some database operations, and in search of a way to stop passing the UID through MVVM layers, I've decided to give dependency injection a try.

My AppModule looks like this:

AppModule.kt

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Singleton
    @Provides
    fun provideServer(@ApplicationContext c: Context) = Server(c, Volley.newRequestQueue(c), provideUid())

    @Singleton
    @Provides
    fun provideFirebase() = FirebaseSource(FirebaseAuth.getInstance())

    @Singleton
    @Provides
    fun provideFirebaseAuth() = Firebase.auth

    @Singleton
    @Provides
    fun provideFirebaseUser(): Boolean {
        return provideFirebaseAuth().currentUser != null
    }

    @Singleton
    @Provides
    // FIXME
    fun provideUid(): String {
        return provideFirebaseAuth().currentUser?.uid ?: "" 
    }
}

Function provideUid() returns the Firebase current user's UID, and injects is as a dependency in a separate Server class:

Server.kt (part of)

class Server @Inject constructor(
    private val c: Context,
    private val volleyRequestQueue: RequestQueue,
    private val uid: String
): IServer {

When the app starts for the first time, Dagger Hilt uses this constructor and provides a null UID because there is no logged-in user at the moment. This behavior persists for the rest of the app's lifecycle, and the Server class never has a non-null UID because it was already constructed with an empty UID as a property.

Restarting the app will construct the Server class with a non-empty UID because Firebase Auth keeps some registry of the user's auth state.

My question, then, is whether Dagger Hilt can realize the UID has changed and update its value or not.

Things I've tried:

  • Deleting the @Singleton annotation from provideUid()
  • Changing the Server's uid parameter from val to var.
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
xvlaze
  • 837
  • 1
  • 10
  • 30

3 Answers3

2

Can Dagger Hilt realize whether a variable in a Module class has changed and then update its value?

The simplest answer is no, it cannot.

To let Dagger Hilt provide an instance for the FirebaseAuth, you can only use:

@Provides
fun provideFirebaseAuth() = FirebaseAuth.getInstance()

If you want to use the above instance in another method, as @Nitrodon already pointed out, you should pass that instance as an argument to the next method and not call it directly. So in code should look like this:

@Provides
fun provideFirebaseUser(auth: FirebaseAuth) = auth.currentUser

However, that's not useful since the currentUser object can be null according to the auth state of the user. Besides that, when an object is created, you can use it but it won't be recreated. So it doesn't make any sense to add this in your AppModule class. What you should do instead is inject an instance of FirebaseAuth in your application code, and check the currentUser against nullity. If it's not null, then you can safely use the UID.

If you want to know the auth state, then you should attach an AuthStateListener as explained in my answer from the following post:

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
0

You can try:

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    fun provideServer(@ApplicationContext c: Context, queue: RequestQueue, uid: String) = Server(c, queue, uid)

    @Provides
    @Singleton
    fun provideResponseQueue(@ApplicationContext c: Context) = Volley.newRequestQueue(c)

    @Singleton
    @Provides
    fun provideFirebase() = FirebaseSource(FirebaseAuth.getInstance())

    @Singleton
    @Provides
    fun provideFirebaseAuth() = Firebase.auth

    @Singleton
    @Provides
    fun provideFirebaseUser(): Boolean {
        return provideFirebaseAuth().currentUser != null
    }

    @Provides
    // FIXME
    fun provideUid(): String {
        return provideFirebaseAuth().currentUser?.uid ?: "" 
    }
}

If an instance of the Server object is created once in the application, this solution should not work.

plplmax
  • 1,765
  • 8
  • 19
  • `provideServer` is `@Singleton`-annotated because I only want a single instance of `Server` in the whole application, so I'm afraid your solution is not what I'm looking for. Thanks however for your quick response!! – xvlaze Jan 26 '22 at 18:07
  • @xvlaze, I am not familiar with firebase authentication, when the uid is null, is currentUser also null? – plplmax Jan 26 '22 at 18:14
  • Yes, currentUser is null if the UID (unique identifier) is null. – xvlaze Jan 26 '22 at 18:17
  • @xvlaze, then I would advise you to have a reference to the Firebase.auth in the Server. Otherwise, you can use my solution with multiple Servers, but the internal dependencies will still be Singleton :) – plplmax Jan 26 '22 at 18:25
0

First, you very rarely want one @Provides method to call another. It is usually better to let Dagger wire up dependencies between those methods for you.

Dagger will never recreate or update an object that it has already created. If you want a reference which will always return the current uid, you can inject either FirebaseAuth directly or a Provider<String>.

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    // Consider making a @Singleton @Provides method for RequestQueue.
    // That would let you use the @Inject annotation on Server's constructor.
    @Singleton
    @Provides
    fun provideServer(
        @ApplicationContext c: Context,
        uidProvider: Provider<String>
    ) = Server(c, Volley.newRequestQueue(c), uidProvider)

    @Singleton
    @Provides
    fun provideFirebase() = FirebaseSource(FirebaseAuth.getInstance())

    @Singleton
    @Provides
    fun provideFirebaseAuth() = Firebase.auth

    @Provides
    fun provideFirebaseUser(auth: FirebaseAuth): Boolean {
        return auth.currentUser != null
    }

    // This should not be a singleton.
    // Consider using @Named or a custom qualifier for common types like String.
    @Provides
    fun provideUid(auth: FirebaseAuth): String {
        return auth.currentUser?.uid ?: "" 
    }
}
Nitrodon
  • 3,089
  • 1
  • 8
  • 15
  • Thanks! Do you have any documentation on when should and should not use @Provides and Provider? I'm new to DI and thought Provides was okay in all cases. By the way, I don't really understand the part of creating a function for VolleyRequestQueue and calling it from the constructor. Got any examples? – xvlaze Jan 26 '22 at 21:59