Categories
Programming

Getting to Know Hilt: Dependency Injection Library for Android

Artikel ini juga tersedia dalam Bahasa Indonesia.

I’ve written about dependency injection (D.I) a couple of times on this blog. First about understanding Dagger (in Indonesian) and second about a simple way to understand D.I. and why it is needed. The latest development is that Google is now recommending a new library for implementing D.I. It’s called Hilt:

Technically the video above is plenty enough to introduce Hilt, so at this point I could have ended this article 😆

Nevertheless, I’ll try to make a shorter summary below with the hope that it will make it simpler to understand from a beginner’s perspective.

Essentialy, Hilt is meant to help with adding D.I. in our application. There’s some automated processes that it does so that we don’t have to manually add dependency injection.

Why should we have Hilt when Dagger already exists? From what I understand, the main idea is to simplify. And Hilt itself is made with Dagger at its base. According to Dagger’s site:

Hilt provides a standard way to incorporate Dagger dependency injection into an Android application.

The goals of Hilt are:

– To simplify Dagger-related infrastructure for Android apps.

– To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps.

– To provide an easy way to provision different bindings to various build types (e.g. testing, debug, or release).

For those who haven’t went deep with Dagger, I think learning Hilt instead will be more efficient and pragmatic (unless if in practice you will be dealing with existing codebases that already use Dagger.)

If we look at the new navigation menu hierarchy on the Dependency Injection page on the Android training page, we can see that Hilt is shown earlier than Dagger, giving it a bit more perceived importance than Dagger.

Basic Concepts for D.I. Frameworks

If you haven’t learned about Dependency Injection whatsoever, please open and read this article first, before continuing here.

The common technique in creating D.I. is by using the idea of container. When creating D.I. manually, container will be a class that is supposed to be the central place where all dependencies are generated. An example from Android training:

// Container of objects shared across the whole app
class AppContainer {

    // Since you want to expose userRepository out of the container, you need to satisfy
    // its dependencies as you did before
    private val retrofit = Retrofit.Builder()
                            .baseUrl("https://example.com")
                            .build()
                            .create(LoginService::class.java)

    private val remoteDataSource = UserRemoteDataSource(retrofit)
    private val localDataSource = UserLocalDataSource()

    // userRepository is not private; it'll be exposed
    val userRepository = UserRepository(localDataSource, remoteDataSource)
}

Next, the class is instantiated within Application(), which acts as the entry point of the app, so that subsequent components like fragments or activites can use the container to get their dependencies.

At its core, a D.I. framework like Hilt is supposed to make the process above easier. It will automatically generate the container class for us, as well as help inject the dependencies (hence why it’s called Dependency Injection) into the components that need them.

Basic Concepts for Hilt

Roughly speaking, first we need to teach Hilt a few things to it can automate things for us. As commonly used in Android programming, we use annotation for this.

First, the @HiltAndroidApp annotation is used to teach Hilt where the Application() instance is in our app:

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

The second annotation that’s needed is @AndroidEntryPoint. It is used to signify components that will need some injections:

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

The third important annotation is @Inject. It has two roles. First, it is used to “tag” dependencies that will eventually be injected into components:

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

Second, it is also used within components to actually do the injection:

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
 @Inject lateinit var analytics: AnalyticsAdapter
 ...
}

Going Forward with Hilt

The three annotations above are the most basic things we need to get started with Hilt. Going further, there are different cases that will require further setup with Hilt. Cases such as:

  • D.I. related with interface
  • D.I. for dependencies with third-party or external libraries like Retrofit or Room database
  • D.I. with more than one implementation for the same type to be injected.

The cases above are beyond this current article. There is a docunemtation page here for further information.

References and Sources

Categories
Bahasa Indonesia Programming

Perkenalan dengan Hilt: Library Dependency Injection untuk Android

Saya sudah beberapa kali menulis soal dependency injection (D.I.) di blog ini. Masih yang basic-basic saja, pertama soal memahami Dagger dan kedua soal cara sederhana memahami D.I dan mengapa dia dibutuhkan. Berita terbaru, ada library baru dari Google untuk membantu mempermudah mengaplikasikan D.I. di app kita. Namanya Hilt:

Sebenarnya video di atas sudah sangat cukup untuk memperkenalkan Hilt, jadi sampai di sini artikel ini sebenarnya bisa ditutup 😆

Tetapi berikut saya akan mengikhtisarkan sedikit dengan harapan agar dia lebih mudah dipahami lagi.

Jadi Hilt pada intinya difungsikan untuk membantu proses menambahkan D.I. di aplikasi kita. Ada proses otomatisasi di dalamnya agar kita tidak perlu melakukan D.I. secara manual.

Mengapa harus ada Hilt kalau sudah ada Dagger? Di sini sepertinya kata kuncinya adalah penyederhanaan. Hilt sendiri dibuat menggunakan Dagger sebagai basisnya. Menurut situs Dagger:

Hilt provides a standard way to incorporate Dagger dependency injection into an Android application.

The goals of Hilt are:

  • To simplify Dagger-related infrastructure for Android apps.
  • To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps.
  • To provide an easy way to provision different bindings to various build types (e.g. testing, debug, or release).

Hilt menyediakan cara standar untuk menambahkan Dagger dependency injection ke dalam sebuah aplikasi Android.

Tujuan Hilt antara lain:

  • Menyederhanakan infrastuktur yang terkait dengan Dagger dalam aplikasi Android
  • Membuat set komponen dan scopes yang standar untuk memudahkan dalam setup, membaca/memahami kode, dan berbagi kode dalam banyak aplikasi.
  • Menyediakan cara yang mudah untuk membuat binding yang berbeda-beda untuk build types yang berbeda (misalkan unutk testing, debug, atau release).

Bagi yang belum terlalu mendalami Dagger, saya rasa mempelajari Hilt akan lebih efisien dan pragmatis (kecuali jika pada prakteknya harus berhadapan dengan kode yang dari awal sudah menggunakan Dagger).

Jika kita melihat dari hierarki menu di halaman Dependency Injection di Android training milik Google, Hilt ditampilkan lebih awal dari Dagger, yang sepertinya secara halus menunjukkan kalau Google ingin mendorong orang-orang untuk memakai Hilt.

Konsep dasar D.I. Frameworks

Bagi yang belum paham D.I. sama sekali, silakan membaca artikel ini dulu, baru kembali ke sini.

Teknik umum dalam membuat D.I. adalah dengan menggunakan konsep kontainer. Apabila membuat D.I. secara manual, kontainer adalah class yang bertugas menjadi tempat semua dependensi dibuat. Contoh dari Android:

// Container of objects shared across the whole app
class AppContainer {

    // Since you want to expose userRepository out of the container, you need to satisfy
    // its dependencies as you did before
    private val retrofit = Retrofit.Builder()
                            .baseUrl("https://example.com")
                            .build()
                            .create(LoginService::class.java)

    private val remoteDataSource = UserRemoteDataSource(retrofit)
    private val localDataSource = UserLocalDataSource()

    // userRepository is not private; it'll be exposed
    val userRepository = UserRepository(localDataSource, remoteDataSource)
}

Kemudian, class ini dibuat instance-nya di dalam Application(), yang bertindak sebagai pintu masuk aplikasi, sehingga komponen-komponen berikutnya seperti fragments atau activities bisa menggunakan kontainer tersebut untuk membuat dependensi.

Sebuah D.I. framework seperti Hilt pada intinya bertugas membuat proses di atas lebih mudah. Dia bisa membuatkan class kontainer untuk kita, sekaligus “menyuntikkan” dependensi (dari sini istilah Injection di D.I. berasal) ke dalam komponen-komponen yang membutuhkannya.

Konsep dasar Hilt

Secara kasar, kita perlu memberi tahu Hilt bagaimana caranya agar dia bisa membuatkan kontainer dan menginjeksi dependensi secara otomatis. Caranya, seperti yang umum digunakan di pemrograman Android, adalah dengan menggunakan anotasi/annotation.

Pertama, anotasi @HiltAndroidApp untuk memberi tahu Hilt di mana Application() instance di aplikasi kita:

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

Anotasi kedua yang diperlukan adalah @AndroidEntryPoint. Ini untuk memberi tanda kepada komponen yang membutuhkan injeksi:

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

Anotasi ketiga yang juga penting adalah @Inject. Dia punya dua tugas. Pertama, untuk memberi tanda kepada dependensi-dependensi yang nantinya perlu diinjeksi ke dalam sebuah komponen:

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

Kedua, dia juga dipakai di dalam komponen di mana injeksi harus dilakukan:

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
 @Inject lateinit var analytics: AnalyticsAdapter
 ...
}

Lebih Lanjut dengan Hilt

Tiga jenis anotasi di atas pada dasarnya sudah cukup untuk memulai menggunakan Hilt. Lebih lanjut, ada kasus-kasus lain yang membutuhkan setup lebih banyak untuk dikerjakan dengan Hilt. Kasus-kasusnya seperti:

  • D.I. terkait dengan interface
  • D.I. untuk dependensi yang berasal dari third-party atau library eksternal seperti Retrofit atau Room database
  • D.I. dengan lebih dari satu implementasi untuk satu type yang sama

Artikel ini tidak membahas kasus-kasus di atas. Ada dokumentasi lebih lanjut di sini bagi yang tertarik.

Referensi dan Sumber-sumber