วิธีรับ Context ใน Android MVVM ViewModel


96

ฉันพยายามใช้รูปแบบ MVVM ในแอป Android ของฉัน ฉันได้อ่านแล้วว่า ViewModels ไม่ควรมีรหัสเฉพาะของ Android (เพื่อให้การทดสอบง่ายขึ้น) อย่างไรก็ตามฉันต้องใช้บริบทสำหรับสิ่งต่างๆ (การรับทรัพยากรจาก xml การกำหนดค่าเริ่มต้น ฯลฯ ) วิธีที่ดีที่สุดในการทำคืออะไร? ฉันเห็นว่าAndroidViewModelมีการอ้างอิงถึงบริบทของแอปพลิเคชัน แต่มีรหัสเฉพาะของ Android ดังนั้นฉันไม่แน่ใจว่าควรอยู่ใน ViewModel หรือไม่ รวมถึงสิ่งเหล่านั้นในกิจกรรมวงจรชีวิตของกิจกรรมด้วย แต่ฉันใช้กริชเพื่อจัดการขอบเขตของส่วนประกอบดังนั้นฉันไม่แน่ใจว่าจะส่งผลอย่างไร ฉันยังใหม่กับรูปแบบ MVVM และ Dagger ดังนั้นขอขอบคุณทุกความช่วยเหลือ!


ในกรณีที่มีคนพยายามใช้AndroidViewModelแต่ได้รับCannot create instance exceptionคุณสามารถอ้างถึงคำตอบนี้ของฉันstackoverflow.com/a/62626408/1055241
gprathour

คุณไม่ควรใช้ Context ใน ViewModel สร้าง UseCase แทนเพื่อรับ Context จากวิธีนั้น
Ruben Caster

คำตอบ:


78

คุณสามารถใช้Applicationบริบทที่จัดทำโดยAndroidViewModelคุณควรขยายAndroidViewModelซึ่งเป็นเพียงViewModelการApplicationอ้างอิงที่มี


ทำงานอย่างมีเสน่ห์!
SPM

63

สำหรับโมเดลมุมมองส่วนประกอบสถาปัตยกรรม Android

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

ดังนั้นเพื่อให้ได้ในบริบทของคุณ ViewModel ชั้น ViewModel ควรขยายAndroid ดูรุ่นคลาส ด้วยวิธีนี้คุณจะได้รับบริบทดังที่แสดงในโค้ดตัวอย่างด้านล่าง

class ActivityViewModel(application: Application) : AndroidViewModel(application) {

    private val context = getApplication<Application>().applicationContext

    //... ViewModel methods 

}

3
เหตุใดจึงไม่ใช้พารามิเตอร์ของแอปพลิเคชันและ ViewModel ปกติโดยตรง ฉันไม่เห็นประเด็นใน "getApplication <Application> ()" เพียงแค่เพิ่มแผ่นสำเร็จรูป
เหลือเชื่อ

ทำไมความจำมันรั่ว
Ben Butterworth

โอ้ฉันเข้าใจเพราะกิจกรรมจะถูกทำลายบ่อยกว่าโมเดลมุมมอง (เช่นเมื่อหน้าจอหมุน) น่าเสียดายที่คอลเล็กชันขยะจะไม่ปล่อยหน่วยความจำเนื่องจากโมเดลมุมมองยังคงมีการอ้างอิงอยู่
Ben Butterworth

52

ไม่ใช่ว่า ViewModels ไม่ควรมีโค้ดเฉพาะของ Android เพื่อให้การทดสอบง่ายขึ้นเนื่องจากเป็นนามธรรมที่ทำให้การทดสอบง่ายขึ้น

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

สิ่งที่ฉันหมายถึงนี้คือสมมติว่าคุณทำการเปลี่ยนแปลงการหมุนเวียนในแอปของคุณ สิ่งนี้ทำให้กิจกรรมและ Fragment ของคุณทำลายตัวเองเพื่อสร้างตัวเองขึ้นมาใหม่ ViewModel มีไว้เพื่อคงอยู่ในสถานะนี้ดังนั้นจึงมีโอกาสที่จะเกิดข้อขัดข้องและมีข้อยกเว้นอื่น ๆ เกิดขึ้นหากยังคงมี View หรือ Context ของกิจกรรมที่ถูกทำลาย

สำหรับวิธีที่คุณควรทำในสิ่งที่คุณต้องการทำ MVVM และ ViewModel ทำงานได้ดีกับส่วนประกอบ Databinding ของ JetPack โดยทั่วไปแล้วคุณมักจะเก็บ String, int หรืออื่น ๆ สำหรับสิ่งต่างๆคุณสามารถใช้ Databinding เพื่อให้ Views แสดงได้โดยตรงดังนั้นจึงไม่จำเป็นต้องเก็บค่าไว้ใน ViewModel

แต่ถ้าคุณไม่ต้องการ Databinding คุณยังสามารถส่ง Context ภายในตัวสร้างหรือวิธีการเพื่อเข้าถึงทรัพยากรได้ อย่าถืออินสแตนซ์ของบริบทนั้นไว้ใน ViewModel ของคุณ


1
ฉันเข้าใจว่าการรวมโค้ดเฉพาะของ Android จำเป็นต้องมีการทดสอบเครื่องมือวัดซึ่งช้ากว่าการทดสอบ JUnit ธรรมดามาก ฉันกำลังใช้ Databinding สำหรับวิธีการคลิก แต่ฉันไม่เห็นว่ามันจะช่วยในการรับทรัพยากรจาก xml หรือค่ากำหนดได้อย่างไร ฉันเพิ่งตระหนักว่าสำหรับความชอบฉันก็ต้องมีบริบทในโมเดลของฉันด้วยเช่นกัน สิ่งที่ฉันกำลังทำคือให้ Dagger แทรกบริบทของแอปพลิเคชัน (โมดูลบริบทได้รับจากวิธีการคงที่ภายในคลาสแอปพลิเคชัน)
Vincent Williams

@VincentWilliams ใช่การใช้ ViewModel จะช่วยแยกโค้ดของคุณออกจากส่วนประกอบ UI ซึ่งทำให้คุณทำการทดสอบได้ง่ายขึ้น แต่สิ่งที่ฉันกำลังพูดคือเหตุผลหลักที่ไม่รวมบริบทมุมมองหรือสิ่งที่คล้ายกันไม่ได้เป็นเพราะเหตุผลในการทดสอบ แต่เป็นเพราะวงจรชีวิตของ ViewModel ซึ่งสามารถช่วยคุณหลีกเลี่ยงข้อขัดข้องและข้อผิดพลาดอื่น ๆ ได้ สำหรับการเชื่อมโยงฐานข้อมูลสิ่งนี้สามารถช่วยคุณในการใช้ทรัพยากรได้เนื่องจากเวลาส่วนใหญ่ที่คุณต้องเข้าถึงทรัพยากรในโค้ดนั้นเกิดจากความจำเป็นในการใช้ String, color, dimen ในเลย์เอาต์ของคุณซึ่ง databinding สามารถทำได้โดยตรง
Jackey

3
หากฉันต้องการสลับข้อความในมุมมองข้อความโดยอิงจากมุมมองฟอร์มค่าสตริงจะต้องถูกแปลเป็นภาษาท้องถิ่นดังนั้นฉันจึงต้องได้รับทรัพยากรใน viewmodel ของฉันโดยไม่มีบริบทว่าฉันจะเข้าถึงทรัพยากรได้อย่างไร
Srishti Roy

3
@SrishtiRoy หากคุณใช้ databinding คุณสามารถสลับข้อความของ TextView ตามค่าจาก viewmodel ของคุณได้อย่างง่ายดาย ไม่จำเป็นต้องเข้าถึงบริบทภายใน ViewModel ของคุณเนื่องจากทั้งหมดนี้เกิดขึ้นภายในไฟล์เลย์เอาต์ อย่างไรก็ตามหากคุณต้องใช้บริบทภายใน ViewModel ของคุณคุณควรพิจารณาใช้ AndroidViewModel แทน ViewModel AndroidViewModel มีบริบทของแอปพลิเคชันที่คุณสามารถเรียกใช้ด้วย getApplication () ดังนั้นจึงควรตอบสนองความต้องการบริบทของคุณหาก ViewModel ของคุณต้องการบริบท
Jackey

1
@Pacerier คุณเข้าใจผิดวัตถุประสงค์หลักของ ViewModel เป็นการแยกประเด็นความกังวล ViewModel ไม่ควรอ้างอิงถึงมุมมองใด ๆ เนื่องจากความรับผิดชอบคือการรักษาข้อมูลที่แสดงโดยเลเยอร์ View คอมโพเนนต์ UI หรือมุมมองที่เรียกว่ามุมมองได้รับการดูแลโดยเลเยอร์มุมมองและระบบ Android จะสร้างมุมมองใหม่หากจำเป็น การอ้างอิงถึงมุมมองเก่าจะขัดแย้งกับพฤติกรรมนี้และทำให้หน่วยความจำรั่วไหล
Jackey

16

คำตอบสั้น ๆ - อย่าทำเช่นนี้

ทำไม?

มันเอาชนะจุดประสงค์ทั้งหมดของโมเดลมุมมอง

เกือบทุกอย่างที่คุณสามารถทำได้ใน view model สามารถทำได้ใน activity / fragment โดยใช้อินสแตนซ์ LiveData และแนวทางอื่น ๆ ที่แนะนำ


26
เหตุใดจึงมีคลาส AndroidViewModel
Alex Berdnikov

1
@AlexBerdnikov จุดประสงค์ของ MVVM คือการแยกมุมมอง (Activity / Fragment) จาก ViewModel ให้มากกว่า MVP เพื่อให้ง่ายต่อการทดสอบ
hushed_voice

3
@free_style ขอบคุณสำหรับคำชี้แจง แต่คำถามยังคงอยู่: ถ้าเราไม่ต้องเก็บบริบทใน ViewModel ทำไมคลาส AndroidViewModel ถึงมีอยู่ วัตถุประสงค์ทั้งหมดคือเพื่อให้บริบทของแอปพลิเคชันใช่หรือไม่?
Alex Berdnikov

7
@AlexBerdnikov การใช้บริบทกิจกรรมภายใน viewmodel อาจทำให้หน่วยความจำรั่วได้ ดังนั้นเมื่อใช้ AndroidViewModel Class คุณจะได้รับ Application Context ซึ่งหวังว่าจะไม่ทำให้หน่วยความจำรั่วไหล ดังนั้นการใช้ AndroidViewModel อาจดีกว่าการส่งบริบทกิจกรรมไป แต่การทำเช่นนั้นจะทำให้การทดสอบยากขึ้น นี่คือสิ่งที่ฉันทำ
hushed_voice

1
ฉันไม่สามารถเข้าถึงไฟล์จากโฟลเดอร์ res / raw จากที่เก็บได้?
Fugogugo

15

สิ่งที่ฉันทำแทนที่จะมีบริบทโดยตรงใน ViewModel ฉันสร้างคลาสผู้ให้บริการเช่น ResourceProvider ที่จะให้ทรัพยากรที่ฉันต้องการและฉันมีการฉีดคลาสผู้ให้บริการเหล่านั้นลงใน ViewModel ของฉัน


1
ฉันใช้ ResourcesProvider กับ Dagger ใน AppModule แนวทางที่ดีในการรับบริบทจาก ResourcesProvider หรือ AndroidViewModel นั้นดีกว่าในการรับบริบทสำหรับทรัพยากรหรือไม่
Usman Rana

@ Vincent: วิธีใช้ resourceProvider เพื่อรับ Drawable ภายใน ViewModel
Bulma

@Vegeta คุณจะเพิ่มเมธอดgetDrawableRes(@DrawableRes int id)ในคลาส ResourceProvider
Vincent Williams

1
สิ่งนี้ขัดกับแนวทาง Clean Architecture ซึ่งระบุว่าการอ้างอิงกรอบงานไม่ควรข้ามขอบเขตไปสู่ตรรกะของโดเมน (ViewModels)
IgorGanapolsky

1
@IgorGanapolsky VMs ไม่ใช่ตรรกะของโดเมน ลอจิกของโดเมนคือคลาสอื่น ๆ เช่นตัวโต้ตอบและที่เก็บเพื่อตั้งชื่อไม่กี่ VM อยู่ในหมวดหมู่ "กาว" เนื่องจากมีการโต้ตอบกับโดเมนของคุณ แต่ไม่ใช่โดยตรง หาก VM ของคุณเป็นส่วนหนึ่งของโดเมนคุณควรพิจารณาว่าคุณใช้รูปแบบนี้อย่างไรเนื่องจากคุณให้ความรับผิดชอบมากเกินไป
mradzinski

9

TL; DR: ใส่บริบทของแอปพลิเคชันผ่าน Dagger ใน ViewModels ของคุณและใช้เพื่อโหลดทรัพยากร หากคุณต้องการโหลดภาพให้ส่งอินสแตนซ์ View ผ่านอาร์กิวเมนต์จากวิธีการ Databinding และใช้บริบท View นั้น

MVVM เป็นสถาปัตยกรรมที่ดีและเป็นอนาคตของการพัฒนา Android อย่างแน่นอน แต่มีบางสิ่งที่ยังคงเป็นสีเขียว ยกตัวอย่างเช่นการสื่อสารเลเยอร์ในสถาปัตยกรรม MVVM ฉันเคยเห็นนักพัฒนารายอื่น (นักพัฒนาที่รู้จักกันดี) ใช้ LiveData เพื่อสื่อสารเลเยอร์ต่างๆในรูปแบบต่างๆ บางคนใช้ LiveData เพื่อสื่อสาร ViewModel กับ UI แต่จากนั้นใช้อินเทอร์เฟซการเรียกกลับเพื่อสื่อสารกับ Repositories หรือมี Interactors / UseCases และใช้ LiveData เพื่อสื่อสารกับพวกเขา จุดนี่คือว่าทุกอย่างไม่ได้เป็น 100% กำหนดเลย

ดังที่กล่าวมาแนวทางของฉันกับปัญหาเฉพาะของคุณคือการมีบริบทของแอปพลิเคชันที่พร้อมใช้งานผ่าน DI เพื่อใช้ใน ViewModels ของฉันเพื่อรับสิ่งต่างๆเช่น String จาก strings.xml ของฉัน

ถ้าฉันกำลังจัดการกับการโหลดรูปภาพฉันพยายามที่จะส่งผ่าน View object จากเมธอดอะแด็ปเตอร์ Databinding และใช้บริบทของ View เพื่อโหลดรูปภาพ ทำไม? เนื่องจากเทคโนโลยีบางอย่าง (เช่น Glide) อาจพบปัญหาหากคุณใช้บริบทของแอปพลิเคชันเพื่อโหลดรูปภาพ

หวังว่าจะช่วยได้!


5
TL; DR ควรอยู่บนสุด
Jacques Koorts

1
ขอบคุณสำหรับคำตอบ. อย่างไรก็ตามทำไมคุณถึงใช้กริชเพื่อฉีดบริบทถ้าคุณสามารถทำให้ viewmodel ของคุณขยายจาก androidviewmodel และใช้บริบทในตัวที่คลาสมีให้ได้ โดยเฉพาะอย่างยิ่งเมื่อพิจารณาถึงจำนวนรหัสสำเร็จรูปที่ไร้สาระเพื่อให้กริชและ MVVM ทำงานร่วมกันวิธีอื่น ๆ ดูเหมือนว่าจะชัดเจนกว่ามาก คุณคิดอย่างไรกับเรื่องนี้
Josip Domazet

8

ดังที่คนอื่น ๆ กล่าวถึงมีAndroidViewModelสิ่งที่คุณสามารถได้รับจากการรับแอปContextแต่จากสิ่งที่ฉันรวบรวมในความคิดเห็นคุณกำลังพยายามจัดการ@drawables จากภายในของคุณViewModelซึ่งเอาชนะจุดประสงค์ MVVM

โดยทั่วไปจำเป็นที่จะต้องมีContextในของคุณViewModelเกือบทุกแห่งแนะนำให้คุณควรพิจารณาทบทวนวิธีการที่คุณแบ่งตรรกะระหว่างคุณและViewViewModels

แทนที่จะมีการViewModelแก้ปัญหา drawables และป้อนไปยัง Activity / Fragment ให้พิจารณาให้ Fragment / Activity เล่นกล drawables ตามข้อมูลที่อยู่ในไฟล์ViewModel. สมมติว่าคุณต้องใช้ drawables ที่แตกต่างกันเพื่อแสดงในมุมมองสำหรับสถานะเปิด / ปิดซึ่งเป็นสิ่งViewModelที่ควรมีสถานะ (อาจเป็นบูลีน) แต่เป็นViewธุรกิจของการเลือก drawable ตามนั้น

สามารถทำได้ค่อนข้างง่ายด้วยDataBinding :

<ImageView
...
app:src="@{viewModel.isOn ? @drawable/switch_on : @drawable/switch_off}"
/>

หากคุณมีสถานะและสิ่งที่วาดได้มากขึ้นเพื่อหลีกเลี่ยงตรรกะที่ยุ่งยากในไฟล์เลย์เอาต์คุณสามารถเขียนBindingAdapterแบบกำหนดเองที่แปลพูดEnumค่าเป็นR.drawable.*(เช่นชุดการ์ด)

หรือบางทีคุณอาจจำเป็นต้องมีContextองค์ประกอบบางอย่างที่คุณใช้ภายในของคุณViewModel- แล้วสร้างองค์ประกอบภายนอกViewModel. และผ่านมันคุณสามารถใช้ DI หรือ singletons หรือสร้างContextขวาองค์ประกอบ -dependent ก่อน initialising ViewModelใน/FragmentActivity

ทำไมต้องกังวล: Contextเป็นสิ่งเฉพาะของ Android และขึ้นอยู่กับสิ่งที่อยู่ในViewModels เป็นแนวทางปฏิบัติที่ไม่ดี: พวกเขายืนอยู่ในลักษณะของการทดสอบหน่วย ในทางกลับกันอินเทอร์เฟซคอมโพเนนต์ / บริการของคุณเองอยู่ภายใต้การควบคุมของคุณอย่างเต็มที่ดังนั้นคุณจึงสามารถจำลองเพื่อทดสอบ


5

มีการอ้างอิงถึงบริบทของแอปพลิเคชันอย่างไรก็ตามมีรหัสเฉพาะของ Android

ข่าวดีคุณสามารถใช้Mockito.mock(Context.class)และทำให้บริบทส่งคืนสิ่งที่คุณต้องการในการทดสอบ!

ดังนั้นเพียงแค่ใช้ a ViewModelตามปกติและให้ ApplicationContext ผ่าน ViewModelProviders.Factory ตามปกติ


3

คุณสามารถเข้าถึงบริบทของแอปพลิเคชันgetApplication().getApplicationContext()จากภายใน ViewModel นี่คือสิ่งที่คุณต้องการเพื่อเข้าถึงทรัพยากรการตั้งค่า ฯลฯ ..


ฉันเดาว่าจะ จำกัด คำถามของฉันให้แคบลง การอ้างอิงบริบทภายใน viewmodel ไม่ดีหรือไม่ (สิ่งนี้ไม่มีผลต่อการทดสอบหรือไม่) และการใช้คลาส AndroidViewModel จะส่งผลต่อ Dagger ในทางใดหรือไม่ ไม่ผูกติดกับวงจรชีวิตของกิจกรรมหรือไม่? ฉันใช้ Dagger เพื่อควบคุมวงจรชีวิตของส่วนประกอบ
Vincent Williams

14
ViewModelชั้นไม่ได้มีgetApplicationวิธีการ
beroal

4
ไม่ แต่AndroidViewModelทำ
4Oh4

1
แต่คุณต้องผ่านอินสแตนซ์แอปพลิเคชันในตัวสร้างมันเหมือนกับการเข้าถึงอินสแตนซ์แอปพลิเคชันจากมัน
John Sardinha

2
ไม่ได้สร้างปัญหาใหญ่ให้กับบริบทของแอปพลิเคชัน คุณไม่ต้องการมีบริบทกิจกรรม / แฟรกเมนต์เนื่องจากคุณเบื่อหากแฟรกเมนต์ / กิจกรรมถูกทำลายและโมเดลมุมมองยังคงมีการอ้างอิงถึงบริบทที่ไม่มีอยู่ในขณะนี้ แต่คุณจะไม่ถูกทำลายบริบทของแอปพลิเคชัน แต่ VM ยังคงมีการอ้างอิงอยู่ ขวา? คุณนึกภาพสถานการณ์ที่แอปของคุณออก แต่ Viewmodel ไม่ออกหรือไม่? :)
user1713450

3

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


2
นี่เป็นข้อสังเกตที่เป็นธรรม แต่ไลบรารีแบ็กเอนด์บางส่วนยังต้องการบริบทของแอปพลิเคชันเช่น MediaStore คำตอบโดย 4gus71n ด้านล่างอธิบายถึงวิธีการประนีประนอม
Bryan W. Wagner

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

2

ผมมีปัญหาในการรับSharedPreferencesเมื่อใช้ระดับดังนั้นฉันเอาคำแนะนำจากคำตอบข้างต้นและได้ใช้ต่อไปViewModel AndroidViewModelตอนนี้ทุกอย่างดูดีมาก

สำหรับ AndroidViewModel

import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;

import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.preference.PreferenceManager;

public class HomeViewModel extends AndroidViewModel {

    private MutableLiveData<String> some_string;

    public HomeViewModel(Application application) {
        super(application);
        some_string = new MutableLiveData<>();
        Context context = getApplication().getApplicationContext();
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        some_string.setValue("<your value here>"));
    }

}

และใน Fragment

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;


public class HomeFragment extends Fragment {


    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        final View root = inflater.inflate(R.layout.fragment_home, container, false);
        HomeViewModel homeViewModel = ViewModelProviders.of(this).get(HomeViewModel.class);
        homeViewModel.getAddress().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String address) {


            }
        });
        return root;
    }
}

0

ฉันสร้างมันด้วยวิธีนี้:

@Module
public class ContextModule {

    @Singleton
    @Provides
    @Named("AppContext")
    public Context provideContext(Application application) {
        return application.getApplicationContext();
    }
}

จากนั้นฉันเพิ่งเพิ่มใน AppComponent ContextModule.class:

@Component(
       modules = {
                ...
               ContextModule.class
       }
)
public interface AppComponent extends AndroidInjector<BaseApplication> {
.....
}

จากนั้นฉันก็ฉีดบริบทใน ViewModel ของฉัน:

@Inject
@Named("AppContext")
Context context;


0

ปัญหาในการแทรกบริบทลงใน ViewModel คือบริบทสามารถเปลี่ยนแปลงได้ตลอดเวลาขึ้นอยู่กับการหมุนหน้าจอโหมดกลางคืนหรือภาษาของระบบและทรัพยากรที่ส่งคืนใด ๆ ก็สามารถเปลี่ยนแปลงได้ตามนั้น การส่งคืน ID ทรัพยากรอย่างง่ายทำให้เกิดปัญหาสำหรับพารามิเตอร์เพิ่มเติมเช่นการแทนที่ getString การส่งคืนผลลัพธ์ระดับสูงและการย้ายตรรกะการแสดงผลไปยังกิจกรรมทำให้ยากต่อการทดสอบ

วิธีแก้ปัญหาของฉันคือให้ ViewModel สร้างและส่งคืนฟังก์ชันที่เรียกใช้ในภายหลังผ่านบริบทของกิจกรรม น้ำตาลสังเคราะห์ของ Kotlin ทำให้เรื่องนี้ง่ายเหลือเชื่อ!

ViewModel.kt:

// connectedStatus holds a function that calls Context methods
// `this` can be elided
val connectedStatus = MutableLiveData<Context.() -> String> {
  // initial value
  this.getString(R.string.connectionStatusWaiting)
}
connectedStatus.postValue {
  this.getString(R.string.connectionStatusConnected, brand)
}
Activity.kt  // is a Context

override fun onCreate(_: Bundle?) {
  connectionViewModel.connectedStatus.observe(this) { it ->
   // runs the posted value with the given Context receiver
   txtConnectionStatus.text = this.run(it)
  }
}

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


และในการเปิดใช้งานการสนับสนุนฐานข้อมูลคุณเพียงแค่เพิ่ม BindingAdapter ง่ายๆดังนี้:@BindingAdapter("android:text") fun setText(view: TextView, value: Context.() -> String) { view.text = view.context.run(value) }
hufman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.