ชุดแอป Kriptofolio - ตอนที่ 4
การพึ่งพาการฉีดจะช่วยปรับปรุงโค้ดของคุณได้อย่างมาก ทำให้โค้ดของคุณเป็นแบบโมดูลาร์ ยืดหยุ่น และสามารถทดสอบได้ อันที่จริงชื่อมันดูซับซ้อนกว่าความคิดที่อยู่ข้างหลังอีก
ในส่วนนี้ของชุดข้อมูล เราจะเรียนรู้เกี่ยวกับการฉีดพึ่งพา จากนั้นเราจะนำไปใช้ในแอป “Kriptofolio” (ก่อนหน้านี้คือ “My Crypto Coins”) เราจะใช้ Dagger 2 Dagger 2 เป็นเฟรมเวิร์กการพึ่งพาโอเพนซอร์สที่ได้รับความนิยมมากที่สุดสำหรับ Android นี่เป็นทักษะอันล้ำค่าที่ต้องมีสำหรับการสร้างแอปสมัยใหม่ แม้จะคิดว่าช่วงการเรียนรู้นั้นยากพอ
เนื้อหาซีรีส์
- บทนำ:แผนงานในการสร้างแอป Android ที่ทันสมัยในปี 2018–2019
- ส่วนที่ 1:การแนะนำหลักการ SOLID
- ส่วนที่ 2:วิธีเริ่มสร้างแอป Android:การสร้าง Mockups, UI และเลย์เอาต์ XML
- ส่วนที่ 3:ทั้งหมดเกี่ยวกับสถาปัตยกรรมนั้น:สำรวจรูปแบบสถาปัตยกรรมต่างๆ และวิธีใช้งานในแอปของคุณ
- ส่วนที่ 4:วิธีใช้งาน Dependency Injection ในแอปของคุณด้วย Dagger 2 (คุณอยู่ที่นี่)
- ส่วนที่ 5:จัดการ RESTful Web Services โดยใช้ Retrofit, OkHttp, Gson, Glide และ Coroutines
การฉีดการพึ่งพาคืออะไร
เพื่ออธิบายการแทรกการพึ่งพาก่อนอื่น เราต้องเข้าใจความหมายของการพึ่งพาในการเขียนโปรแกรม การพึ่งพาอาศัยกันคือเมื่อวัตถุตัวใดตัวหนึ่งขึ้นอยู่กับการใช้งานวัตถุอื่นอย่างเป็นรูปธรรม คุณสามารถระบุการขึ้นต่อกันในโค้ดของคุณได้ทุกครั้งที่คุณสร้างอินสแตนซ์อ็อบเจ็กต์ภายในโค้ดอื่น มาดูตัวอย่างการใช้งานจริงกัน
class MyAppClass() {
private val library: MyLibrary = MyLibrary(true)
...
}
class MyLibrary(private val useSpecialFeature: Boolean) {
...
}
ดังที่คุณเห็นจากตัวอย่างนี้ ชั้นเรียนของคุณ MyAppClass
จะขึ้นอยู่กับการกำหนดค่าที่เป็นรูปธรรมและการใช้งานคลาสไลบรารีของคุณโดยตรง MyLibrary
. จะทำอย่างไรถ้าคุณต้องการใช้ห้องสมุดบุคคลที่สามแทนสักวันหนึ่ง จะเป็นอย่างไรถ้าคุณต้องการมีคลาสอื่นที่คุณต้องการใช้การกำหนดค่าไลบรารีเดียวกันทุกประการ ทุกครั้งที่คุณจะต้องค้นหารหัสของคุณ ค้นหาตำแหน่งที่แน่นอนและเปลี่ยนรหัส เป็นเพียงตัวอย่างบางส่วนเท่านั้น
แนวคิดก็คือการเชื่อมต่อที่แน่นแฟ้นระหว่างส่วนประกอบของแอปพลิเคชันจะทำให้การพัฒนาของคุณทำงานหนักขึ้นเมื่อโครงการของคุณเติบโตขึ้น เพื่อหลีกเลี่ยงปัญหา ลองใช้การเติมการพึ่งพาเพื่อคลายการมีเพศสัมพันธ์ตามที่อธิบายไว้
class MyAppClass(private val library: MyLibrary) {
...
}
class MyLibrary(private val useSpecialFeature: Boolean) {
...
}
แค่นั้นแหละ นั่นคือตัวอย่างการฉีดพึ่งพาดั้งเดิมมาก แทนที่จะสร้างและกำหนดค่าใหม่ MyLibrary
วัตถุคลาสภายในคลาสของคุณ MyAppClass
คุณเพียงแค่ผ่านหรือฉีดเข้าไปในตัวสร้าง ดังนั้น MyAppClass
สามารถขาดความรับผิดชอบโดยสิ้นเชิงสำหรับ MyLibrary
.
Dagger 2 คืออะไร
Dagger เป็นเฟรมเวิร์กการพึ่งพาโอเพนซอร์สแบบสแตติก เวลาคอมไพล์ เวลาคอมไพล์สำหรับทั้ง Java และ Android ในบทความนี้ฉันจะพูดถึงเวอร์ชันที่สองที่ Google ดูแลรักษา Square สร้างเวอร์ชันก่อนหน้า
Dagger 2 ถือเป็นหนึ่งในเฟรมเวิร์กการพึ่งพาอาศัยที่มีประสิทธิภาพที่สุดที่สร้างขึ้นมาจนถึงปัจจุบัน ที่จริงแล้วถ้าคุณเปรียบเทียบ Dagger 1, Dagger 2 และ Dagger 2.10 คุณจะพบว่าการใช้งานแต่ละครั้งต่างกัน คุณต้องเรียนรู้ใหม่ทุกครั้งเนื่องจากมีการเปลี่ยนแปลงที่สำคัญโดยผู้เขียน เมื่อเขียนบทความนี้ ฉันใช้เวอร์ชัน Dagger 2.16 และเราจะเน้นที่มันเท่านั้น
ตามที่คุณเข้าใจเกี่ยวกับการฉีดการพึ่งพาแล้ว คลาสของเราไม่ควรสร้างหรือมีการพึ่งพา แต่พวกเขาจำเป็นต้องได้รับทุกอย่างจากภายนอก ดังนั้นเมื่อใช้ Dagger 2 เฟรมเวิร์กนี้จะจัดเตรียมการพึ่งพาที่จำเป็นทั้งหมด
ทำได้โดยการสร้างรหัสสำเร็จรูปจำนวนมากสำหรับเรา รหัสที่สร้างขึ้นนั้นจะสามารถตรวจสอบย้อนกลับได้อย่างสมบูรณ์และจะเลียนแบบรหัสที่ผู้ใช้อาจเขียนด้วยมือ Dagger 2 เขียนด้วย Java และโค้ดที่สร้างโดยตัวประมวลผลคำอธิบายประกอบจะเป็นโค้ด Java ด้วย
อย่างไรก็ตาม มันใช้งานได้กับ Kotlin โดยไม่มีปัญหาหรือการปรับเปลี่ยนใดๆ โปรดจำไว้ว่า Kotlin สามารถทำงานร่วมกับ Java ได้อย่างสมบูรณ์ หากเปรียบเทียบกับเฟรมเวิร์กที่คล้ายคลึงกัน Dagger 2 นั้นไดนามิกน้อยกว่า มันทำงานในเวลารวบรวมมากกว่าเวลาทำงานที่มีการสะท้อนกลับ ไม่มีการใช้การสะท้อนเลย นั่นหมายความว่าเฟรมเวิร์กนี้จะตั้งค่าและเรียนรู้ได้ยากขึ้น มันจะเพิ่มประสิทธิภาพด้วยความปลอดภัยในการคอมไพล์
การฉีดการพึ่งพาแบบแมนนวลโดยไม่ต้องใช้เครื่องมือ
คุณอาจสังเกตเห็นในซอร์สโค้ดของแอป My Crypto Coins จากส่วนก่อนหน้านี้ว่ามีโค้ดบางส่วนสำหรับการฉีดออบเจ็กต์โดยไม่ต้องใช้เครื่องมือฉีดพึ่งพา มันใช้งานได้ดี และโซลูชันนี้จะดีพอสำหรับแอปขนาดเล็กเช่นนี้ ดูแพ็คเกจยูทิลิตี้:
/**
* Static methods used to inject classes needed for various Activities and Fragments.
*/
object InjectorUtils {
private fun getCryptocurrencyRepository(context: Context): CryptocurrencyRepository {
return CryptocurrencyRepository.getInstance(
AppDatabase.getInstance(context).cryptocurrencyDao())
}
fun provideMainViewModelFactory(
application: Application
): MainViewModelFactory {
val repository = getCryptocurrencyRepository(application)
return MainViewModelFactory(application, repository)
}
fun provideAddSearchViewModelFactory(
context: Context
): AddSearchViewModelFactory {
val repository = getCryptocurrencyRepository(context)
return AddSearchViewModelFactory(repository)
}
}
อย่างที่คุณเห็นคลาสนี้จะทำทุกอย่าง จะสร้างโรงงาน ViewModel สำหรับกิจกรรมหรือชิ้นส่วนที่ต้องการ
/**
* Factory for creating a [MainViewModel] with a constructor that takes a
* [CryptocurrencyRepository].
*/
class MainViewModelFactory(private val application: Application, private val repository: CryptocurrencyRepository) : ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(application, repository) as T
}
}
จากนั้นคุณใช้ InjectorUtils
คลาสเช่นนี้ที่คุณต้องได้รับโรงงาน ViewModel เฉพาะ:
/**
* A placeholder fragment containing a simple view.
*/
class MainListFragment : Fragment() {
...
private lateinit var viewModel: MainViewModel
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupList()
...
}
...
private fun subscribeUi(activity: FragmentActivity) {
// This is the old way how we were injecting code before using Dagger.
val factory = InjectorUtils.provideMainViewModelFactory(activity.application)
// Obtain ViewModel from ViewModelProviders, using parent activity as LifecycleOwner.
viewModel = ViewModelProviders.of(activity, factory).get(MainViewModel::class.java)
...
}
}
ตามที่คุณเห็น MainListFragment
. ของเรา ชั้นเรียนไม่รู้เรื่อง CryptocurrencyRepository
หรือ AppDatabase
. ได้โรงงานที่สร้างสำเร็จจากคลาส InjectorUtils อันที่จริง นี่เป็นวิธีง่ายๆ วิธีหนึ่งที่จะทำ เราจะกำจัดมันและเรียนรู้วิธีตั้งค่าเครื่องมือ Dagger 2 สำหรับการฉีดพึ่งพาขั้นสูง หากแอปนี้จะขยายการทำงานและโค้ด ฉันไม่สงสัยเลยว่าเราจะเริ่มเห็นประโยชน์อย่างรวดเร็วจากการใช้เฟรมเวิร์กการพึ่งพาแบบมืออาชีพแทนโซลูชันแบบแมนนวล
เรามาลบ InjectorUtils
. กันเถอะ เรียนตอนนี้และเรียนรู้วิธีตั้งค่า Dagger 2 ในซอร์สโค้ดของแอป My Crypto Coins
Dependency Injection สำหรับ MVVM ด้วย Kotlin
วิธีตั้งค่า Dagger 2 ด้วย ViewModels, กิจกรรม และ Fragments
ตอนนี้เราจะพูดถึงการตั้งค่า Dagger 2 ทีละขั้นตอนในโครงการแอป My Crypto Coins
ในการเริ่มต้น คุณควรเปิดใช้งานเครื่องมือประมวลผลคำอธิบายประกอบของ Kotlin (kapt) จากนั้นเพิ่มการพึ่งพา Dagger 2 พิเศษ
คุณสามารถทำได้โดยเพิ่มบรรทัดเหล่านี้ในไฟล์ gradle ของคุณ:
apply plugin: 'kotlin-kapt' // For annotation processing
...
implementation "com.google.dagger:dagger:$versions.dagger"
implementation "com.google.dagger:dagger-android:$versions.dagger"
implementation "com.google.dagger:dagger-android-support:$versions.dagger"
kapt "com.google.dagger:dagger-compiler:$versions.dagger"
kapt "com.google.dagger:dagger-android-processor:$versions.dagger"
ปลั๊กอิน Kapt จะช่วยให้คอมไพเลอร์สร้างคลาส stub ที่จำเป็นสำหรับการทำงานร่วมกันระหว่าง Java และ Kotlin เพื่อความสะดวก เราจะกำหนดเวอร์ชันกริช 2 ที่เป็นรูปธรรมในไฟล์ gradle แยกต่างหาก เช่นเดียวกับที่เราทำกับการอ้างอิงทั้งหมดของเรา
def versions = [:]
versions.dagger = "2.16"
ext.versions = versions
หากต้องการค้นหาเวอร์ชันล่าสุดที่มีให้ตรวจสอบการวางจำหน่ายที่พื้นที่เก็บข้อมูลอย่างเป็นทางการของ Dagger 2 บน Github
ตอนนี้ สร้างแอปพลิเคชันของคุณ App
ระดับ.
ข้ามสิ่งนี้ถ้าคุณมีชุดคลาสนี้อยู่แล้ว หลังจากที่คุณทำเสร็จแล้ว เราจะปล่อยให้มันเป็นไปสักพัก แต่กลับมาทีหลัง
class App : Application() {
override fun onCreate() {
super.onCreate()
}
}
สำหรับแอป My Crypto Coins เราได้สร้างคลาสแอปพลิเคชันก่อนหน้านี้แล้ว
ถัดไป อัปเดตไฟล์รายการของคุณเพื่อเปิดใช้งาน App
ระดับ.
ข้ามสิ่งนี้หากคุณเคยทำมาก่อน
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.baruckis.mycryptocoins">
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
...
สำหรับแอป My Crypto Coins เราได้ตั้งค่า App
. แล้ว ในรายการก่อนหน้านี้
ตอนนี้เรามาสร้างแพ็คเกจใหม่ชื่อ dependencyinjection
.
เราจะเก็บไฟล์ทั้งหมดที่เกี่ยวข้องกับการนำ Dagger ไปใช้งาน
สร้าง AppModule
โมดูลคลาสที่จะให้การพึ่งพาทั่วทั้งแอปพลิเคชันของคุณ
/**
* AppModule will provide app-wide dependencies for a part of the application.
* It should initialize objects used across our application, such as Room database, Retrofit, Shared Preference, etc.
*/
@Module(includes = [ViewModelsModule::class])
class AppModule() {
@Singleton // Annotation informs Dagger compiler that the instance should be created only once in the entire lifecycle of the application.
@Provides // Annotation informs Dagger compiler that this method is the constructor for the Context return type.
fun provideContext(app: App): Context = app // Using provide as a prefix is a common convention but not a requirement.
@Singleton
@Provides
fun provideCryptocurrencyRepository(context: Context): CryptocurrencyRepository {
return CryptocurrencyRepository.getInstance(AppDatabase.getInstance(context).cryptocurrencyDao())
}
}
อย่างที่คุณเห็น ในการสร้างโมดูลกริช เราจำเป็นต้องใส่คำอธิบายประกอบด้วย @Module
. พิเศษ คำอธิบายประกอบ โครงการมักจะมีโมดูลกริชหลายโมดูล เป็นเรื่องปกติสำหรับหนึ่งในนั้นที่จะให้การพึ่งพาทั่วทั้งแอป AppModule
นี้ จะถูกใช้เพื่อเริ่มต้นวัตถุที่ใช้ในแอปพลิเคชันของเรา เช่น ฐานข้อมูลห้อง ชุดติดตั้งเพิ่มเติม การตั้งค่าที่ใช้ร่วมกัน ฯลฯ
ตัวอย่างเช่น เราสามารถพูดคุยเกี่ยวกับสถานการณ์ทั่วไปสำหรับ AppModule เพื่อจัดเตรียมวัตถุบริบท ในกรณีที่เราต้องการให้เข้าถึงได้จากทุกที่ในแอปของเรา มาวิเคราะห์โค้ดกันเพื่อดูวิธีการทำ
เราจำเป็นต้องใช้คำอธิบายประกอบกริชพิเศษ @Provides
. มันบอกกริชว่าเมธอดจัดให้มีการพึ่งพาเฉพาะประเภท ในกรณีของเรา วัตถุบริบท ดังนั้นเมื่ออยู่ที่ไหนสักแห่งในแอปที่เราขอให้ใส่บริบท AppModule เป็นที่ที่ Dagger ค้นพบ และไม่สำคัญว่าชื่อวิธีการของเราจะเป็นอย่างไร เนื่องจากกริชสนใจเฉพาะประเภทการส่งคืนเท่านั้น เป็นวิธีปฏิบัติทั่วไปในการตั้งชื่อเมธอดโดยใช้คำนำหน้า แต่จะเป็นอะไรก็ได้ที่คุณต้องการ
@Singleton
คำอธิบายประกอบที่คุณเห็นว่าใช้กับวิธีการเดียวกันนั้นไม่ได้เป็นส่วนหนึ่งของคำอธิบายประกอบกริช มีอยู่ภายในแพ็คเกจ javax คำอธิบายประกอบนี้บอก Dagger ว่าควรมีอินสแตนซ์ของการพึ่งพานั้นเพียงอินสแตนซ์เดียวเท่านั้น
คุณไม่จำเป็นต้องเขียนรหัสต้นแบบเพื่อตรวจสอบว่ามีอินสแตนซ์อื่นของวัตถุอยู่แล้วหรือไม่ เมื่อสร้างรหัส Dagger จะจัดการกับตรรกะทั้งหมดนั้นสำหรับคุณเนื่องจากคำอธิบายประกอบนี้ โปรดสังเกตว่า AppModule ของเรามีโมดูลอื่น ViewModelsModule มาสร้างมันกันเถอะ
สร้าง ViewModelsModule
โมดูลคลาส โมดูลนี้จะรับผิดชอบในการจัดหา ViewModels ทั่วทั้งแอปพลิเคชันของคุณ
/**
* Will be responsible for providing ViewModels.
*/
@Module
abstract class ViewModelsModule {
// We'd like to take this implementation of the ViewModel class and make it available in an injectable map with MainViewModel::class as a key to that map.
@Binds
@IntoMap
@ViewModelKey(MainViewModel::class) // We use a restriction on multibound map defined with @ViewModelKey annotation, and if don't need any, we should use @ClassKey annotation provided by Dagger.
abstract fun bindMainViewModel(mainViewModel: MainViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(AddSearchViewModel::class)
abstract fun bindAddSearchViewModel(addSearchViewModel: AddSearchViewModel): ViewModel
@Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
โมดูลนี้ใช้การผูกแบบหลายแมปคุณลักษณะของ Dagger 2 การใช้มันทำให้เรามีส่วนร่วมกับวัตถุที่เราเลือกไว้ในแผนที่ที่สามารถฉีดได้ทุกที่ในแอพของเรา การใช้คำอธิบายประกอบกริชร่วมกัน @Binds
, @IntoMap
และคำอธิบายประกอบที่กำหนดเองของเรา @ViewModelKey
(อันนี้เราจะสร้าง) เราสร้างรายการภายในแผนที่ของเราด้วยรหัส MainViewModel::class
และค่า MainViewModel
ตัวอย่าง. เราผูกโรงงานเฉพาะด้วยความช่วยเหลือจาก ViewModelFactory
common ทั่วไป ระดับ. เราต้องสร้างคลาสนี้ขึ้นมา
สร้างคลาสคำอธิบายประกอบที่กำหนดเอง ViewModelKey
.
/**
* An annotation class which tells dagger that it can be used to determine keys in multi bound maps.
*/
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>) // We might use only those classes which inherit from ViewModel.
คลาสนี้ใช้สำหรับผูก ViewModels ใน ViewModelsModule
. หมายเหตุเฉพาะ @ViewModelKey
แสดงถึงกุญแจสำคัญของแผนที่ของเรา คีย์ของเราสามารถเป็นได้เฉพาะคลาสที่สืบทอดมาจาก ViewModel
.
สร้าง ViewModelFactory
ระดับ.
/**
* Factory to auto-generate a Class to Provider Map.
* We use Provider<T> to create an injectable object at a later time.
*/
@Suppress("UNCHECKED_CAST")
@Singleton
class ViewModelFactory @Inject constructor(private val viewModelsMap: Map<Class<out ViewModel>,
@JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = viewModelsMap[modelClass]
if (creator == null) {
for (entry in viewModelsMap.entries) {
if (modelClass.isAssignableFrom(entry.key)) {
creator = entry.value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("Unknown model class $modelClass")
}
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
ViewModelFactory
นี้ เป็นคลาสยูทิลิตี้ที่ช่วยให้คุณสร้าง ViewModels แบบไดนามิก ที่นี่คุณให้แผนที่ที่สร้างขึ้นเป็นอาร์กิวเมนต์ create()
จะสามารถเลือกอินสแตนซ์ที่เหมาะสมจากแผนที่ได้
สร้าง ActivityBuildersModule
โมดูลคลาส
/**
* All activities intended to use Dagger @Inject should be listed here.
*/
@Module
abstract class ActivityBuildersModule {
@ContributesAndroidInjector(modules = [MainListFragmetBuildersModule::class]) // Where to apply the injection.
abstract fun contributeMainActivity(): MainActivity
@ContributesAndroidInjector
abstract fun contributeAddSearchActivity(): AddSearchActivity
}
โมดูลนี้มีหน้าที่รับผิดชอบในการสร้างกิจกรรมทั้งหมดของคุณ มันจะสร้าง AndroidInjector
สำหรับกิจกรรมทั้งหมดที่กำหนดไว้ในคลาสนี้ จากนั้นนำวัตถุเข้าสู่กิจกรรมโดยใช้ AndroidInjection.inject(this)
ใน onCreate
ฟังก์ชันจากวงจรชีวิตกิจกรรม โปรดสังเกตว่าโมดูลนี้ยังใช้โมดูลอื่นแยกต่างหากที่รับผิดชอบส่วนย่อย เราจะสร้างโมดูลนี้ต่อไป
สร้าง MainListFragmetBuildersModule
โมดูลคลาส
/**
* All fragments related to MainActivity intended to use Dagger @Inject should be listed here.
*/
@Module
abstract class MainListFragmetBuildersModule {
@ContributesAndroidInjector() // Attaches fragment to Dagger graph.
abstract fun contributeMainListFragment(): MainListFragment
}
โมดูลนี้จะสร้างชิ้นส่วนทั้งหมดของคุณที่เกี่ยวข้องกับ MainActivity
. มันจะสร้าง AndroidInjector
สำหรับ Fragment ทั้งหมดที่กำหนดไว้ในคลาสนี้ วัตถุสามารถฉีดเข้าไปใน Fragment ได้โดยใช้ AndroidSupportInjection.inject(this)
ใน onAttach
ฟังก์ชันจากวงจรชีวิตของแฟรกเมนต์
สร้าง AppComponent
ส่วนประกอบของคลาส
/**
* Singleton component interface for the app. It ties all the modules together.
* The component is used to connect objects to their dependencies.
* Dagger will auto-generate DaggerAppComponent which is used for initialization at Application.
*/
@Singleton
@Component(
modules = [
// AndroidSupportInjectionModule is a class of Dagger and we don't need to create it.
// If you want to use injection in fragment then you should use AndroidSupportInjectionModule.class else use AndroidInjectionModule.
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityBuildersModule::class
]
)
interface AppComponent {
@Component.Builder // Used for instantiation of a component.
interface Builder {
@BindsInstance // Bind our application instance to our Dagger graph.
fun application(application: App): Builder
fun build(): AppComponent
}
// The application which is allowed to request the dependencies declared by the modules
// (by means of the @Inject annotation) should be declared here with individual inject() methods.
fun inject(app: App)
}
ส่วนประกอบเป็นคลาสที่สำคัญมาก จะทำให้ทุกอย่างข้างต้นเริ่มต้นทำงานร่วมกันได้ ทำได้โดยการเชื่อมต่ออ็อบเจ็กต์กับการพึ่งพา กริชจะใช้อินเทอร์เฟซนี้เพื่อสร้างโค้ดที่จำเป็นต่อการฉีดสารอ้างอิง
ในการสร้างคลาสส่วนประกอบ คุณจะต้องใช้คำอธิบายประกอบกริช @Component
. ใช้รายการโมดูลเป็นอินพุต คำอธิบายประกอบอื่น @Component.Builder
ทำให้เราสามารถผูกบางอินสแตนซ์กับส่วนประกอบได้
จากนั้นสร้างวัตถุกราฟ
ในขณะนี้ คุณมีโมดูลทั้งหมดและการตั้งค่าส่วนประกอบของคุณ คุณสามารถสร้างวัตถุกราฟได้โดยเลือก Build -> Make Module ภายใน Android Studio IDE ของคุณ เราต้องการคนรุ่นนี้เพื่อก้าวต่อไปในอนาคต
ตอนนี้สร้าง Injectable
อินเทอร์เฟซ
/**
* It is just a plain empty marker interface, which tells to automatically inject activities or fragments if they implement it.
*/
interface Injectable
สิ่งนี้จำเป็นสำหรับขั้นตอนในอนาคตด้วย Injectable
อินเทอร์เฟซควรถูกใช้งานโดยกิจกรรมหรือชิ้นส่วนที่เราต้องการให้ฉีดโดยอัตโนมัติ
สร้างคลาสตัวช่วยใหม่ชื่อ AppInjector
.
/**
* It is simple helper class to avoid calling inject method on each activity or fragment.
*/
object AppInjector {
fun init(app: App) {
// Here we initialize Dagger. DaggerAppComponent is auto-generated from AppComponent.
DaggerAppComponent.builder().application(app).build().inject(app)
app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityDestroyed(activity: Activity) {
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
handleActivity(activity)
}
})
}
private fun handleActivity(activity: Activity) {
if (activity is HasSupportFragmentInjector || activity is Injectable) {
// Calling inject() method will cause Dagger to locate the singletons in the dependency graph to try to find a matching return type.
// If it finds one, it assigns the references to the respective fields.
AndroidInjection.inject(activity)
}
if (activity is FragmentActivity) {
activity.supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentCreated(fragmentManager: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) {
if (fragment is Injectable) {
AndroidSupportInjection.inject(fragment)
}
}
}, true)
}
}
}
คลาสตัวช่วยง่ายๆ เพื่อหลีกเลี่ยงไม่ให้เรียกใช้เมธอด inject ในแต่ละกิจกรรมหรือแฟรกเมนต์
ต่อไป ตั้งค่า App
คลาสที่เราสร้างไว้ก่อนหน้านี้แล้ว
class App : Application(), HasActivityInjector {
@Inject // It implements Dagger machinery of finding appropriate injector factory for a type.
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
// Initialize in order to automatically inject activities and fragments if they implement Injectable interface.
AppInjector.init(this)
...
}
// This is required by HasActivityInjector interface to setup Dagger for Activity.
override fun activityInjector(): AndroidInjector<Activity> = dispatchingAndroidInjector
}
เนื่องจากแอปพลิเคชันมีกิจกรรม เราจึงต้องติดตั้ง HasActivityInjector
อินเตอร์เฟซ. หากคุณเห็นข้อผิดพลาดที่ Android Studio เรียกใช้ใน DaggerAppComponent
เป็นเพราะคุณยังไม่ได้สร้างไฟล์ใหม่ดังที่ได้กล่าวไว้ในขั้นตอนก่อนหน้านี้
ดังนั้น ตั้งค่า MainActivity
เพื่อฉีดโรงงาน ViewModel หลักและเพิ่มการรองรับการฉีดชิ้นส่วน
// To support injecting fragments which belongs to this activity we need to implement HasSupportFragmentInjector.
// We would not need to implement it, if our activity did not contain any fragments or the fragments did not need to inject anything.
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Obtain ViewModel from ViewModelProviders, using this activity as LifecycleOwner.
mainViewModel = ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
...
}
...
override fun supportFragmentInjector(): AndroidInjector<Fragment> = dispatchingAndroidInjector
...
}
เนื่องจากกิจกรรมของเรามีชิ้นส่วนย่อย เราจึงต้องติดตั้ง HasSupportFragmentInjector
อินเตอร์เฟซ. เราต้องการสิ่งนี้ด้วยเพราะเราวางแผนที่จะฉีดเข้าไปในชิ้นส่วนของเรา กิจกรรมของเราไม่ควรทราบเกี่ยวกับวิธีการฉีด เราใช้ AndroidInjection.inject(this)
บรรทัดรหัสภายในแทนที่ onCreate()
วิธีการ
กำลังโทร inject()
วิธีจะทำให้กริช 2 ค้นหาซิงเกิลตันในกราฟการพึ่งพาเพื่อพยายามค้นหาประเภทการส่งคืนที่ตรงกัน อย่างไรก็ตาม เราไม่จำเป็นต้องเขียนโค้ดใดๆ ที่นี่ เพราะมันทำเพื่อเราโดย AppInjector
ที่สร้างไว้ก่อนหน้านี้ คลาสตัวช่วยที่เราเริ่มต้นในคลาสแอปพลิเคชันของเรา
จากนั้น ตั้งค่า MainListFragment
เพื่อฉีดโรงงาน ViewModel หลัก
/**
* A placeholder fragment containing a simple view.
*/
class MainListFragment : Fragment(), Injectable {
...
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var viewModel: MainViewModel
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
...
subscribeUi(activity!!)
}
...
private fun subscribeUi(activity: FragmentActivity) {
// Obtain ViewModel from ViewModelProviders, using parent activity as LifecycleOwner.
viewModel = ViewModelProviders.of(activity, viewModelFactory).get(MainViewModel::class.java)
...
}
}
คล้ายกับกิจกรรม หากเราต้องการให้ชิ้นส่วนของเราสามารถฉีดเข้าไปได้ ให้ใส่ onAttach
วิธีที่เราควรเขียนโค้ด AndroidSupportInjection.inject(this)
. แต่นี่เป็นงานที่ทำโดย AppInjector
ผู้ช่วยเพื่อให้เราสามารถข้ามสิ่งนั้นได้ เพิ่งสังเกตว่าเราต้องเพิ่ม Injectable
อินเทอร์เฟซที่เราสร้างขึ้นก่อนหน้านี้เพื่อให้ตัวช่วยทำงาน
ขอแสดงความยินดี เราได้ใช้งาน Dagger 2 ในโครงการแอป My Crypto Coins แน่นอน บทความนี้เป็นคู่มือฉบับย่อในการปรับใช้ Dagger 2 ในแอปของคุณทันที แต่ไม่ครอบคลุมถึงมันอย่างลึกซึ้ง ฉันแนะนำให้คุณค้นคว้าหัวข้อนี้ต่อไปหากคุณรู้สึกหลงทางในพื้นฐาน
ที่เก็บ
ตรวจสอบซอร์สโค้ดของแอป “Kriptofolio” (ก่อนหน้านี้คือ “My Crypto Coins”) ที่อัปเดตบน GitHub
ดูซอร์สบน GitHub
อาชิอู! ขอบคุณที่อ่าน! ฉันเผยแพร่โพสต์นี้สำหรับบล็อกส่วนตัวของฉัน www.baruckis.com เมื่อวันที่ 7 ตุลาคม 2018