โปรเจ็กต์ที่มีโมดูล Gradle หลายรายการเรียกว่าโปรเจ็กต์ที่มีหลายโมดูล
ในโปรเจ็กต์หลายโมดูลที่จัดส่งเป็น APK เดี่ยวโดยไม่มีฟีเจอร์
จึงเป็นเรื่องปกติที่จะมีโมดูล app
ที่ขึ้นอยู่กับ
โมดูลของโครงการ และโมดูล base
หรือ core
ที่ส่วนที่เหลือของ
โมดูลมักจะขึ้นอยู่กับ โดยทั่วไปโมดูล app
จะมี
Application
ส่วน base
โมดูลมีชั้นเรียนทั่วไปทั้งหมดที่แชร์ในทุกโมดูลในโปรเจ็กต์ของคุณ
โดยโมดูล app
เป็นตำแหน่งที่ดีในการประกาศคอมโพเนนต์ของแอปพลิเคชัน (สำหรับ
เช่น ApplicationComponent
ในรูปภาพด้านล่าง) ที่ระบุออบเจ็กต์
ที่คอมโพเนนต์อื่นๆ อาจต้องการ รวมถึงซิงเกิลตันของแอป ในฐานะ
เช่น คลาสอย่าง OkHttpClient
, โปรแกรมแยกวิเคราะห์ JSON, ตัวเข้าถึงฐานข้อมูล
หรือ SharedPreferences
ออบเจ็กต์ที่อาจกำหนดไว้ในโมดูล core
จะจัดเตรียมโดย ApplicationComponent
ที่กำหนดไว้ในโมดูล app
ในโมดูล app
คุณยังมีคอมโพเนนต์อื่นๆ ที่มีอายุการใช้งานสั้นได้ด้วย
ตัวอย่างเช่น UserComponent
ที่มีการกำหนดค่าเฉพาะผู้ใช้
(เช่น UserSession
) หลังจากเข้าสู่ระบบ
ในโมดูลต่างๆ ของโครงการ คุณสามารถระบุอย่างน้อยหนึ่งรายการ คอมโพเนนต์ย่อยที่มีตรรกะเฉพาะต่อโมดูลนั้นดังที่แสดงในรูปที่ 1
![](https://developer.android.google.cn/static/images/training/dependency-injection/5-graph-modules.png?hl=th)
รูปที่ 1 ตัวอย่างกราฟ Dagger ใน โปรเจ็กต์หลายโมดูล
ตัวอย่างเช่น ในโมดูล login
คุณอาจมี LoginComponent
กำหนดขอบเขตโดยมีคำอธิบายประกอบ @ModuleScope
ที่กำหนดเองซึ่งระบุออบเจ็กต์ที่พบได้ทั่วไป
กับฟีเจอร์นั้น เช่น LoginRepository
ในโมดูลนี้ คุณยังสามารถ
มีคอมโพเนนต์อื่นๆ ที่ขึ้นอยู่กับ LoginComponent
ที่มีแอตทริบิวต์
ตัวอย่างเช่น @FeatureScope
สำหรับ LoginActivityComponent
หรือ
TermsAndConditionsComponent
ที่กำหนดขอบเขตลอจิกเฉพาะฟีเจอร์ได้มากขึ้น
เช่น ViewModel
ออบเจ็กต์
สำหรับโมดูลอื่นๆ เช่น Registration
คุณจะต้องตั้งค่าที่คล้ายกัน
กฎทั่วไปสำหรับโปรเจ็กต์หลายโมดูลคือโมดูลในระดับเดียวกัน ไม่ควรพึ่งพากัน หากมี ให้พิจารณาว่า (ทรัพยากร Dependency ระหว่าง 2 อย่างนี้) ควรเป็นส่วนหนึ่งของโมดูลระดับบน หากใช่ เปลี่ยนโครงสร้างภายในโค้ดเพื่อย้ายชั้นเรียนไปยังโมดูลระดับบนสุด หากไม่เป็นเช่นนั้น ให้สร้างโมดูลใหม่ ซึ่งขยายโมดูลหลักและมีโมดูลเดิมทั้ง 2 โมดูลขยายโมดูล โมดูลใหม่
ตามแนวทางปฏิบัติแนะนำ โดยทั่วไปคุณจะสร้างคอมโพเนนต์ใน ในกรณีต่อไปนี้
คุณต้องแทรกข้อมูลในช่องเช่นเดียวกับ
LoginActivityComponent
คุณต้องกำหนดขอบเขตออบเจ็กต์ เช่นเดียวกับ
LoginComponent
หากไม่มีกรณีเหล่านี้เลยและคุณต้องบอก Dagger ว่าจะให้ส่งอย่างไร
จากโมดูลดังกล่าว ให้สร้างและแสดงโมดูล Dagger ด้วย @Provides
หรือ
@Binds
เมธอด หากการแทรกการก่อสร้างสำหรับคลาสเหล่านั้นใช้ไม่ได้
การใช้งานด้วยคอมโพเนนต์ย่อย Dagger
หน้าเอกสารการใช้ Dagger ในแอป Android พูดถึงวิธีสร้างและใช้
คอมโพเนนต์ย่อย อย่างไรก็ตาม คุณไม่สามารถใช้รหัสเดียวกันได้ เนื่องจาก
โมดูลฟีเจอร์ไม่ทราบเกี่ยวกับโมดูล app
ตัวอย่างเช่น ถ้าคุณคิดว่า
เกี่ยวกับขั้นตอนการเข้าสู่ระบบโดยทั่วไป และโค้ดที่เรามีในหน้าก่อนหน้านี้
คอมไพล์เพิ่มเติมอื่นๆ ดังนี้
Kotlin
class LoginActivity: Activity() { ... override fun onCreate(savedInstanceState: Bundle?) { // Creation of the login graph using the application graph loginComponent = (applicationContext as MyDaggerApplication) .appComponent.loginComponent().create() // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this) ... } }
Java
public class LoginActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { // Creation of the login graph using the application graph loginComponent = ((MyApplication) getApplicationContext()) .appComponent.loginComponent().create(); // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this); ... } }
เนื่องจากโมดูล login
ไม่ทราบเกี่ยวกับ MyApplication
หรือ
appComponent
หากต้องการให้ใช้งานได้ คุณต้องกำหนดอินเทอร์เฟซในฟีเจอร์นี้
ที่ให้ FeatureComponent
ซึ่ง MyApplication
ต้องการ
เพื่อนำไปปฏิบัติ
ในตัวอย่างต่อไปนี้ คุณจะกำหนดอินเทอร์เฟซ LoginComponentProvider
ได้
ที่ระบุ LoginComponent
ในโมดูล login
สำหรับโฟลว์การเข้าสู่ระบบ
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
ตอนนี้ LoginActivity
จะใช้อินเทอร์เฟซนั้นแทนข้อมูลโค้ด
ตามที่ระบุไว้ข้างต้น
Kotlin
class LoginActivity: Activity() { ... override fun onCreate(savedInstanceState: Bundle?) { loginComponent = (applicationContext as LoginComponentProvider) .provideLoginComponent() loginComponent.inject(this) ... } }
Java
public class LoginActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { loginComponent = ((LoginComponentProvider) getApplicationContext()) .provideLoginComponent(); loginComponent.inject(this); ... } }
ตอนนี้ MyApplication
จะต้องนำอินเทอร์เฟซดังกล่าวมาใช้
วิธีการที่จำเป็น
Kotlin
class MyApplication: Application(), LoginComponentProvider { // Reference to the application graph that is used across the whole app val appComponent = DaggerApplicationComponent.create() override fun provideLoginComponent(): LoginComponent { return appComponent.loginComponent().create() } }
Java
public class MyApplication extends Application implements LoginComponentProvider { // Reference to the application graph that is used across the whole app ApplicationComponent appComponent = DaggerApplicationComponent.create(); @Override public LoginComponent provideLoginComponent() { return appComponent.loginComponent.create(); } }
นี่คือวิธีใช้คอมโพเนนต์ย่อย Dagger ในโปรเจ็กต์หลายโมดูล เมื่อใช้โมดูลฟีเจอร์ โซลูชันจะแตกต่างกันเพราะ จะขึ้นอยู่กับแต่ละแบบ
ทรัพยากร Dependency ของคอมโพเนนต์ที่มีโมดูลฟีเจอร์
ในโมดูลฟีเจอร์ โดยปกติแล้วโมดูลต่างๆ จะขึ้นอยู่กับ
จะกลับด้านกัน แทนที่โมดูล app
รวมถึงฟีเจอร์
โมดูล โมดูลฟีเจอร์จะขึ้นอยู่กับโมดูล app
ดูรูปที่ 2
เพื่อดูการนำเสนอว่าโมดูลมีโครงสร้างอย่างไร
![](https://developer.android.google.cn/static/images/training/dependency-injection/5-graph-dynamic-modules.png?hl=th)
รูปที่ 2 ตัวอย่างกราฟ Dagger ใน โปรเจ็กต์ที่มีโมดูลฟีเจอร์
ใน Dagger คอมโพเนนต์จำเป็นต้องทราบเกี่ยวกับองค์ประกอบย่อยของตัวเอง ข้อมูลนี้
รวมอยู่ในโมดูล Dagger ที่เพิ่มในคอมโพเนนต์หลัก (เช่น
SubcomponentsModule
ในการใช้ Dagger ในแอป Android)
แต่ด้วยทรัพยากร Dependency แบบย้อนกลับระหว่างแอปและ
โมดูลฟีเจอร์ คอมโพเนนต์ย่อยไม่ปรากฏจากโมดูล app
เนื่องจาก
ไม่ได้อยู่ในเส้นทางบิลด์ ตัวอย่างเช่น LoginComponent
ที่กำหนดไว้ใน
โมดูลฟีเจอร์ login
ไม่สามารถเป็นองค์ประกอบย่อยของ
ApplicationComponent
ที่กำหนดไว้ในโมดูล app
Dagger มีกลไกที่เรียกว่าทรัพยากร Dependency ของคอมโพเนนต์ ซึ่งคุณใช้เพื่อ แก้ปัญหานี้ได้ แทนที่จะเป็นคอมโพเนนต์ย่อยของ คอมโพเนนต์หลัก คอมโพเนนต์ย่อยจะขึ้นอยู่กับคอมโพเนนต์หลัก ด้วย ว่าไม่มีความสัมพันธ์แบบแม่กับลูก ตอนนี้ คอมโพเนนต์จะขึ้นอยู่กับผู้ใช้รายอื่น เพื่อดูการขึ้นต่อกันบางอย่าง คอมโพเนนต์ต้องแสดงประเภทจากกราฟ สำหรับคอมโพเนนต์ที่เกี่ยวข้องเพื่อใช้งาน
เช่น โมดูลฟีเจอร์ชื่อ login
ต้องการสร้าง
LoginComponent
ที่ขึ้นอยู่กับ AppComponent
ที่มีใน
app
โมดูล Gradle
ด้านล่างนี้เป็นคำจำกัดความของชั้นเรียนและ AppComponent
ที่เป็นส่วนหนึ่งของ
โมดูล Gradle ของ app
:
Kotlin
// UserRepository's dependencies class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... } // UserRepository is scoped to AppComponent @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } @Singleton @Component interface AppComponent { ... }
Java
// UserRepository's dependencies public class UserLocalDataSource { @Inject public UserLocalDataSource() {} } public class UserRemoteDataSource { @Inject public UserRemoteDataSource() { } } // UserRepository is scoped to AppComponent @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; } } @Singleton @Component public interface ApplicationComponent { ... }
ในโมดูล Gradle ของ login
ที่มีโมดูล Gradle app
คุณมี
LoginActivity
ที่ต้องแทรกอินสแตนซ์ LoginViewModel
:
Kotlin
// LoginViewModel depends on UserRepository that is scoped to AppComponent class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
// LoginViewModel depends on UserRepository that is scoped to AppComponent public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
LoginViewModel
มีทรัพยากร Dependency ของ UserRepository
ที่พร้อมใช้งานและ
กำหนดขอบเขตเป็น AppComponent
ลองสร้าง LoginComponent
ที่ขึ้นอยู่กับ
AppComponent
เพื่อแทรก LoginActivity
:
Kotlin
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = [AppComponent::class]) interface LoginComponent { fun inject(activity: LoginActivity) }
Java
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = AppComponent.class) public interface LoginComponent { void inject(LoginActivity loginActivity); }
LoginComponent
ระบุทรัพยากร Dependency สำหรับ AppComponent
ด้วยการเพิ่มทรัพยากรดังกล่าวลงใน
พารามิเตอร์ Dependency ของคำอธิบายประกอบคอมโพเนนต์ เนื่องจาก LoginActivity
จะ
Dagger จะแทรกเมธอด ให้เพิ่มเมธอด inject()
ลงในอินเทอร์เฟซ
เมื่อสร้าง LoginComponent
อินสแตนซ์ของ AppComponent
ต้องเป็น
ผ่านเข้ามา โดยใช้โรงงานผลิตคอมโพเนนต์เพื่อดำเนินการ
Kotlin
@Component(dependencies = [AppComponent::class]) interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent fun create(appComponent: AppComponent): LoginComponent } fun inject(activity: LoginActivity) }
Java
@Component(dependencies = AppComponent.class) public interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent LoginComponent create(AppComponent appComponent); } void inject(LoginActivity loginActivity); }
ตอนนี้ LoginActivity
สามารถสร้างอินสแตนซ์ของ LoginComponent
และเรียกเมธอด
inject()
วิธี
Kotlin
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Gets appComponent from MyApplication available in the base Gradle module val appComponent = (applicationContext as MyApplication).appComponent // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this) super.onCreate(savedInstanceState) // Now you can access loginViewModel } }
Java
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Gets appComponent from MyApplication available in the base Gradle module AppComponent appComponent = ((MyApplication) getApplicationContext()).appComponent; // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this); // Now you can access loginViewModel } }
LoginViewModel
ขึ้นอยู่กับ UserRepository
และเพื่อให้ LoginComponent
เข้าถึงได้จาก AppComponent
โดย AppComponent
ต้องเปิดเผยใน
อินเทอร์เฟซ:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
กฎขอบเขตที่มีคอมโพเนนต์อ้างอิงทำงานในลักษณะเดียวกับ
คอมโพเนนต์ย่อย เนื่องจาก LoginComponent
ใช้อินสแตนซ์ของ AppComponent
จะใช้คำอธิบายประกอบขอบเขตเดียวกันไม่ได้
หากต้องการกำหนดขอบเขตระดับ LoginViewModel
เป็น LoginComponent
คุณจะต้องทำในฐานะ
ก่อนหน้านี้คุณใช้คำอธิบายประกอบ @ActivityScope
ที่กำหนดเอง
Kotlin
@ActivityScope @Component(dependencies = [AppComponent::class]) interface LoginComponent { ... } @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
@ActivityScope @Component(dependencies = AppComponent.class) public interface LoginComponent { ... } @ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
แนวทางปฏิบัติแนะนำ
ApplicationComponent
ควรอยู่ในโมดูลapp
เสมอสร้างคอมโพเนนต์ Dagger ในโมดูลหากต้องการดำเนินการแทรกฟิลด์ ในโมดูลนี้ หรือคุณต้องกำหนดขอบเขตออบเจ็กต์สำหรับขั้นตอนที่เฉพาะเจาะจงของ แอปพลิเคชันของคุณ
สำหรับโมดูล Gradle ที่ใช้สำหรับยูทิลิตีหรือตัวช่วยต่างๆ และไม่จำเป็นต้องใช้ เพื่อสร้างกราฟ (คุณต้องมีส่วนประกอบของ Dagger) สร้างและแสดง โมดูล Dagger สาธารณะที่มีเมธอด @Provides และ @Binds ของคลาสเหล่านั้น ไม่สนับสนุนการแทรกตัวสร้าง
หากต้องการใช้ Dagger ในแอป Android ที่มีโมดูลฟีเจอร์ ให้ใช้คอมโพเนนต์ ของทรัพยากร Dependency เพื่อเข้าถึงทรัพยากร Dependency ที่ระบุโดย
ApplicationComponent
ที่กำหนดไว้ในโมดูลapp