Skip to main content
Version: 4.2

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.

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

ComponentInjection MethodBuilt-in SupportNotes
ApplicationstartKoin {} in onCreate()✅ YesEntry point for Koin setup
Activityby inject() or get()✅ YesDirect injection support
Fragmentby inject() or get()✅ YesDirect injection support
ViewModelby viewModel()✅ YesLifecycle-aware injection
Serviceby inject() or get()✅ YesDirect injection support
BroadcastReceiverKoinComponent + get()⚠️ ManualImplement KoinComponent
ContentProviderKoinComponent + get()⚠️ ManualSpecial timing considerations
Custom ViewConstructor or KoinComponent⚠️ ManualConsider 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)
}
}
}
info

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()
}
}

// Module definition
val appModule = module {
factoryOf(::UserDetailPresenter)
}
info

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()
}
info

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
}
}

Module Definition

val serviceModule = module {
single { MediaPlayer() }
singleOf(::MusicRepository)
singleOf(::FileDownloader)
}

Lifecycle Considerations

  • Services are long-lived: Use single for 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()
}
}
note

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 of by 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)
}
}
danger

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)
}
}

Best Practices

class DataContentProvider : ContentProvider(), KoinComponent {
// Lazy initialization pattern
private val repository: DataRepository by lazy { get() }

override fun onCreate(): Boolean {
// Minimal initialization
// Don't access Koin here
return true
}

override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
// Koin definitely ready by now
return repository.getData(uri)
}

override fun insert(uri: Uri, values: ContentValues?): Uri? {
return repository.insert(uri, values)
}
}
danger

Critical: ContentProviders are created before Application.onCreate(). Always use lazy initialization or check if Koin is initialized before injecting dependencies.

Custom View Injection

Custom Views can use dependency injection, but it should be approached carefully.

// Domain/ViewModel layer - uses constructor injection
class ChartViewModel(
private val dataRepository: ChartDataRepository
) : ViewModel() {
fun loadChartData(): LiveData<ChartData> {
return dataRepository.getChartData()
}
}

// View layer - receives data, no DI needed
class ChartView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

// No dependencies injected - just draws what it's given
fun setData(data: ChartData) {
// Draw chart
invalidate()
}
}

// Activity wires them together
class ChartActivity : AppCompatActivity() {
private val viewModel: ChartViewModel by viewModel()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val chartView = findViewById<ChartView>(R.id.chart)

viewModel.loadChartData().observe(this) { data ->
chartView.setData(data)
}
}
}

Option 2: KoinComponent (When View Has Complex Logic)

class SmartChartView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr), KoinComponent {

// Only inject if view has significant logic
private val chartRenderer: ChartRenderer by inject()
private val colorScheme: ColorScheme by inject()

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
chartRenderer.render(canvas, colorScheme)
}
}

val viewModule = module {
factoryOf(::ChartRenderer)
singleOf(::ColorScheme)
}

When to Avoid DI in Views

Avoid injecting in Views when:

  • View is purely presentational (just draws data)
  • View is used in layout preview (DI not available in preview)
  • View is inflated from XML (can't pass constructor params)
  • Dependencies can be passed as method parameters

Consider injecting in Views when:

  • View has complex rendering logic that's shared across app
  • View needs configuration that changes per build variant
  • View manages significant state or business logic (though consider moving to ViewModel)

Best Practice: Keep Views Simple

// ❌ Too much logic in View
class UserCardView(context: Context) : FrameLayout(context), KoinComponent {
private val userRepository: UserRepository by inject()
private val imageLoader: ImageLoader by inject()

fun loadUser(userId: String) {
val user = userRepository.getUser(userId) // Business logic in View!
imageLoader.load(user.imageUrl, imageView)
}
}

// ✅ Better: Move logic to ViewModel
class UserViewModel(
private val userRepository: UserRepository,
private val imageLoader: ImageLoader
) : ViewModel() {
fun loadUser(userId: String): UserUiState {
val user = userRepository.getUser(userId)
return UserUiState(user, imageLoader.load(user.imageUrl))
}
}

class UserCardView(context: Context) : FrameLayout(context) {
// Just displays data - no DI needed
fun bind(state: UserUiState) {
textView.text = state.name
imageView.setImageBitmap(state.image)
}
}
info

Recommendation: Prefer keeping Views as "dumb" presentational components. Move business logic to ViewModels or Presenters where constructor injection is cleaner and more testable.

Summary

Choosing the Right Injection Approach

ComponentRecommended ApproachRationale
ApplicationstartKoin {}Entry point - initialize Koin here
Activity/Fragmentby inject() or by viewModel()Built-in support, clean syntax
ViewModelConstructor injection via viewModel {}Best testability
Serviceby inject() or get()Built-in support, long-lived
BroadcastReceiverKoinComponent + get()No built-in support, short-lived
ContentProviderKoinComponent + lazy { get() }Timing issues, use lazy init
Custom ViewAvoid DI, pass data via methodsKeep Views simple, move logic to ViewModel

General Best Practices

  1. Prefer constructor injection for business logic classes (Repositories, UseCases, ViewModels)
  2. Use field injection (by inject()) for Android framework classes (Activities, Fragments, Services)
  3. Implement KoinComponent only when necessary (BroadcastReceivers, ContentProviders, Custom Views)
  4. Use get() over by inject() for short-lived components (BroadcastReceivers)
  5. Keep Views simple - avoid injecting into Views when possible
  6. Watch lifecycle timing - ContentProviders need special handling

Next Steps