@Module @InstallIn(SingletonComponent::class) object RepositoryModule { @Provides @Singleton fun provideRepository(): Repository = Repository() } class Repository { val id = System.identityHashCode(this) } @AndroidEntryPoint class ActivityA : AppCompatActivity() { @Inject lateinit var repository: Repository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) println("ActivityA Repository ID: ${repository.id}") } } @AndroidEntryPoint class ActivityB : AppCompatActivity() { @Inject lateinit var repository: Repository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) println("ActivityB Repository ID: ${repository.id}") } }
The @Singleton annotation combined with @InstallIn(SingletonComponent::class) ensures that the same instance of Repository is shared across the entire application lifecycle. Therefore, both ActivityA and ActivityB receive the same instance, confirmed by identical identity hash codes.
class NetworkClient(private val baseUrl: String) { fun fetchData(): String = "Data from $baseUrl" }
To enable Hilt to inject a class via constructor injection, the constructor must be annotated with @Inject. This tells Hilt how to create instances of the class.
@HiltViewModel class UserViewModel @Inject constructor( private val userRepository: UserRepository ) : ViewModel() class UserRepository @Inject constructor( private val apiService: ApiService ) @Module @InstallIn(ActivityComponent::class) object NetworkModule { @Provides fun provideApiService(): ApiService = ApiService() }
The ApiService is provided in ActivityComponent scope, but UserViewModel is created in ViewModelComponent scope. Hilt cannot inject a dependency from a shorter-lived component (Activity) into a longer-lived component (ViewModel), causing a runtime error.
@InstallIn tells Hilt which component(s) the module belongs to. This controls where the provided dependencies live and their lifecycle, such as SingletonComponent for app-wide singletons or ActivityComponent for activity-scoped dependencies.
class UserRepository @Inject constructor() class UserViewModel @AssistedInject constructor( private val userRepository: UserRepository, @Assisted private val userId: Int ) { fun printUser() = println("UserViewModel with userId: $userId and repo: $userRepository") } @AssistedFactory interface UserViewModelFactory { fun create(userId: Int): UserViewModel } fun main() { // Simulate assisted injection factory usage val repo = UserRepository() val factory = object : UserViewModelFactory { override fun create(userId: Int) = UserViewModel(repo, userId) } val vm = factory.create(42) vm.printUser() }
AssistedInject lets you inject dependencies and pass runtime parameters like userId. The factory creates UserViewModel with userId=42 and injected UserRepository instance, so the print shows the correct userId and repository reference.