Android Entry Points
This guide covers how to inject dependencies into different Android components using Koin. Each Android component type has specific lifecycle characteristics that affect how you should use dependency injection.
This page focuses on where to inject (entry points). The injection APIs (by inject(), get(), by viewModel()) work the same regardless of how you declare definitions. For declaring definitions, see Definitions.
Overview
Android applications consist of various component types, each with their own lifecycle and initialization patterns. Koin provides flexible ways to inject dependencies into all of them.
Quick Reference
| Component | Injection Method | Built-in Support | Notes |
|---|---|---|---|
| Application | startKoin {} in onCreate() | ✅ Yes | Entry point for Koin setup |
| Activity | by inject() or get() | ✅ Yes | Direct injection support |
| Fragment | by inject() or get() | ✅ Yes | Direct injection support |
| ViewModel | by viewModel() | ✅ Yes | Lifecycle-aware injection |
| Service | by inject() or get() | ✅ Yes | Direct injection support |
| BroadcastReceiver | KoinComponent + get() | ⚠️ Manual | Implement KoinComponent |
| ContentProvider | KoinComponent + get() | ⚠️ Manual | Special timing considerations |
| Custom View | Constructor or KoinComponent | ⚠️ Manual | Consider avoiding DI |
Application Class
The Application class is where you initialize Koin. This is the foundation for all dependency injection in your app.
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this@MyApplication)
modules(appModule, networkModule, dataModule)
}
}
}
For complete Application setup instructions, see Starting Koin on Android.
Activity Injection
Activities have built-in Koin support through extension functions.
Using by inject()
class UserActivity : AppCompatActivity() {
// Lazy injection - created when first accessed
private val presenter: UserPresenter by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
presenter.loadUser() // Presenter created here
}
}
Using get()
class UserActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
// Eager injection - created immediately
val presenter: UserPresenter = get()
presenter.loadUser()
}
}
With Parameters
class UserDetailActivity : AppCompatActivity() {
private val userId: String by lazy { intent.getStringExtra("USER_ID") ?: "" }
// Pass runtime parameters
private val presenter: UserDetailPresenter by inject { parametersOf(userId) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter.loadUserDetails()
}
}
See Injected Parameters for declaring definitions with parameters.
For more Activity injection patterns, see Injecting in Android.
Fragment Injection
Fragments work identically to Activities with Koin extensions.
class UserListFragment : Fragment() {
private val viewModel: UserListViewModel by viewModel()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_user_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.loadUsers()
}
}
Shared ViewModels
Share a ViewModel between Activity and Fragments:
class UserDetailFragment : Fragment() {
// Get ViewModel scoped to Activity
private val sharedViewModel: UserViewModel by activityViewModel()
// Get ViewModel scoped to this Fragment
private val fragmentViewModel: DetailViewModel by viewModel()
}
For Fragment and ViewModel injection details, see:
Service Injection
Services have built-in Koin support through extension functions, just like Activities and Fragments.
Using by inject()
class MusicPlayerService : Service() {
// Lazy injection - created when first accessed
private val player: MediaPlayer by inject()
private val repository: MusicRepository by inject()
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val songId = intent?.getStringExtra("SONG_ID")
if (songId != null) {
val song = repository.getSong(songId)
player.play(song)
}
return START_NOT_STICKY
}
override fun onDestroy() {
player.release()
super.onDestroy()
}
}
Using get()
class DownloadService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Eager injection - created immediately
val downloader: FileDownloader = get()
val url = intent?.getStringExtra("URL") ?: ""
downloader.start(url)
return START_STICKY
}
}
Lifecycle Considerations
- Services are long-lived: Use
singlefor expensive resources - Cleanup is critical: Release resources in
onDestroy() - Background threads: Consider scoping background work properly
Best Practices
class DownloadService : Service() {
// Use lazy injection to delay initialization
private val downloader: FileDownloader by inject()
private val notificationManager: NotificationManager by inject()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Dependencies created only when service actually starts
downloader.start(intent?.getStringExtra("URL") ?: "")
return START_STICKY
}
override fun onDestroy() {
// Always cleanup
downloader.cancel()
super.onDestroy()
}
}
Alternative: For WorkManager background tasks, use Koin's built-in WorkManager support instead of Services. See WorkManager Integration.
BroadcastReceiver Injection
BroadcastReceivers also need KoinComponent for dependency injection.
Dynamically Registered Receiver
class NetworkChangeReceiver : BroadcastReceiver(), KoinComponent {
// Use get() for eager injection (receivers are short-lived)
private val networkMonitor: NetworkMonitor by inject()
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
ConnectivityManager.CONNECTIVITY_ACTION -> {
networkMonitor.checkConnectivity()
}
}
}
}
// Register in Activity or Service
class MainActivity : AppCompatActivity() {
private val receiver = NetworkChangeReceiver()
override fun onResume() {
super.onResume()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(receiver, filter)
}
override fun onPause() {
unregisterReceiver(receiver)
super.onPause()
}
}
Statically Registered Receiver (Manifest)
class BootReceiver : BroadcastReceiver(), KoinComponent {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
// Ensure Koin is initialized
val app = context?.applicationContext as? MyApplication
// Now safe to inject
val scheduler: JobScheduler by inject()
scheduler.scheduleWork()
}
}
}
<!-- AndroidManifest.xml -->
<receiver android:name=".BootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Important Considerations
Lifecycle:
- Receivers are extremely short-lived (typically < 10 seconds)
- Use
get()instead ofby inject()for faster initialization - Avoid heavy operations in
onReceive()
Koin Initialization:
- For manifest-declared receivers, ensure Koin is initialized in
Application.onCreate() - For dynamically registered receivers, Koin is already initialized
Best Practices:
class AlarmReceiver : BroadcastReceiver(), KoinComponent {
override fun onReceive(context: Context?, intent: Intent?) {
// Use get() for immediate access (more efficient for receivers)
val repository: AlarmRepository = get()
// Offload work to a Service or WorkManager
val workRequest = OneTimeWorkRequestBuilder<AlarmWorker>()
.setInputData(workDataOf("alarm_id" to intent?.getIntExtra("ID", -1)))
.build()
WorkManager.getInstance(context!!).enqueue(workRequest)
}
}
BroadcastReceivers have strict time limits (~10 seconds). For any significant work, use Service, WorkManager, or JobScheduler instead.
ContentProvider Injection
ContentProviders have special timing considerations because they're created before Application.onCreate().
The Challenge
// ❌ PROBLEM: This won't work!
class MyContentProvider : ContentProvider(), KoinComponent {
// Koin not initialized yet when ContentProvider is created!
private val database: Database by inject() // Will crash
override fun onCreate(): Boolean {
// This runs BEFORE Application.onCreate()
return true
}
}
Solution 1: Lazy Initialization
class UserContentProvider : ContentProvider(), KoinComponent {
// Use lazy to delay initialization
private val database: Database by lazy {
// Koin is ready by the time first query happens
get<Database>()
}
override fun onCreate(): Boolean {
// Don't access injected dependencies here!
return true
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
// Safe to use database here (after Application.onCreate)
return database.query(uri)
}
}
Solution 2: Manual Koin Initialization
class UserContentProvider : ContentProvider(), KoinComponent {
private lateinit var database: Database
override fun onCreate(): Boolean {
// Initialize Koin if not already initialized
val context = context ?: return false
if (GlobalContext.getOrNull() == null) {
startKoin {
androidContext(context.applicationContext)
modules(databaseModule)
}
}
// Now safe to inject
database = get()
return true
}
override fun query(/* ... */): Cursor? {
return database.query(uri)
}
}