โปรเจ็กต์ที่มีโมดูล Gradle หลายรายการเรียกว่าโปรเจ็กต์ที่มีหลายโมดูล
ในโปรเจ็กต์หลายโมดูลที่จัดส่งเป็น APK เดี่ยวโดยไม่มีฟีเจอร์
จึงเป็นเรื่องปกติที่จะมีโมดูล app ที่ขึ้นอยู่กับ
โมดูลของโครงการ และโมดูล base หรือ core ที่ส่วนที่เหลือของ
โมดูลมักจะขึ้นอยู่กับ โดยทั่วไปโมดูล app จะมี
Application ส่วน base
โมดูลมีชั้นเรียนทั่วไปทั้งหมดที่แชร์ในทุกโมดูลในโปรเจ็กต์ของคุณ
โดยโมดูล app เป็นตำแหน่งที่ดีในการประกาศคอมโพเนนต์ของแอปพลิเคชัน (สำหรับ
เช่น ApplicationComponent ในรูปภาพด้านล่าง) ที่ระบุออบเจ็กต์
ที่คอมโพเนนต์อื่นๆ อาจต้องการ รวมถึงซิงเกิลตันของแอป ในฐานะ
เช่น คลาสอย่าง OkHttpClient, โปรแกรมแยกวิเคราะห์ JSON, ตัวเข้าถึงฐานข้อมูล
หรือ SharedPreferences ออบเจ็กต์ที่อาจกำหนดไว้ในโมดูล core
จะจัดเตรียมโดย ApplicationComponent ที่กำหนดไว้ในโมดูล app
ในโมดูล app คุณยังมีคอมโพเนนต์อื่นๆ ที่มีอายุการใช้งานสั้นได้ด้วย
ตัวอย่างเช่น UserComponent ที่มีการกำหนดค่าเฉพาะผู้ใช้
(เช่น UserSession) หลังจากเข้าสู่ระบบ
ในโมดูลต่างๆ ของโครงการ คุณสามารถระบุอย่างน้อยหนึ่งรายการ คอมโพเนนต์ย่อยที่มีตรรกะเฉพาะต่อโมดูลนั้นดังที่แสดงในรูปที่ 1
รูปที่ 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
เพื่อดูการนำเสนอว่าโมดูลมีโครงสร้างอย่างไร
รูปที่ 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