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) ?
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
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.
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.
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.
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.
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
}
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")
}
SingletonComponent, you must annotate it with @Singleton to always inject the same manager.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()
}
}
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()
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With