Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom scope using Hilt

I am using HILT library as dependency injection library. In my application user should login with username and password, and when success, I will provide the login token as a singleton to be injected to the rest of Activities. Till now everything is fine. But when user logs out and tries to login with another username and password, the token remains the same, since I've been using a singleton to provide the token. What I need here is to make a custom Scope, which will have the same instance for Activities except login Activity, But when user logs out and again login, A new instance of login token would be provided for the next Activities. How can I do that? Is there a simple way to add a scope living shorter than Application Scope, but living longer than other components (scopes) ?

like image 944
user3540530 Avatar asked Oct 27 '25 12:10

user3540530


1 Answers

You can do this using a custom Hilt module (or a Dagger component with a dependency on one of the existing Hilt modules). You can find the docs on how to do it here, but I'll apply it here to get what you need:

Tl; dr

Create a custom component and a class that manages its scope (the moments when the component must be created and destroyed). This class will be injectable, so inject it whenever you need to start the user scope, whenever you need to use user-bound dependencies and whenever the user scope ends.

Disclaimer

  • You won't get to use member injection with your custom component.
  • You are not safe from using your custom component out of its scope (when it hasn't been initialized yet).

If you want to use member injection, use a Dagger component instead.

Since you need to inject certain objects only when the user is available and to reinstantiate them whenever the user changes, you need a UserComponent that will be created when the user logs in, and destroyed after the user logs out.

  1. Create it like this:

    @DefineComponent(parent = SingletonComponent::class)  
    interface UserComponent
    

    I'm setting the SingletonComponent as the parent component because I don't think any smaller scope may suit the scope of a User object, but you may set whichever Hilt component you wish, as long as it's a descendant of SingletonComponent.

  2. In a separate class, create a builder for the component like this:

    @DefineComponent.Builder  
    interface UserComponentBuilder {  
    
      @BindsInstance  
      fun bindUser(user: User): UserComponentBuilder  
      fun build(): UserComponent  
    }
    

    Since the user will be available at some point during the execution and it's intrinsic to this scope, the component cannot exist without the User, hence, @BindsInstance will work perfectly. Sadly, there is no Factory interface in @DefineComponent, to make it more robust.

  3. Since you need a provider method to get the user token, add a module to the UserComponent with it:

    @Module  
    @InstallIn(UserComponent::class)  
    class UserModule {
    
      @UserToken
      fun provideToken(user: User): String = user.token
    }
    

    I've assumed that the token comes from the user. Also, I've assumed that you will inject the token with a qualifier (read about it in the Qualifier section at the end of this article, but you can ignore this part and inject the user directly if you will obtain it from the user, or just don't use String with a qualifier.

  4. Hilt only lets you use custom components to expose providers (you cannot use an annotation to automatically bootstrap injection in a class), so you need to create an @EntryPoint to expose the providers of our UserComponent. Do it like this:

    @InstallIn(UserComponent::class)  
    @EntryPoint  
    interface UserComponentEntryPoint {  
    
      @UserToken  
      fun getUserToken(): String  
    
      fun getUser(): User
    }
    
  5. Now you need a class to manage this component's life and behaviour. Create it like this:

    @Singleton  
    class UserDIManager @Inject constructor(  
      private val builder: UserComponentBuilder  
    ) {  
    
      private var component: UserComponent? = null  
      private var entryPoint: UserEntryPoint? = null  
    
      val userToken: String
        get() = getEntryPoint().getUserToken()  
    
      val user: User
        get() = getEntryPoint().getUser()  
    
      fun init(user: User) {
        component = builder
          .bindUser(user)
          .build()
          .also { component = it }
    
        entryPoint = EntryPoints.get(getComponent(), UserComponentEntryPoint::class.java)  
      }
    
      fun release() {
        component = null
        entryPoint = null
      }  
    
      private fun getComponent(): UserComponent = component
        ?: throw Exception("UserComponent not initialized yet")  
    
      private fun getEntryPoint(): UserComponentEntryPoint = entryPoint
        ?: throw Exception("UserComponent not initialized yet")  
    }
    
    • This manager must be injected so it can receive the builder to create the component. Since it is created from the SingletonComponent, you must annotate it with @Singleton to always inject the same manager.
    • This manager has a property for each dependence it can inject.
    • This manager has a method to initialize your component and a method to destroy it.
  6. Manage the component's life. Inject the manager wherever the user scope starts and ends, and initialize and destroy the component:

    class LogInUseCase @Inject constructor(
      private val userManager: UserDIManager,
    ) {
    
      fun execute() {
        val user = // Do whatever you need to log in and get the user
        userManager.init(user)
      }
    }
    
    class LogOutUseCase @Inject constructor(
      private val userManager: UserDIManager,
    ) {
    
      fun execute() {
        // After the user has finished logging out...
        userManager.release()
      }
    }
    
  7. Use your component wherever you are inside the user scope. For example:

    @AndroidEntryPoint
    class UserInfoActivity(): AppCompatActivity {
    
      @Inject
      lateinit var userManager: UserDIManager
    
      override fun onCreate() {
          super.onCreate()
          val user = userManager.getUser()
      }
    }
    
like image 196
Juan José Melero Gómez Avatar answered Oct 29 '25 03:10

Juan José Melero Gómez



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!