ชุดแอป Kriptofolio - ตอนที่ 3
สิ่งที่สำคัญที่สุดที่ต้องให้ความสำคัญเมื่อเริ่มสร้างแอปใหม่คือสถาปัตยกรรม ข้อผิดพลาดที่ใหญ่ที่สุดที่คุณสามารถทำได้คือการไม่มีรูปแบบสถาปัตยกรรมเลย
หัวข้อของการเลือกสถาปัตยกรรมเป็นที่ถกเถียงกันมากสำหรับชุมชน Android ในช่วงไม่กี่ปีที่ผ่านมา แม้แต่ Google ก็ตัดสินใจเข้าร่วม ในปี 2560 พวกเขาเสนอแนวทางของตนเองเกี่ยวกับสถาปัตยกรรมที่ได้มาตรฐานโดยเปิดตัว Android Architecture Components มีวัตถุประสงค์เพื่อทำให้ชีวิตของนักพัฒนาง่ายขึ้น
ในโพสต์นี้ อันดับแรกฉันจะพูดถึงเหตุผลที่เราต้องออกแบบแอปของเรา เราจะครอบคลุมตัวเลือกที่เรามี แล้วเราจะมาเรียนรู้วิธีการทำ แทนที่จะสร้างวงล้อใหม่ เราจะใช้หลักเกณฑ์ที่ทีม 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
เหตุใดคุณจึงควรสนใจสถาปัตยกรรมของแอป
โดยปกติเมื่อคุณเริ่มทำงานกับ Android คุณจะต้องเขียนตรรกะทางธุรกิจหลักส่วนใหญ่ในกิจกรรมหรือส่วนย่อย สิ่งนี้เกิดขึ้นกับนักพัฒนาซอฟต์แวร์ Android ใหม่ทั้งหมด รวมทั้งตัวฉันเองด้วย บทแนะนำสั้น ๆ ทั้งหมดและตัวอย่างทั้งหมดแนะนำให้ทำเช่นนั้น และที่จริงแล้ว สำหรับแอปขนาดเล็กที่สร้างขึ้นเพื่ออธิบาย ก็ใช้ได้ดีพอ
อย่างไรก็ตาม ให้ลองทำสิ่งนั้นในแอปจริงที่เปลี่ยนแปลงตลอดเวลาตามความต้องการของผู้ใช้และขยายด้วยฟีเจอร์ใหม่ๆ ในไม่ช้า คุณจะเห็นว่าประสบการณ์การเขียนโค้ดของคุณเริ่มเจ็บปวดมากขึ้นเรื่อยๆ ทุกอย่างได้รับการจัดการโดยสิ่งที่เรียกว่า "คลาสพระเจ้า" เช่นกิจกรรมหรือชิ้นส่วน มีโค้ดหลายบรรทัดที่คุณหลงทางได้ง่าย
โดยพื้นฐานแล้วโค้ดทั้งหมดของคุณเริ่มดูเหมือนสปาเก็ตตี้ที่ทุกอย่างปะปนกัน ชิ้นส่วนทั้งหมดขึ้นอยู่กับแต่ละอื่น ๆ จากนั้นเมื่อธุรกิจต้องการการเปลี่ยนแปลงใหม่ คุณจะไม่มีทางเลือกอื่นนอกจากต้องสร้างโครงการใหม่ทั้งหมด นั่นคือจุดที่คำถามเกี่ยวกับสถาปัตยกรรมเริ่มปรากฏขึ้น
มีวิธีจัดโครงสร้างโค้ดที่ดีกว่านี้ไหม
มีแน่นอน! กุญแจสำคัญสำหรับโค้ดคุณภาพสูงคือการปฏิบัติตามหลักการ SOLID ฉันพูดถึงสิ่งนี้ในโพสต์ก่อนหน้าของฉัน (ไม่ใช่โดยไม่มีเหตุผล) คุณควรใช้รูปแบบสถาปัตยกรรมบางอย่างเพื่อแยกข้อกังวล อันที่จริง การแยกข้อกังวลควรเป็นเป้าหมายสูงสุดของคุณ เป็นจุดที่สำคัญที่สุดซึ่งบ่งบอกถึงคุณภาพของโค้ด มีรูปแบบค่อนข้างน้อยสำหรับสถาปัตยกรรมแอป ที่รู้จักกันดีที่สุดคือสถาปัตยกรรมสามระดับคลาสสิกเช่น:
- MVC:Model-View-Controller
- MVP:Model-View-Presenter
- MVVM:Model-View-ViewModel
รูปแบบทั้งหมดเหล่านี้แสดงถึงแนวคิดหลักที่คล้ายคลึงกัน — เพื่อจัดโครงสร้างโค้ดของโครงการในลักษณะที่แยกจากกันโดยเลเยอร์ทั่วไปต่างๆ ทุกชั้นมีความรับผิดชอบของตัวเอง นั่นคือเหตุผลที่โครงการของคุณกลายเป็นโมดูล:ส่วนของโค้ดที่แยกจากกันนั้นสามารถทดสอบได้มากกว่า และแอปของคุณมีความยืดหยุ่นเพียงพอสำหรับการเปลี่ยนแปลงอย่างต่อเนื่อง
ถ้าเราพูดถึงแต่ละรูปแบบทีละหัวข้อ หัวข้อก็จะกว้างเกินไป ฉันจะแนะนำคุณให้รู้จักแต่ละข้อเพื่อให้คุณเข้าใจความแตกต่างหลัก ๆ เท่านั้น
รูปแบบ Model-View-Controller (MVC)
รูปแบบนี้เป็นสถาปัตยกรรมแอพ Android แบบวนซ้ำครั้งแรกในสมัยก่อน แนะนำให้แยกโค้ดออกเป็น 3 ชั้น:
โมเดล — ชั้นข้อมูล รับผิดชอบในการจัดการตรรกะทางธุรกิจและการสื่อสารกับชั้นเครือข่ายและฐานข้อมูล
ดู — เลเยอร์อินเทอร์เฟซผู้ใช้ (UI) เป็นการแสดงข้อมูลจากแบบจำลองอย่างง่าย
Controller — ลอจิกเลเยอร์ รับการแจ้งเตือนพฤติกรรมของผู้ใช้และอัปเดตโมเดลตามความจำเป็น
นี่คือสคีมา MVC ในนั้นเราจะเห็นว่าทั้งคอนโทรลเลอร์และมุมมองขึ้นอยู่กับรุ่น ตัวควบคุมจะอัปเดตข้อมูล มุมมองได้รับข้อมูล อย่างไรก็ตาม โมเดลถูกแยกออกจากกันและสามารถทดสอบได้โดยอิสระจาก UI
มีสองสามวิธีในการใช้รูปแบบ MVC มันค่อนข้างสับสน
หนึ่งคือเมื่อกิจกรรมและชิ้นส่วนทำหน้าที่เหมือนผู้ควบคุม พวกเขามีหน้าที่ในการประมวลผลข้อมูลและอัปเดตมุมมอง ปัญหาของแนวทางสถาปัตยกรรมนี้คือกิจกรรมและชิ้นส่วนอาจมีขนาดค่อนข้างใหญ่และยากต่อการทดสอบ
อีกแนวทางหนึ่งซึ่งดูสมเหตุสมผลกว่า (และถูกต้อง) คือการที่กิจกรรมและส่วนย่อยควรเป็นมุมมองในโลก MVC ตัวควบคุมควรเป็นคลาสที่แยกจากกันซึ่งไม่ขยายหรือใช้คลาส Android ใดๆ เช่นเดียวกับรุ่นต่างๆ
อย่างไรก็ตาม หากคุณตรวจสอบเพิ่มเติมเกี่ยวกับ MVC คุณจะพบว่าเมื่อใช้กับโปรเจ็กต์ Android แม้ว่าเลเยอร์โค้ดจะขึ้นอยู่กับวิธีที่ถูกต้องก็ตาม นั่นเป็นเหตุผลที่ฉันไม่แนะนำให้คุณใช้แอปนี้กับแอป Android ตัวต่อไปของคุณอีกต่อไป
รูปแบบ Model-View-Presenter (MVP)
หลังจากแนวทางแรกซึ่งไม่ได้ผล นักพัฒนา Android ก็เดินหน้าต่อไปและพยายามใช้รูปแบบสถาปัตยกรรมยอดนิยมรูปแบบหนึ่ง นั่นคือ MVP รูปแบบนี้แสดงถึงการเลือกสถาปัตยกรรมซ้ำครั้งที่สอง รูปแบบนี้ใช้กันอย่างแพร่หลายและยังคงเป็นรูปแบบที่แนะนำ สำหรับใครก็ตามที่เริ่มพัฒนา Android ก็เรียนรู้ได้ง่าย มาดูบทบาท 3 เลเยอร์แยกกัน:
โมเดล — ชั้นข้อมูล ซึ่งเหมือนกับรูปแบบ MVC รับผิดชอบในการจัดการตรรกะทางธุรกิจและการสื่อสารกับชั้นเครือข่ายและฐานข้อมูล
ดู — เลเยอร์อินเทอร์เฟซผู้ใช้ (UI) แสดงข้อมูลและแจ้งผู้นำเสนอเกี่ยวกับการดำเนินการของผู้ใช้
ผู้นำเสนอ — ดึงข้อมูลจากโมเดล ใช้ตรรกะ UI และจัดการสถานะของมุมมอง ตัดสินใจว่าจะแสดงอะไร และตอบสนองต่อการแจ้งเตือนอินพุตของผู้ใช้จากมุมมอง โดยพื้นฐานแล้วนี่คือคอนโทรลเลอร์จาก MVC ยกเว้นว่าไม่ได้เชื่อมโยงกับ View เลย เป็นเพียงอินเทอร์เฟซ
สคีมา MVP แสดงให้เห็นว่ามุมมองและผู้นำเสนอมีความเกี่ยวข้องกันอย่างใกล้ชิด พวกเขาต้องมีการอ้างอิงถึงกัน ความสัมพันธ์ของพวกเขาถูกกำหนดไว้ใน Contract
คลาสอินเทอร์เฟซ
รูปแบบนี้มีข้อเสียที่สำคัญอย่างหนึ่งแต่สามารถควบคุมได้ ผู้นำเสนอมีแนวโน้มที่จะขยายไปสู่ชั้นเรียนที่รอบรู้มากถ้าคุณไม่ระมัดระวังเพียงพอและไม่ทำลายรหัสของคุณตามหลักการความรับผิดชอบเดียว อย่างไรก็ตาม โดยทั่วไปแล้ว รูปแบบ MVP แยกข้อกังวลได้ดีมาก อาจเป็นทางเลือกหลักสำหรับโครงการของคุณ
รูปแบบ Model-View-ViewModel (MVVM)
รูปแบบ MVVM เป็นการวนซ้ำครั้งที่สามของแนวทาง มันกลายเป็นรูปแบบสถาปัตยกรรมที่แนะนำโดยทีม Android ด้วยการเปิดตัว Android Architecture Components นั่นเป็นเหตุผลที่เราจะเน้นการเรียนรู้รูปแบบนี้มากที่สุด นอกจากนี้ ฉันจะใช้มันสำหรับแอป “My Crypto Coins” ก่อนหน้านี้ มาดูชั้นโค้ดแยกกัน:
โมเดล — สรุปแหล่งข้อมูล ViewModel ทำงานร่วมกับ Model เพื่อรับและบันทึกข้อมูล
มุมมอง — ที่แจ้ง ViewModel เกี่ยวกับการกระทำของผู้ใช้
ViewModel — เปิดเผยสตรีมข้อมูลที่เกี่ยวข้องกับมุมมอง
ความแตกต่างเมื่อเปรียบเทียบกับรูปแบบ MVP คือใน MVVM ViewModel ไม่ได้อ้างอิงถึง View เหมือนกับที่มีกับ Presenter ใน MVVM ViewModel จะแสดงสตรีมของเหตุการณ์ที่ Views ต่างๆ สามารถผูกได้ ในทางกลับกัน ในกรณี MVP ผู้นำเสนอจะบอกโดยตรงว่าต้องแสดงอะไร มาดูสคีมา MVVM:
ใน MVVM มุมมองมีการอ้างอิงถึง ViewModel ViewModel ไม่มีข้อมูลเกี่ยวกับมุมมอง มีความสัมพันธ์แบบหลายต่อหนึ่งระหว่าง View และ ViewModel
การเปรียบเทียบระหว่าง MVC กับ MVP กับ MVVM
นี่คือตารางที่สรุปรูปแบบทั้งหมดที่ฉันพูดถึง:
อย่างที่คุณอาจสังเกตเห็น MVC นั้นไม่ดีนักเมื่อเทียบกับ MVP และ MVVM เมื่อสร้างแอปที่ทันสมัยแบบโมดูลาร์และทดสอบได้ แต่แต่ละรูปแบบก็มีข้อดีและข้อเสียต่างกันไป เป็นทางเลือกที่ดีหากตรงกับความต้องการของคุณ ฉันแนะนำให้คุณตรวจสอบและเรียนรู้เพิ่มเติมเกี่ยวกับรูปแบบเหล่านี้ทั้งหมดตามที่เห็นสมควร
ในระหว่างนี้ ฉันจะทำโปรเจ็กต์ต่อไปด้วยรูปแบบที่กำลังมาแรงในปี 2018 ซึ่ง Google ก็สนับสนุนเช่นกัน — MVVM
ส่วนประกอบสถาปัตยกรรม Android
หากคุณคุ้นเคยกับวงจรชีวิตของแอปพลิเคชัน Android คุณจะรู้ว่าการสร้างแอปที่หลีกเลี่ยงปัญหาการไหลของข้อมูลและปัญหาความคงอยู่และความเสถียรที่มักเกิดขึ้นระหว่างการเปลี่ยนแปลงการกำหนดค่านั้นเป็นเรื่องที่ยุ่งยากเพียงใด
ในปี 2017 ทีม Android ตัดสินใจว่าเราพยายามมากพอแล้ว พวกเขารับผิดชอบและแนะนำเฟรมเวิร์กของ Android Architecture Components ในที่สุดสิ่งนี้ก็ให้คุณแก้ปัญหาเหล่านี้ได้โดยไม่ทำให้โค้ดของคุณซับซ้อนหรือแฮ็คเลย
คอมโพเนนต์สถาปัตยกรรม Android คือชุดของไลบรารีที่ช่วยให้คุณออกแบบแอปที่มีประสิทธิภาพ ทดสอบได้ และบำรุงรักษาได้ ในขณะที่ฉันกำลังเขียนบล็อกโพสต์นี้ ประกอบด้วยองค์ประกอบเหล่านี้:
- การผูกข้อมูล — ผูกข้อมูลที่สังเกตได้อย่างชัดเจนกับองค์ประกอบ UI
- วงจรชีวิต — จัดการกิจกรรมและวงจรชีวิตส่วนย่อยของคุณ
- LiveData — แจ้งเตือนมุมมองเมื่อฐานข้อมูลเปลี่ยนแปลง
- การนำทาง — จัดการทุกอย่างที่จำเป็นสำหรับการนำทางในแอป
- การเพจ — ค่อยๆ โหลดข้อมูลตามความต้องการจากแหล่งข้อมูลของคุณ
- ห้อง — เข้าถึงฐานข้อมูล SQLite ได้อย่างคล่องแคล่ว
- ViewModel — จัดการข้อมูลที่เกี่ยวข้องกับ UI อย่างคำนึงถึงวงจรชีวิต
- WorkManager — จัดการงานพื้นหลัง Android ของคุณ
ด้วยความช่วยเหลือของ Android Architecture Components เราจะนำรูปแบบสถาปัตยกรรม MVVM ไปใช้ในแอป My Crypto Coins ตามแผนภาพนี้:
เป็นสถาปัตยกรรมที่แนะนำโดย Google มันแสดงให้เห็นว่าโมดูลทั้งหมดควรโต้ตอบกันอย่างไร ต่อไป เราจะพูดถึงเฉพาะส่วนประกอบสถาปัตยกรรม Android เฉพาะที่เราจะใช้ในโครงการของเรา
การจัดระเบียบไฟล์ต้นฉบับของคุณ
ก่อนเริ่มการพัฒนา เราควรพิจารณาว่าเราควรจัดระเบียบไฟล์ต้นฉบับของโครงการอย่างไร เราไม่สามารถปล่อยให้คำถามนี้ไม่มีคำตอบ เพราะต่อมาเราจะมีโครงสร้างที่ยุ่งเหยิงยากต่อการทำความเข้าใจและแก้ไข
มีหลายวิธีที่จะทำ หนึ่งคือการจัดระเบียบตามหมวดหมู่ส่วนประกอบ ตัวอย่างเช่น กิจกรรมทั้งหมดไปที่โฟลเดอร์ของตัวเอง อแด็ปเตอร์ทั้งหมดไปที่โฟลเดอร์และอื่น ๆ
อีกวิธีหนึ่งคือการจัดระเบียบทุกอย่างตามคุณสมบัติของแอพ ตัวอย่างเช่น ค้นหาและเพิ่ม crypto ในคุณสมบัติรายการสกุลเงิน crypto ทั้งหมดไปที่ addsearchlist
. ของตัวเอง โฟลเดอร์ แนวคิดหลักคือคุณต้องทำในลักษณะเฉพาะแทนที่จะวางทุกอย่างแบบสุ่ม ฉันใช้ทั้งสองอย่างผสมกัน
นอกจากโครงสร้างโฟลเดอร์ของโปรเจ็กต์แล้ว คุณควรพิจารณาใช้กฎเกณฑ์บางประการในการตั้งชื่อไฟล์โปรเจ็กต์ ตัวอย่างเช่น เมื่อตั้งชื่อคลาส Android คุณควรกำหนดวัตถุประสงค์ของคลาสอย่างชัดเจนในชื่อ
ViewModel
สำหรับการเริ่มต้นการพัฒนาสถาปัตยกรรมแอป ขั้นแรกเราจะสร้าง ViewModel โมเดลการดูคือออบเจ็กต์ที่ให้ข้อมูลสำหรับส่วนประกอบ UI และเปลี่ยนแปลงการกำหนดค่า
คุณสามารถใช้ ViewModel เพื่อเก็บข้อมูลตลอดวงจรชีวิตทั้งหมดของกิจกรรมหรือส่วนย่อย กิจกรรมและชิ้นส่วนเป็นวัตถุอายุสั้น สิ่งเหล่านี้ถูกสร้างขึ้นและถูกทำลายบ่อยครั้งเมื่อผู้ใช้โต้ตอบกับแอพ ViewModel ยังเหมาะสมกว่าสำหรับการจัดการงานที่เกี่ยวข้องกับการสื่อสารในเครือข่าย ตลอดจนการจัดการข้อมูลและความคงอยู่
มาดูตัวอย่างการสร้าง ViewModel สำหรับ MainListFragment
เพื่อแยกข้อมูล UI ออกจากข้อมูล
class MainViewModel : ViewModel() {
...
}
จากนั้นรับ ViewModel พร้อมโค้ดบรรทัดเดียว
class MainListFragment : Fragment() {
...
private lateinit var viewModel: MainViewModel
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupList()
// Obtain ViewModel from ViewModelProviders, using this fragment as LifecycleOwner.
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
...
}
...
}
แค่นั้นแหละ ขอแสดงความยินดี! ? ไปกันเถอะ
LiveData
LiveData เป็นคลาสผู้ถือข้อมูลที่สังเกตได้ มันเป็นไปตามรูปแบบผู้สังเกต LiveData รับรู้วงจรชีวิต ซึ่งหมายความว่าจะอัปเดตเฉพาะผู้สังเกตการณ์ในองค์ประกอบของแอป (กิจกรรม ส่วนย่อย ฯลฯ) ที่อยู่ในสถานะวงจรชีวิตที่ใช้งานอยู่
คลาส LiveData ส่งกลับค่าล่าสุดของข้อมูล เมื่อข้อมูลเปลี่ยนแปลงจะส่งกลับค่าที่อัปเดต LiveData เหมาะสมที่สุดกับ ViewModel
เราจะใช้ LiveData ร่วมกับ ViewModel ดังนี้:
...
class MainViewModel : ViewModel() {
private val liveData = MutableLiveData<ArrayList<Cryptocurrency>>()
val data: LiveData<ArrayList<Cryptocurrency>>
get() = liveData
init {
val tempData = ArrayList<Cryptocurrency>()
val btc:Cryptocurrency = Cryptocurrency("Bitcoin", 1, 0.56822348, "BTC", 8328.77, 4732.60, 0.19, -10.60, 0.44, 20.82)
val eth:Cryptocurrency = Cryptocurrency("Etherium", 2, 6.0, "ETH", 702.99, 4217.94, 0.13, -7.38, 0.79, 33.32)
tempData.add(btc)
tempData.add(eth)
liveData.value = tempData
}
}
สังเกตข้อมูลบน ViewModel ซึ่งแสดงเป็น LiveData:
...
class MainListFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
private lateinit var recyclerAdapter: MainRecyclerViewAdapter
private lateinit var viewModel: MainViewModel
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupList()
// Obtain ViewModel from ViewModelProviders, using this fragment as LifecycleOwner.
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
// Observe data on the ViewModel, exposed as a LiveData
viewModel.data.observe(this, Observer { data ->
// Set the data exposed by the LiveData
if (data != null) {
recyclerAdapter.setData(data)
}
})
}
...
}
เรียกดูที่เก็บ ณ จุดนี้ในประวัติที่นี่
การผูกข้อมูล
Data Binding Library ถูกสร้างขึ้นเพื่อลบโค้ดสำเร็จรูปที่จำเป็นในการเชื่อมต่อกับเลย์เอาต์ XML
ในการใช้ Data Binding ในโปรเจ็กต์ Kotlin ของคุณ คุณจะต้องเปิดการรองรับโปรเซสเซอร์คำอธิบายประกอบด้วยปลั๊กอินคอมไพเลอร์ kapt เพิ่มบล็อกการเชื่อมโยงข้อมูลลงในไฟล์ gradle การกำหนดค่า Android:
...
apply plugin: 'kotlin-kapt'
android {
...
dataBinding {
enabled = true
}
}
...
ในการใช้คลาสที่สร้างการเชื่อมโยงข้อมูล เราต้องใส่โค้ดการดูทั้งหมดไว้ใน <layo
ut> แท็ก แนวคิดที่มีประสิทธิภาพที่สุดของการเชื่อมโยงข้อมูลคือเราสามารถผูกคลาสข้อมูลบางคลาสกับเลย์เอาต์ XML และคุณสมบัติของไอเท็มกับฟิลด์ได้โดยตรง
<layout xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools">
<data>
<variable
name="cryptocurrency"
type="com.baruckis.mycryptocoins.data.Cryptocurrency" />
</data>
...
<android.support.v7.widget.AppCompatTextView
android:id="@+id/item_name"
style="@style/MainListItemPrimeText"
android:layout_marginEnd="@dimen/main_cardview_list_item_text_between_margin"
android:layout_marginStart="@dimen/main_cardview_list_item_inner_margin"
android:text="@{cryptocurrency.name}"
android:textAlignment="viewStart"
app:layout_constraintBottom_toTopOf="@+id/item_amount_symbol"
app:layout_constraintEnd_toStartOf="@+id/guideline1_percent"
app:layout_constraintStart_toEndOf="@+id/item_image_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread"
tools:text="@string/sample_text_item_name" />
...
</layout>
อะแดปเตอร์ RecyclerView พร้อมการเสนอราคาข้อมูลจะมีลักษณะดังนี้:
class MainRecyclerViewAdapter() : RecyclerView.Adapter<MainRecyclerViewAdapter.BindingViewHolder>() {
private lateinit var dataList: ArrayList<Cryptocurrency>
fun setData(newDataList: ArrayList<Cryptocurrency>) {
dataList = newDataList
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = FragmentMainListItemBinding.inflate(inflater, parent, false)
return BindingViewHolder(binding)
}
override fun onBindViewHolder(holder: BindingViewHolder, position: Int) = holder.bind(dataList[position])
override fun getItemCount(): Int = dataList.size
...
inner class BindingViewHolder(var binding: FragmentMainListItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(cryptocurrency: Cryptocurrency) {
binding.cryptocurrency = cryptocurrency
binding.itemRanking.text = String.format("${cryptocurrency.rank}")
...
binding.executePendingBindings()
}
}
}
ในที่สุดก็ไม่มีการเขียน findViewById
. อีกต่อไป ? เรียกดูที่เก็บ ณ จุดนี้ในประวัติที่นี่
ห้อง
แอพของเราต้องการเก็บข้อมูลถาวรของ cryptocurrencies ต่าง ๆ ที่ผู้ใช้ถืออยู่ ควรเก็บไว้ในฐานข้อมูลภายในเครื่องที่เก็บไว้ในอุปกรณ์ Android แบบส่วนตัว
สำหรับการจัดเก็บข้อมูลที่มีโครงสร้างในฐานข้อมูลส่วนตัว เราจะใช้ฐานข้อมูล SQLite ซึ่งมักจะเป็นตัวเลือกที่ดีที่สุด
ในการสร้างฐานข้อมูล SQLite สำหรับแอพของเรา เราจะใช้ Room Room เป็นไลบรารีการคงอยู่ที่สร้างโดยทีม Android ซึ่งเป็นตัวห่อหุ้มเหนือ SQLite เป็นเลเยอร์นามธรรมที่ลบโค้ดสำเร็จรูปส่วนใหญ่ที่คุณต้องการโต้ตอบกับ SQLite นอกจากนี้ยังเพิ่มการตรวจสอบเวลาคอมไพล์ของการสืบค้น SQL ของคุณด้วย
วิธีคิดที่ดีที่สุดคือเป็นเครื่องมือ ORM (Object Relational Mapper) ที่ออกแบบมาเพื่อสร้างโค้ดกาวโดยอัตโนมัติเพื่อจับคู่ระหว่างอินสแตนซ์ของออบเจ็กต์และแถวในฐานข้อมูลของคุณ
โดยทั่วไปมีองค์ประกอบหลัก 3 อย่างในห้อง:
- Entity — คอมโพเนนต์นี้แสดงถึงคลาสที่มีแถวฐานข้อมูล สำหรับแต่ละเอนทิตี ตารางฐานข้อมูลจะถูกสร้างขึ้นเพื่อเก็บรายการ
- DAO (Data Access Object) — องค์ประกอบหลักที่รับผิดชอบในการกำหนดวิธีการที่เข้าถึงฐานข้อมูล
- ฐานข้อมูล — ส่วนประกอบที่เป็นคลาสผู้ถือที่ใช้คำอธิบายประกอบเพื่อกำหนดรายการเอนทิตี รายการ DAO และเวอร์ชันฐานข้อมูล และทำหน้าที่เป็นจุดเชื่อมต่อหลักสำหรับการเชื่อมต่อพื้นฐาน
มาทำตามขั้นตอนง่ายๆ เหล่านี้เพื่อตั้งค่าห้องในแอป My Crypto Coins:
- สร้างเอนทิตี
@Entity
data class Cryptocurrency(val name: String,
val rank: Short,
val amount: Double,
@PrimaryKey
val symbol: String,
val price: Double,
val amountFiat: Double,
val pricePercentChange1h: Double,
val pricePercentChange7d: Double,
val pricePercentChange24h: Double,
val amountFiatChange24h: Double)
เพิ่มข้อมูลเพิ่มเติมเพื่อบอก Room เกี่ยวกับโครงสร้างในฐานข้อมูล
2. สร้าง DAO
@Dao
interface MyCryptocurrencyDao {
@Query("SELECT * FROM Cryptocurrency")
fun getMyCryptocurrencyLiveDataList(): LiveData<List<Cryptocurrency>>
@Insert
fun insertDataToMyCryptocurrencyList(data: List<Cryptocurrency>)
}
สำหรับการเริ่มต้น เราจะสร้าง DAO ซึ่งช่วยให้เราสามารถดึงระเบียนจากตารางที่เราสร้างด้วย Entity เท่านั้น และยังสามารถแทรกข้อมูลตัวอย่างบางส่วนได้
3. สร้างและตั้งค่าฐานข้อมูล
สิ่งสำคัญคือต้องบอกว่าอินสแตนซ์ฐานข้อมูลควรสร้างขึ้นเพียงครั้งเดียวต่อเซสชันเท่านั้น วิธีหนึ่งที่จะบรรลุสิ่งนี้คือการใช้รูปแบบซิงเกิลตัน
@Database(entities = [Cryptocurrency::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun myCryptocurrencyDao(): MyCryptocurrencyDao
// The AppDatabase a singleton to prevent having multiple instances of the database opened at the same time.
companion object {
// Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
@Volatile
private var instance: AppDatabase? = null
// For Singleton instantiation.
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
// Creates and pre-populates the database.
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
// Prepopulate the database after onCreate was called.
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// Insert the data on the IO Thread.
ioThread {
getInstance(context).myCryptocurrencyDao().insertDataToMyCryptocurrencyList(PREPOPULATE_DATA)
}
}
})
.build()
}
// Sample data.
val btc: Cryptocurrency = Cryptocurrency("Bitcoin", 1, 0.56822348, "BTC", 8328.77, 4732.60, 0.19, -10.60, 0.44, 20.82)
val eth: Cryptocurrency = Cryptocurrency("Etherium", 2, 6.0, "ETH", 702.99, 4217.94, 0.13, -7.38, 0.79, 33.32)
val PREPOPULATE_DATA = listOf(btc, eth)
}
}
private val IO_EXECUTOR = Executors.newSingleThreadExecutor()
// Utility method to run blocks on a dedicated background thread, used for io/database work.
fun ioThread(f : () -> Unit) {
IO_EXECUTOR.execute(f)
}
ดังที่คุณเห็นในการรันครั้งแรก ฐานข้อมูลจะถูกเติมข้อมูลตัวอย่างไว้ล่วงหน้าเพื่อวัตถุประสงค์ในการทดสอบเท่านั้น
4. ขั้นตอนพิเศษ สร้างที่เก็บ
Repository ไม่ได้เป็นส่วนหนึ่งของไลบรารี Architecture Components เป็นแนวทางปฏิบัติที่ดีที่สุดที่แนะนำสำหรับการแยกโค้ดและสถาปัตยกรรม
เป็นแหล่งข้อมูลความจริงแหล่งเดียวสำหรับข้อมูลแอปทั้งหมด ในกรณีที่คุณต้องจัดการแหล่งข้อมูลหลายแหล่ง
class MyCryptocurrencyRepository private constructor(
private val myCryptocurrencyDao: MyCryptocurrencyDao
) {
fun getMyCryptocurrencyLiveDataList(): LiveData<List<Cryptocurrency>> {
return myCryptocurrencyDao.getMyCryptocurrencyLiveDataList()
}
companion object {
// Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
@Volatile
private var instance: MyCryptocurrencyRepository? = null
// For Singleton instantiation.
fun getInstance(myCryptocurrencyDao: MyCryptocurrencyDao) =
instance ?: synchronized(this) {
instance
?: MyCryptocurrencyRepository(myCryptocurrencyDao).also { instance = it }
}
}
}
เราจะใช้ที่เก็บนี้ใน ViewModel ของเรา
class MainViewModel(myCryptocurrencyRepository: MyCryptocurrencyRepository) : ViewModel() {
val liveData = myCryptocurrencyRepository.getMyCryptocurrencyLiveDataList()
}
โค้ด Fragment ของเรามีวิวัฒนาการเช่นกัน
class MainListFragment : Fragment() {
...
private lateinit var viewModel: MainViewModel
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupList()
subscribeUi()
}
...
private fun subscribeUi() {
val factory = InjectorUtils.provideMainViewModelFactory(requireContext())
// Obtain ViewModel from ViewModelProviders, using this fragment as LifecycleOwner.
viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)
// Update the list when the data changes by observing data on the ViewModel, exposed as a LiveData.
viewModel.liveData.observe(this, Observer<List<Cryptocurrency>> { data ->
if (data != null && data.isNotEmpty()) {
emptyListView.visibility = View.GONE
recyclerView.visibility = View.VISIBLE
recyclerAdapter.setData(data)
} else {
recyclerView.visibility = View.GONE
emptyListView.visibility = View.VISIBLE
}
})
}
}
เนื่องจากคลาส ViewModel ของเราตอนนี้มีคอนสตรัคเตอร์ที่ไม่ว่างเปล่าอีกต่อไป เราจึงต้องนำรูปแบบโรงงานของผู้ให้บริการไปใช้ นี้จะถูกส่งต่อไปยัง ViewModelProviders.of()
เมธอดเป็นพารามิเตอร์ที่สอง
object InjectorUtils {
fun provideMainViewModelFactory(
context: Context
): MainViewModelFactory {
val repository = getMyCryptocurrencyRepository(context)
return MainViewModelFactory(repository)
}
private fun getMyCryptocurrencyRepository(context: Context): MyCryptocurrencyRepository {
return MyCryptocurrencyRepository.getInstance(
AppDatabase.getInstance(context).myCryptocurrencyDao())
}
}
class MainViewModelFactory(private val repository: MyCryptocurrencyRepository) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(repository) as T
}
}
เรียกดูที่เก็บ ณ จุดนี้ในประวัติที่นี่
ความคิดสุดท้าย
สถาปัตยกรรมการออกแบบที่เรากล่าวถึงในส่วนนี้ ควรใช้เป็นแนวทางที่มีข้อมูลครบถ้วน แต่ไม่ใช่กฎที่เข้มงวด ฉันไม่ต้องการลงรายละเอียดมากเกินไปในแต่ละหัวข้อ ด้วย Android Architecture Components เราได้ดูกระบวนการเขียนโค้ด โปรดทราบว่ายังมีอีกมากให้เรียนรู้ในแต่ละองค์ประกอบ และฉันแนะนำให้คุณทำเช่นนั้น
มาสรุปทุกสิ่งที่เราทำเสร็จแล้ว:
- ในแอป My Crypto Coins ทุกหน้าจอแยกกันมี ViewModel ของตัวเอง สิ่งนี้จะคงอยู่ต่อไปการเปลี่ยนแปลงการกำหนดค่าใดๆ และปกป้องผู้ใช้จากการสูญหายของข้อมูล
- อินเทอร์เฟซผู้ใช้ของแอปเป็นแบบตอบสนอง ซึ่งหมายความว่าจะอัปเดตทันทีเมื่อข้อมูลเปลี่ยนแปลงในส่วนแบ็คเอนด์ ซึ่งทำได้ด้วยความช่วยเหลือของ LiveData
- โปรเจ็กต์ของเรามีโค้ดน้อยกว่าเมื่อเราผูกกับตัวแปรในโค้ดของเราโดยตรงโดยใช้ Data Binding
- สุดท้าย แอปของเราจัดเก็บข้อมูลผู้ใช้ไว้ภายในอุปกรณ์เป็นฐานข้อมูล SQLite ฐานข้อมูลถูกสร้างขึ้นอย่างสะดวกด้วยองค์ประกอบห้อง โค้ดของแอปมีโครงสร้างตามคุณลักษณะต่างๆ และสถาปัตยกรรมของโปรเจ็กต์ทั้งหมดคือ MVVM ซึ่งเป็นรูปแบบที่ทีม Android แนะนำ
ที่เก็บ
ตอนนี้เมื่อคุณเห็นแอพ “Kriptofolio” (ก่อนหน้านี้คือ “My Crypto Coins”) เริ่มเป็นรูปเป็นร่างขึ้นแล้ว ด้วยคอมมิตที่เก็บล่าสุดสำหรับส่วนที่ 3 นี้ คุณจะพบว่ามันแสดงข้อมูลฐานข้อมูลที่เติมไว้ล่วงหน้าสำหรับผู้ใช้โดยคำนวณมูลค่าพอร์ตโฟลิโอการถือครองทั้งหมดอย่างถูกต้อง
ดูซอร์สบน GitHub
อาชิอู! ขอบคุณที่อ่าน! ฉันเผยแพร่โพสต์นี้สำหรับบล็อกส่วนตัวของฉัน www.baruckis.com เมื่อวันที่ 22 สิงหาคม 2018