ข้อมูลเบื้องต้นเกี่ยวกับกริช

บริการหรือการแทรกทรัพยากร Dependency ด้วยตนเอง ตัวระบุตำแหน่งในแอป Android อาจทำให้เกิดปัญหาได้ ทั้งนี้ขึ้นอยู่กับขนาด คุณสามารถจำกัดความซับซ้อนของโปรเจ็กต์เมื่อปรับขนาดได้โดยใช้ Dagger เพื่อจัดการทรัพยากร Dependency

Dagger จะสร้างโค้ดที่เลียนแบบโค้ดที่คุณจะทำโดยอัตโนมัติ ที่เขียนด้วยลายมือ เนื่องจากโค้ดจะสร้างขึ้นในเวลาคอมไพล์ จึงสามารถตรวจสอบย้อนกลับได้ และมีประสิทธิภาพมากกว่าโซลูชันอื่นๆ ที่อาศัยการสะท้อนความรู้สึก เช่น Guice

ประโยชน์ของการใช้ Dagger

Dagger จะช่วยหลีกเลี่ยงการเขียนโค้ดต้นแบบที่น่าเบื่อหน่ายและมีโอกาสเกิดข้อผิดพลาดได้ง่ายๆ โดยทำดังนี้

  • การสร้างโค้ด AppContainer (กราฟแอปพลิเคชัน) ที่คุณดำเนินการด้วยตนเอง นำไปใช้ในส่วน DI ด้วยตนเอง

  • การสร้างโรงงานสําหรับชั้นเรียนที่มีอยู่ในกราฟของแอปพลิเคชัน ช่วงเวลานี้ คือการตอบสนองภายในทรัพยากร Dependency อย่างไร

  • ตัดสินใจว่าจะใช้ทรัพยากร Dependency ซ้ำหรือสร้างอินสแตนซ์ใหม่ผ่าน การใช้ขอบเขต

  • การสร้างคอนเทนเนอร์สำหรับขั้นตอนเฉพาะเหมือนกับที่คุณทำกับขั้นตอนการเข้าสู่ระบบใน ส่วนก่อนหน้าโดยใช้องค์ประกอบย่อยของ Dagger ซึ่งจะช่วยปรับปรุงประสิทธิภาพ โดยปล่อยออบเจ็กต์ในหน่วยความจำเมื่อไม่จำเป็นต้องใช้อีกต่อไป

Dagger จะทำทุกอย่างนี้โดยอัตโนมัติที่เวลาสร้าง ตราบใดที่คุณ ประกาศทรัพยากร Dependency ของคลาสและระบุวิธีตอบสนองด้วยคลาส คำอธิบายประกอบ Dagger จะสร้างโค้ดที่คล้ายคลึงกับโค้ดที่คุณอาจเขียนไว้ ด้วยตนเอง จากนั้น Dagger จะสร้างกราฟของวัตถุที่ใช้อ้างอิงได้ เพื่อค้นหาวิธีจัดทำอินสแตนซ์ของชั้นเรียน สำหรับทุกชั้นเรียนในกราฟ Dagger จะสร้างคลาสประเภทจากโรงงานที่ใช้ ภายในเพื่อรับอินสแตนซ์ของประเภทนั้นๆ

ขณะสร้าง Dagger จะค่อยๆ ตรวจสอบโค้ดของคุณและ:

  • สร้างและตรวจสอบกราฟทรัพยากร Dependency เพื่อให้มั่นใจว่า

    • สามารถตอบสนองทรัพยากร Dependency ของออบเจ็กต์ทุกรายการได้ ดังนั้นจึงไม่มีรันไทม์ ข้อยกเว้น
    • ไม่มีรอบการขึ้นต่อกัน จึงไม่มีการวนซ้ำ
  • สร้างคลาสที่ใช้ขณะรันไทม์เพื่อสร้างออบเจ็กต์จริงและทรัพยากร Dependency ของออบเจ็กต์

กรณีการใช้งานง่ายๆ ใน Dagger: การสร้างโรงงาน

เพื่อแสดงให้เห็นว่าคุณจะทำงานกับ Dagger ได้อย่างไร ลองสร้าง factory ของคลาส UserRepository ที่แสดงใน แผนภาพต่อไปนี้

กําหนด UserRepository ดังนี้

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

    ...
}

เพิ่มคำอธิบายประกอบ @Inject ลงในตัวสร้าง UserRepository เพื่อให้ Dagger รู้ วิธีสร้าง 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;
    }
}

ในข้อมูลโค้ดด้านบน คุณกำลังบอก Dagger ว่า

  1. วิธีสร้างอินสแตนซ์ UserRepository ที่มีคำอธิบายประกอบ @Inject เครื่องมือสร้างขึ้นมา

  2. ทรัพยากร Dependency คืออะไร: UserLocalDataSource และ UserRemoteDataSource

ตอนนี้ Dagger รู้วิธีสร้างอินสแตนซ์ของ UserRepository แล้ว แต่มันไม่ รู้วิธีสร้างทรัพยากร Dependency ได้ หากคุณใส่คำอธิบายประกอบ ในชั้นเรียนอื่นด้วย Dagger รู้วิธีสร้างแท็ก

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

ส่วนประกอบของเครื่องหมายกริช

Dagger สามารถสร้างกราฟของทรัพยากร Dependency ในโปรเจ็กต์ของคุณได้ ใช้เพื่อดูว่าควรรับทรัพยากร Dependency ได้จากที่ใดเมื่อจำเป็น ในการทำให้ Dagger ทำเช่นนี้ คุณต้องสร้างอินเทอร์เฟซและทำคำอธิบายประกอบ @Component Dagger จะสร้างคอนเทนเนอร์เหมือนที่คุณทำด้วยตัวเอง Dependency Injection

ภายในอินเทอร์เฟซ @Component คุณสามารถกำหนดฟังก์ชันที่แสดงผล อินสแตนซ์ของคลาสที่คุณต้องการ (เช่น UserRepository) @Component บอก Dagger จะสร้างคอนเทนเนอร์ที่มีทรัพยากร Dependency ทั้งหมดที่จำเป็นเพื่อให้เป็นไปตามข้อกำหนด ที่แสดงออกมา วิธีนี้เรียกว่าคอมโพเนนต์กริช ประกอบด้วย กราฟที่ประกอบด้วยวัตถุต่างๆ ที่ Dagger รู้วิธีแสดง ระบุและทรัพยากร Dependency ที่เกี่ยวข้อง

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

เมื่อคุณสร้างโปรเจ็กต์ Dagger จะสร้างการติดตั้งใช้งานของ อินเทอร์เฟซ ApplicationGraph สำหรับคุณ: DaggerApplicationGraph ด้วย ตัวประมวลผลคำอธิบายประกอบ Dagger จะสร้างกราฟทรัพยากร Dependency ที่ประกอบด้วย ความสัมพันธ์ระหว่างทั้ง 3 คลาส (UserRepository UserLocalDatasource และ UserRemoteDataSource) ที่มีจุดแรกเข้าเพียงจุดเดียว: กำลังรับอินสแตนซ์ UserRepository วิธีการคือ

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 จะสร้างอินสแตนซ์ใหม่ของ UserRepository ทุกครั้งที่มีการขอ

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)

บางครั้ง คุณก็ต้องมีอินสแตนซ์ที่ไม่ซ้ำกันของทรัพยากร Dependency ในคอนเทนเนอร์ ซึ่งอาจเกิดจากหลายสาเหตุดังนี้

  1. คุณต้องการให้ประเภทอื่นๆ ที่มีประเภทนี้เป็นทรัพยากร Dependency แชร์เหมือนกันหรือไม่ เช่น ออบเจ็กต์ ViewModel หลายรายการในขั้นตอนการเข้าสู่ระบบที่ใช้ออบเจ็กต์เดียวกัน LoginUserData

  2. การสร้างออบเจ็กต์มีราคาแพงและคุณไม่ต้องการสร้างออบเจ็กต์ใหม่ ทุกครั้งที่มีการประกาศว่าเป็นทรัพยากร Dependency (เช่น โปรแกรมแยกวิเคราะห์ JSON)

ในตัวอย่างนี้ คุณอาจต้องการอินสแตนซ์ที่ไม่ซ้ำกันของ UserRepository อยู่ในกราฟ ดังนั้นทุกครั้งที่คุณขอ UserRepository จะได้รับอินสแตนซ์เดียวกันเสมอ ซึ่งจะเป็นประโยชน์ในตัวอย่างของคุณเนื่องจาก การใช้งานจริงที่มีกราฟ ของแอปพลิเคชันที่ซับซ้อนขึ้น ออบเจ็กต์ ViewModel หลายรายการขึ้นอยู่กับ UserRepository ซึ่งคุณไม่ต้องการ เพื่อสร้างอินสแตนซ์ใหม่ของ UserLocalDataSource และ UserRemoteDataSource ทุกครั้งที่ต้องระบุ UserRepository

ในการแทรกทรั��ยากร Dependency �����������นเอง คุณจะทำได้โดยการส่งผ่าน UserRepository ให้กับตัวสร้างของคลาส ViewModel แต่ ใน Dagger เนื่องจากคุณไม่ได้เขียนโค้ดเอง คุณจะต้องปล่อยให้ Dagger รู้ว่าคุณต้องการใช้อินสแตนซ์เดียวกัน ซึ่งทำได้ด้วยขอบเขต คำอธิบายประกอบ

กำหนดขอบเขตด้วยกริช

คุณสามารถใช้คำอธิบายประกอบขอบเขตเพื่อจำกัดอายุการใช้งานของออบเจ็กต์เป็นอายุการ��ช้งาน ของส่วนประกอบ ซึ่งหมายความว่าระบบจะใช้อินสแตนซ์เดียวกันของทรัพยากร Dependency ทุกครั้งที่ต้องระบุประเภทดังกล่าว

เพื่อให้มีอินสแตนซ์ที่ไม่ซ้ำกันของ UserRepository เมื่อคุณขอที่เก็บ ใน ApplicationGraph ให้ใช้คำอธิบายประกอบขอบเขตเดียวกันสำหรับ @Component ของอินเทอร์เฟซและUserRepository คุณสามารถใช้คำอธิบายประกอบ @Singleton ที่ มาพร้อมกับแพ็คเกจ javax.inject ที่ 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;
    }
}

หรือคุณจะสร้างและใช้คำอธิบายประกอบขอบเขตที่กำหนดเองก็ได้ คุณสามารถสร้างคำอธิบายประกอบขอบเขตได้ดังนี้

Kotlin

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

Java

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

จากนั้นคุณจะใช้งานได้ตามเดิมดังนี้

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

ในทั้ง 2 กรณี ออบเจ็กต์จะมีขอบเขตเดียวกับที่ใช้ใส่คำอธิบายประกอบ อินเทอร์เฟซของ @Component ดังนั้น ทุกครั้งที่��ุณโทร applicationGraph.repository() คุณจะได้รับอินสแตนซ์เดียวกันของ UserRepository

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)

บทสรุป

สิ่งสำคัญคือการตระหนักถึงประโยชน์ของ Dagger และพื้นฐานการทำงานของ Dagger ก่อนที่จะใช้งานในสถานการณ์ที่ซับซ้อนยิ่งขึ้นได้

ในหน้าถัดไป คุณจะได้เรียนรู้วิธีเพิ่ม Dagger ในแอปพลิเคชัน Android