Dasar-dasar Dagger

Injeksi dependensi manual atau pencari layanan di aplikasi Android dapat menjadi masalah, bergantung pada ukuran project Anda. Anda dapat membatasi kompleksitas project sembari meningkatkan skalanya dengan menggunakan Dagger untuk mengelola dependensi.

Secara otomatis Dagger akan membuat kode yang meniru kode yang seharusnya akan Anda tulis secara manual. Karena kode dihasilkan pada waktu kompilasi, kode tersebut dapat dilacak dan berperforma lebih baik daripada solusi berbasis refleksi lainnya seperti Panduan.

Manfaat menggunakan Dagger

Dagger membebaskan Anda dari penulisan kode boilerplate yang melelahkan serta rawan error dengan:

  • Menghasilkan kode AppContainer (grafik aplikasi) yang Anda implementasikan secara manual di bagian DI manual.

  • Membuat factory untuk class yang tersedia dalam grafik aplikasi. Inilah cara dependensi terpenuhi secara internal.

  • Menentukan apakah akan menggunakan kembali dependensi atau membuat instance baru melalui penggunaan cakupan.

  • Membuat container untuk alur tertentu seperti yang Anda lakukan dengan alur login di bagian sebelumnya menggunakan subkomponen Dagger. Hal ini akan meningkatkan performa aplikasi Anda dengan merilis objek dalam memori ketika objek tersebut tidak diperlukan lagi.

Secara otomatis Dagger melakukan semua operasi ini pada waktu build selama Anda mendeklarasikan dependensi class dan menentukan cara untuk memenuhinya menggunakan anotasi. Dagger menghasilkan kode yang mirip dengan yang akan Anda tulis secara manual. Secara internal, Dagger membuat grafik objek yang dapat dirujuk untuk menemukan cara dalam menyediakan instance class. Untuk setiap class dalam grafik, Dagger menghasilkan class jenis factory yang digunakannya secara internal untuk mendapatkan instance dari jenis tersebut.

Pada waktu build, Dagger menelusuri kode Anda dan:

  • Membuat dan memvalidasi grafik dependensi, memastikan bahwa:

    • Setiap dependensi objek dapat dipenuhi, sehingga tidak ada pengecualian runtime.
    • Tidak ada siklus dependensi, sehingga tidak ada loop tak terbatas.
  • Menghasilkan class yang digunakan pada waktu proses untuk membuat objek aktual dan dependensinya.

Kasus penggunaan sederhana di Dagger: Membuat factory

Untuk mendemonstrasikan cara bekerja dengan Dagger, buatlah factory sederhana untuk class UserRepository yang ditampilkan dalam diagram berikut:

Tentukan UserRepository seperti berikut:

Kotlin

class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Java

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }

    ...
}

Tambahkan anotasi @Inject ke konstruktor UserRepository sehingga Dagger mengetahui cara membuat UserRepository:

Kotlin

// @Inject lets Dagger know how to create instances of this object
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Java

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    // @Inject lets Dagger know how to create instances of this object
    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

Dalam cuplikan kode di atas, Anda memberi tahu Dagger:

  1. Cara membuat instance UserRepository dengan konstruktor @Inject beranotasi.

  2. Apa saja dependensinya: UserLocalDataSource dan UserRemoteDataSource.

Sekarang Dagger mengetahui cara membuat instance UserRepository, tetapi tidak tahu cara membuat dependensinya. Jika Anda juga menganotasi class lain, Dagger mengetahui cara membuatnya:

Kotlin

// @Inject lets Dagger know how to create instances of these objects
class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }

Java

public class UserLocalDataSource {
    @Inject
    public UserLocalDataSource() { }
}

public class UserRemoteDataSource {
    @Inject
    public UserRemoteDataSource() { }
}

Komponen Dagger

Dagger dapat membuat grafik dependensi dalam project Anda yang dapat digunakan untuk mengetahui tempat mendapatkan dependensi tersebut saat diperlukan. Agar Dagger melakukan ini, Anda harus membuat antarmuka dan menganotasikannya dengan @Component. Dagger membuat container seperti yang akan Anda lakukan dengan injeksi dependensi manual.

Dalam antarmuka @Component, Anda dapat menentukan fungsi yang menampilkan instance class yang Anda perlukan (misalnya UserRepository ). @Component memberi tahu Dagger untuk membuat container dengan semua dependensi yang diperlukan untuk memenuhi jenis yang ditampilkan. Ini disebut komponen Dagger; komponen ini berisi grafik yang terdiri dari objek yang diketahui Dagger tentang cara penyediaan dan masing-masing dependensinya.

Kotlin

// @Component makes Dagger create a graph of dependencies
@Component
interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be provided from the container
    fun repository(): UserRepository
}

Java

// @Component makes Dagger create a graph of dependencies
@Component
public interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be consumed from the graph
    UserRepository userRepository();
}

Saat Anda membuat project, Dagger menghasilkan implementasi antarmuka ApplicationGraph untuk Anda: DaggerApplicationGraph. Dengan pemroses anotasi, Dagger membuat grafik dependensi yang terdiri dari hubungan antara tiga class (UserRepository, UserLocalDatasource, dan UserRemoteDataSource) dengan hanya satu titik masuk: mendapatkan instance UserRepository. Anda dapat menggunakannya seperti berikut:

Kotlin

// Create an instance of the application graph
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
// Grab an instance of UserRepository from the application graph
val userRepository: UserRepository = applicationGraph.repository()

Java

// Create an instance of the application graph
ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

// Grab an instance of UserRepository from the application graph
UserRepository userRepository = applicationGraph.userRepository();

Dagger membuat instance UserRepository baru setiap kali diminta.

Kotlin

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository != userRepository2)

Java

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository != userRepository2)

Terkadang, Anda harus memiliki instance unik dari dependensi dalam container. Anda mungkin menginginkannya karena beberapa alasan:

  1. Anda ingin jenis lain yang memiliki jenis ini sebagai dependensi untuk berbagi instance yang sama, seperti beberapa objek ViewModel dalam alur login menggunakan LoginUserData yang sama.

  2. Pembuatan objek berharga mahal, dan Anda tidak ingin membuat instance baru setiap kali objek tersebut dideklarasikan sebagai dependensi (misalnya, parser JSON).

Dalam contoh ini, Anda mungkin ingin memiliki instance UserRepository unik yang tersedia dalam grafik sehingga setiap kali Anda meminta UserRepository, Anda akan selalu mendapatkan instance yang sama. Ini berguna dalam contoh Anda karena dalam aplikasi sungguhan dengan grafik aplikasi yang lebih kompleks, Anda mungkin memiliki beberapa objek ViewModel, bergantung pada UserRepository dan Anda tidak ingin membuat instance UserLocalDataSource dan UserRemoteDataSource baru setiap kali UserRepository perlu disediakan.

Dalam injeksi dependensi manual, Anda melakukannya dengan meneruskan instance UserRepository yang sama ke konstruktor class ViewModel; tetapi di Dagger, karena tidak menulis kode tersebut secara manual, Anda harus memberi tahu Dagger bahwa Anda ingin menggunakan instance yang sama. Hal ini dapat dilakukan dengan anotasi cakupan.

Pencakupan dengan Dagger

Anda dapat menggunakan anotasi cakupan untuk membatasi masa aktif suatu objek hingga masa aktif komponennya. Ini berarti bahwa instance dependensi yang sama digunakan setiap kali jenis tersebut perlu diberikan.

Untuk memiliki instance UserRepository unik saat Anda meminta repositori di ApplicationGraph, gunakan anotasi cakupan yang sama untuk antarmuka @Component dan UserRepository. Anda dapat menggunakan anotasi @Singleton yang sudah disertakan dalam paket javax.inject yang digunakan oleh Dagger:

Kotlin

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are bound to the life of the graph and so
// the same instance of that type is provided every time the type is requested.
@Singleton
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Java

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@Singleton
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

Atau, Anda dapat membuat dan menggunakan anotasi cakupan kustom. Anda dapat membuat anotasi cakupan seperti berikut:

Kotlin

// Creates MyCustomScope
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope

Java

// Creates MyCustomScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}

Kemudian, Anda dapat menggunakannya seperti sebelumnya:

Kotlin

@MyCustomScope
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@MyCustomScope
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val service: UserService
) { ... }

Java

@MyCustomScope
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

@MyCustomScope
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

Dalam kedua kasus tersebut, objek disediakan dengan cakupan yang sama dengan yang digunakan untuk menganotasi antarmuka @Component. Jadi, setiap kali memanggil applicationGraph.repository(), Anda mendapatkan instance UserRepository yang sama.

Kotlin

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository == userRepository2)

Java

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository == userRepository2)

Kesimpulan

Penting untuk mengetahui manfaat Dagger dan dasar-dasar cara kerjanya sebelum Anda dapat menggunakannya dalam skenario yang lebih rumit.

Di halaman berikutnya, Anda akan mempelajari cara menambahkan aplikasi Dagger ke aplikasi Android.