Injeksi dependensi dengan Hilt

Hilt adalah library injeksi dependensi untuk Android yang mengurangi boilerplate ketika melakukan injeksi dependensi manual dalam project Anda. Dengan melakukan injeksi dependensi manual, Anda harus membuat setiap class dan dependensinya secara manual, serta menggunakan container untuk menggunakan kembali dan mengelola dependensi.

Hilt menyediakan cara standar untuk menggunakan DI dalam aplikasi Anda dengan menyediakan container untuk setiap class Android dalam project Anda dan mengelola siklus prosesnya secara otomatis. Hilt ditambahkan pada library DI yang populer Dagger untuk mendapatkan manfaat dari ketepatan waktu kompilasi, performa runtime, skalabilitas, dan dukungan Android Studio yang disediakan oleh Dagger. Untuk informasi lebih lanjut, lihat Hilt dan Dagger.

Panduan ini menjelaskan konsep dasar Hilt dan container yang dihasilkannya. Panduan ini juga mencakup demo cara melakukan bootstrap pada aplikasi yang sudah ada untuk menggunakan Hilt.

Menambahkan dependensi

Pertama, tambahkan plugin hilt-android-gradle-plugin ke file build.gradle root project Anda:

Kotlin

plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.57.1" apply false
}

Groovy

plugins {
  ...
  id 'com.google.dagger.hilt.android' version '2.57.1' apply false
}

Kemudian, terapkan plugin Gradle dan tambahkan dependensi ini di file app/build.gradle Anda:

Kotlin

plugins {
  id("com.google.devtools.ksp")
  id("com.google.dagger.hilt.android")
}

android {
  ...
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.57.1")
  ksp("com.google.dagger:hilt-android-compiler:2.57.1")
}

Groovy

...
plugins {
  id 'com.google.devtools.ksp'
  id 'com.google.dagger.hilt.android'
}

android {
  ...
}

dependencies {
  implementation "com.google.dagger:hilt-android:2.57.1"
  ksp "com.google.dagger:hilt-compiler:2.57.1"
}

Untuk memastikan project Anda dikonfigurasi untuk Java 17, yang diperlukan oleh versi Jetpack Compose dan Hilt, tambahkan kode berikut ke file app/build.gradle:

Kotlin

android {
  ...
  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
  }
}

Groovy

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_17
    targetCompatibility JavaVersion.VERSION_17
  }
}

Class aplikasi Hilt

Semua aplikasi yang menggunakan Hilt harus berisi class Application yang dianotasi dengan @HiltAndroidApp.

@HiltAndroidApp memicu pembuatan kode Hilt, termasuk class dasar untuk aplikasi Anda yang berfungsi sebagai container dependensi tingkat aplikasi.

@HiltAndroidApp
class ExampleApplication : Application() { ... }

Komponen Hilt yang dihasilkan ini dipasang ke siklus proses objek Application dan menyediakan dependensi ke objek tersebut. Selain itu, komponen ini adalah komponen induk dari aplikasi, yang berarti bahwa komponen lain dapat mengakses dependensi yang disediakannya.

Menginjeksikan dependensi ke class Android

Setelah Hilt disiapkan di class Application Anda dan komponen tingkat aplikasi tersedia, Hilt dapat menyediakan dependensi ke class Android lain yang memiliki anotasi @AndroidEntryPoint:

@AndroidEntryPoint
class ExampleActivity : ComponentActivity() { ... }

Saat ini Hilt mendukung class Android berikut:

  • Application (dengan menggunakan @HiltAndroidApp)
  • ViewModel (dengan menggunakan @HiltViewModel)
  • Activity
  • Service
  • BroadcastReceiver

Di Compose, Anda tidak perlu membuat anotasi pada setiap composable. Sebagai gantinya, anotasikan ComponentActivity root Anda dengan @AndroidEntryPoint. Hal ini berfungsi sebagai titik entri DI tunggal untuk seluruh hierarki UI, sehingga Anda dapat mengakses ViewModel yang di-inject Hilt secara langsung dalam fungsi composable.

@AndroidEntryPoint menghasilkan komponen Hilt individual untuk setiap class Android dalam project Anda. Komponen ini dapat menerima dependensi dari class induk mereka masing-masing, seperti yang dijelaskan dalam Hierarki komponen.

Untuk mendapatkan dependensi dari komponen, gunakan anotasi @Inject untuk melakukan injeksi kolom:

@AndroidEntryPoint
class ExampleActivity : ComponentActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Class yang diinjeksikan oleh Hilt dapat memiliki class dasar lainnya yang juga menggunakan injeksi. Class tersebut tidak memerlukan anotasi @AndroidEntryPoint jika bersifat abstrak.

Untuk mempelajari lebih lanjut callback siklus proses tempat class Android diinjeksikan, lihat Masa aktif komponen.

Menentukan binding Hilt

Untuk melakukan injeksi kolom, Hilt perlu mengetahui cara menyediakan instance dependensi yang diperlukan dari komponen yang sesuai. Binding berisi informasi yang diperlukan untuk menyediakan instance suatu jenis sebagai dependensi.

Salah satu cara untuk memberikan informasi binding ke Hilt adalah injeksi konstruktor. Gunakan anotasi @Inject pada konstruktor class untuk memberi tahu Hilt cara menyediakan instance class tersebut:

class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

Parameter dari konstruktor anotasi sebuah class adalah dependensi class tersebut. Dalam contoh ini, AnalyticsAdapter memiliki AnalyticsService sebagai dependensi. Oleh karena itu, Hilt juga harus mengetahui cara menyediakan instance AnalyticsService.

Modul Hilt

Terkadang suatu jenis tidak dapat di-injeksikan melalui konstruktor. Hal ini dapat terjadi karena beberapa alasan. Misalnya, Anda tidak dapat menginjeksikan antarmuka melalui konstruktor. Anda juga tidak dapat menginjeksikan jenis yang tidak Anda miliki melalui konstruktor, seperti class dari library eksternal. Dalam hal ini, Anda dapat memberikan informasi binding kepada Hilt dengan menggunakan modul Hilt.

Modul Hilt adalah class yang dianotasi dengan @Module. Ini memberikan petunjuk kepada Hilt tentang cara membuat instance jenis yang tidak dapat disediakan melalui injeksi konstruktor, seperti antarmuka atau class pihak ketiga. Anda juga harus menganotasi setiap modul dengan @InstallIn untuk memberi tahu Hilt class Android mana yang akan digunakan atau dipasang dalam setiap modul.

Dependensi yang Anda berikan di modul Hilt tersedia di semua komponen yang dihasilkan dan dikaitkan dengan class Android tempat Anda memasang modul Hilt.

Menginjeksikan instance antarmuka dengan @Binds

Pertimbangkan contoh AnalyticsService. Jika AnalyticsService adalah antarmuka, Anda tidak dapat menginjeksikannya melalui konstruktor. Sebagai gantinya, berikan informasi binding kepada Hilt dengan cara membuat fungsi abstrak yang dianotasi dengan @Binds di dalam modul Hilt.

Anotasi @Binds akan memberi tahu Hilt implementasi mana yang akan digunakan ketika Hilt harus menyediakan instance antarmuka.

Fungsi yang dianotasi menyediakan informasi berikut kepada Hilt:

  • Fungsi jenis nilai yang ditampilkan memberi tahu Hilt antarmuka mana yang instance-nya disediakan oleh fungsi.
  • Parameter fungsi memberi tahu Hilt implementasi mana yang harus disediakan.
interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

Modul Hilt AnalyticsModule dianotasi dengan @InstallIn(ActivityComponent.class) karena Anda ingin Hilt menginjeksikan dependensi tersebut ke ExampleActivity. Anotasi ini berarti bahwa semua dependensi dalam AnalyticsModule tersedia di semua aktivitas aplikasi.

Menginjeksikan instance dengan @Provides

Antarmuka bukanlah satu-satunya kasus saat Anda tidak dapat memasukkan jenis melalui konstruktor. Injeksi Konstruktor juga tidak dapat dilakukan jika Anda bukan pemilik class karena class itu berasal dari library eksternal (class seperti Retrofit, OkHttpClient, atau database Room), atau jika instance harus dibuat dengan pola builder.

Pertimbangkan contoh sebelumnya. Jika Anda tidak secara langsung memiliki class AnalyticsService, Anda dapat memberi tahu Hilt cara menyediakan instance jenis ini dengan cara membuat fungsi di dalam modul Hilt dan menganotasi fungsi itu dengan @Provides.

Fungsi yang dianotasi menyediakan informasi berikut kepada Hilt:

  • Fungsi jenis nilai yang ditampilkan memberi tahu Hilt antarmuka mana yang instance-nya disediakan oleh fungsi.
  • Parameter fungsi memberi tahu Hilt dependensi dari jenis yang sesuai.
  • Isi fungsi memberi tahu Hilt cara menyediakan instance dari jenis yang sesuai. Hilt menjalankan isi fungsi setiap kali harus menyediakan instance dari jenis tersebut.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Menyediakan beberapa binding untuk jenis yang sama

Ketika Anda membutuhkan Hilt untuk menyediakan berbagai implementasi dari jenis yang sama sebagai dependensi, Anda harus memberikan beberapa binding kepada Hilt. Anda dapat menentukan beberapa binding untuk jenis yang sama dengan penentu.

Penentu adalah anotasi yang Anda gunakan agar dapat mengidentifikasi binding tertentu untuk suatu jenis ketika jenis itu memiliki beberapa binding yang telah ditetapkan.

Pertimbangkan contohnya. Jika Anda perlu meng-intercept panggilan ke AnalyticsService, Anda dapat menggunakan objek OkHttpClient dengan interceptor. Untuk layanan lain, Anda mungkin perlu meng-intercept panggilan dengan cara lain. Dalam hal ini, Anda perlu memberi tahu Hilt cara menyediakan dua implementasi OkHttpClient yang berbeda.

Pertama, tentukan penentu yang akan Anda gunakan untuk menganotasi metode @Binds atau @Provides:

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient

Kemudian, Hilt perlu mengetahui cara menyediakan instance dari jenis yang sesuai dengan setiap penentu. Dalam hal ini, Anda dapat menggunakan modul Hilt dengan @Provides. Kedua metode memiliki persamaan dalam jenis nilai yang ditampilkan, tetapi penentu melabelinya sebagai dua binding yang berbeda:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @OtherInterceptorOkHttpClient
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}

Anda dapat menginjeksikan jenis tertentu yang dibutuhkan dengan menganotasi kolom atau parameter dengan penentu yang sesuai:

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
  @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...

// At field injection.
@AndroidEntryPoint
class ExampleActivity: ComponentActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}

Sebagai praktik terbaik, jika Anda menambahkan penentu ke suatu jenis, tambahkan penentu ke semua cara yang memungkinkan untuk menyediakan dependensi tersebut. Membiarkan implementasi dasar atau umum tanpa penentu akan rawan terhadap error dan dapat menyebabkan Hilt memasukkan dependensi yang salah.

Penentu yang telah ditetapkan dalam Hilt

Hilt menyediakan beberapa penentu yang telah ditetapkan. Misalnya, karena Anda mungkin memerlukan class Context dari aplikasi atau aktivitas, Hilt menyediakan penentu @ApplicationContext dan @ActivityContext.

Misalkan class AnalyticsAdapter dari contoh membutuhkan konteks aktivitas. Kode berikut menunjukkan cara menyediakan konteks aktivitas untuk AnalyticsAdapter:

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }

Untuk binding lain yang telah ditetapkan sebelumnya dan tersedia di Hilt, lihat Binding default komponen.

Komponen yang dihasilkan untuk class Android

Untuk setiap class Android tempat Anda dapat melakukan injeksi kolom, ada komponen Hilt terkait yang dapat Anda lihat dalam anotasi @InstallIn. Setiap komponen Hilt bertanggung jawab untuk menginjeksikan binding ke kelas Android yang sesuai.

Contoh sebelumnya menunjukkan penggunaan ActivityComponent modul dalam Hilt.

Hilt memberikan komponen berikut:

Komponen Hilt Injektor untuk
SingletonComponent Application
ActivityRetainedComponent T/A
ViewModelComponent ViewModel
ActivityComponent Activity
ServiceComponent Service

Masa aktif komponen

Hilt secara otomatis membuat dan menghancurkan instance dari class komponen yang dihasilkan setelah siklus proses class Android terkait.

Komponen yang dihasilkan Dibuat pada Dihancurkan pada
SingletonComponent Application#onCreate() Application dihancurkan
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent ViewModel dibuat ViewModel dihancurkan
ActivityComponent Activity#onCreate() Activity#onDestroy()
ServiceComponent Service#onCreate() Service#onDestroy()

Cakupan komponen

Secara default, semua binding di Hilt adalah tanpa cakupan. Ini berarti setiap kali aplikasi Anda meminta binding, Hilt membuat instance baru dari jenis yang diperlukan.

Dalam contoh ini, setiap kali Hilt memberikan AnalyticsAdapter sebagai dependensi ke jenis lain atau melalui injeksi kolom (seperti di ExampleActivity), Hilt memberikan instance baru AnalyticsAdapter.

Namun, Hilt juga memungkinkan binding untuk tercakup dalam komponen tertentu. Hilt hanya membuat binding cakupan sekali saja per instance dari komponen tempat binding tercakup, dan semua permintaan untuk binding itu memiliki instance yang sama.

Tabel di bawah mencantumkan anotasi cakupan untuk setiap komponen yang dihasilkan:

Class Android Komponen yang dihasilkan Cakupan
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Service ServiceComponent @ServiceScoped

Dalam contoh ini, jika Anda cakupkan AnalyticsAdapter ke ActivityComponent menggunakan @ActivityScoped, Hilt akan memberikan instance AnalyticsAdapter yang sama selama masa aktif aktivitas yang terkait:

@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

Misalkan AnalyticsService memiliki status internal yang mewajibkan instance yang sama digunakan setiap saat—bukan hanya di ExampleActivity, tetapi di mana saja dalam aplikasi. Dalam hal ini, cakupan AnalyticsService sesuai dengan SingletonComponent. Hasilnya, setiap kali komponen perlu menyediakan instance AnalyticsService, komponen akan menyediakan instance yang sama setiap saat.

Contoh berikut menunjukkan cara mengatur cakupan binding ke komponen dalam modul Hilt. Cakupan binding harus cocok dengan cakupan komponen tempatnya diinstal, sehingga dalam contoh ini Anda harus menginstal AnalyticsService di SingletonComponent, bukan ActivityComponent:

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Untuk mempelajari cakupan komponen Hilt lebih lanjut, lihat Cakupan di Android dan Hilt.

Hierarki komponen

Menginstal modul ke dalam komponen memungkinkan binding-nya untuk diakses sebagai dependensi binding lain dalam komponen itu atau dalam komponen turunan di bawahnya dalam hierarki komponen.

ActivityComponent berada di bawah
    ActivityRetainedComponent. ViewModelComponent berada di bawah
    ActivityPersistedComponent. ActivityPersistedComponent dan ServiceComponent
    berada di bawah SingletonComponent.
Gambar 1. Hierarki komponen yang dihasilkan Hilt.

Binding default komponen

Setiap komponen Hilt dilengkapi dengan serangkaian binding default yang dapat diinjeksikan oleh Hilt sebagai dependensi ke binding kustom Anda sendiri. Perhatikan bahwa binding ini sesuai dengan jenis aktivitas umum, bukan dengan subclass tertentu. Ini karena Hilt menggunakan definisi komponen aktivitas tunggal untuk menginjeksikan semua aktivitas. Setiap aktivitas memiliki instance yang berbeda dari komponen ini.

Komponen Android Binding default
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent Application, Activity
ServiceComponent Application, Service

Binding konteks aplikasi juga tersedia menggunakan @ApplicationContext. Contoh:

class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }

Binding konteks aktivitas juga tersedia menggunakan @ActivityContext. Contoh:

class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: ComponentActivity
) { ... }

Menginjeksikan dependensi dalam class yang tidak didukung oleh Hilt

Di Compose, pola standar adalah menyuntikkan dependensi ke dalam @HiltViewModel menggunakan injeksi konstruktor, lalu menggunakan hiltViewModel() di dalam composable untuk mengakses ViewModel. Meskipun Hilt mendukung class Android yang paling umum, Anda mungkin masih menemukan class yang tidak didukung dan memerlukan injeksi kolom.

Dalam kasus itu, Anda dapat membuat titik entri menggunakan anotasi @EntryPoint. Titik entri adalah batas antara kode yang dikelola oleh Hilt dan yang tidak. Ini adalah titik di mana kode pertama kali masuk ke grafik objek yang dikelola Hilt. Titik masuk memungkinkan Hilt untuk menggunakan kode yang tidak dikelola Hilt untuk menyediakan dependensi dalam grafik dependensi.

Misalnya, Hilt tidak mendukung penyedia konten secara langsung. Jika Anda ingin penyedia konten menggunakan Hilt untuk mendapatkan beberapa dependensi, Anda perlu menentukan antarmuka yang dianotasi dengan @EntryPoint untuk setiap jenis binding yang diinginkan dan menyertakan penentu. Lalu tambahkan @InstallIn untuk menentukan komponen tempat menginstal titik masuk sebagai berikut:

class ExampleContentProvider : ContentProvider() {

  @EntryPoint
  @InstallIn(SingletonComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }

  ...
}

Untuk mengakses titik masuk, gunakan metode statis yang sesuai dari EntryPointAccessors. Parameter harus berupa instance komponen atau objek @AndroidEntryPoint yang berfungsi sebagai pemegang komponen. Pastikan bahwa komponen yang Anda teruskan sebagai parameter dan metode statis EntryPointAccessors cocok dengan class Android dalam anotasi @InstallIn pada antarmuka @EntryPoint:

class ExampleContentProvider: ContentProvider() {
    ...

  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)

    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}

Dalam contoh ini, Anda harus menggunakan ApplicationContext untuk mengambil titik masuk karena titik masuk diinstal di SingletonComponent. Jika binding yang ingin Anda ambil berada di ActivityComponent, Anda perlu menggunakan ActivityContext sebagai gantinya.

Hilt dan Dagger

Hilt adalah library yang direkomendasikan secara resmi untuk injeksi dependensi di Android. Library ini menyediakan cara yang standar, berpendapat, dan efisien untuk menerapkan injeksi dependensi di aplikasi Anda, yang dioptimalkan secara khusus untuk Jetpack Compose dan arsitektur satu aktivitas.

Tujuan Hilt adalah sebagai berikut:

  • Membuat serangkaian komponen dan cakupan standar untuk memudahkan penyiapan, keterbacaan, dan berbagi kode antar-aplikasi.
  • Memberikan cara yang mudah dalam menyediakan binding yang berbeda untuk berbagai jenis build, seperti pengujian, debug, atau rilis.

Karena sistem operasi Android membuat instance untuk berbagai class framework-nya sendiri, menggunakan Dagger di aplikasi Android mengharuskan Anda untuk menulis sejumlah besar boilerplate. Hilt mengurangi kode boilerplate yang terlibat dalam penggunaan Dagger pada aplikasi Android. Secara otomatis Hilt menghasilkan dan menyediakan hal berikut:

  • Komponen untuk mengintegrasikan class framework Android dengan Dagger yang seharusnya Anda buat sendiri.
  • Anotasi cakupan yang akan digunakan bersama komponen yang dihasilkan Hilt secara otomatis.
  • Binding yang telah ditetapkan sebelumnya untuk mewakili class Android seperti Application atau Activity.
  • Penentu yang telah ditetapkan sebelumnya untuk mewakili @ApplicationContext dan @ActivityContext.

Kode Dagger dan Hilt dapat berdampingan dalam codebase yang sama. Namun, dalam sebagian besar kasus, sebaiknya gunakan Hilt untuk mengelola semua penggunaan Dagger di Android. Untuk memigrasikan project yang menggunakan Dagger ke Hilt, lihat panduan migrasi.

Referensi lainnya

Untuk mempelajari Hilt lebih lanjut, lihat referensi tambahan berikut.

Contoh

Blog

Melihat konten