Computer >> คอมพิวเตอร์ >  >> ระบบ >> Android

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

ชุดแอป 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 . ของตัวเอง โฟลเดอร์ แนวคิดหลักคือคุณต้องทำในลักษณะเฉพาะแทนที่จะวางทุกอย่างแบบสุ่ม ฉันใช้ทั้งสองอย่างผสมกัน

ทั้งหมดเกี่ยวกับสถาปัตยกรรมนั้น:สำรวจรูปแบบสถาปัตยกรรมต่างๆ และวิธีใช้งานในแอปของคุณ
โครงสร้างโฟลเดอร์แอป Crypto Coins ของฉัน

นอกจากโครงสร้างโฟลเดอร์ของโปรเจ็กต์แล้ว คุณควรพิจารณาใช้กฎเกณฑ์บางประการในการตั้งชื่อไฟล์โปรเจ็กต์ ตัวอย่างเช่น เมื่อตั้งชื่อคลาส 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 อย่างในห้อง:

  1. Entity — คอมโพเนนต์นี้แสดงถึงคลาสที่มีแถวฐานข้อมูล สำหรับแต่ละเอนทิตี ตารางฐานข้อมูลจะถูกสร้างขึ้นเพื่อเก็บรายการ
  2. DAO (Data Access Object) — องค์ประกอบหลักที่รับผิดชอบในการกำหนดวิธีการที่เข้าถึงฐานข้อมูล
  3. ฐานข้อมูล — ส่วนประกอบที่เป็นคลาสผู้ถือที่ใช้คำอธิบายประกอบเพื่อกำหนดรายการเอนทิตี รายการ DAO และเวอร์ชันฐานข้อมูล และทำหน้าที่เป็นจุดเชื่อมต่อหลักสำหรับการเชื่อมต่อพื้นฐาน
ทั้งหมดเกี่ยวกับสถาปัตยกรรมนั้น:สำรวจรูปแบบสถาปัตยกรรมต่างๆ และวิธีใช้งานในแอปของคุณ

มาทำตามขั้นตอนง่ายๆ เหล่านี้เพื่อตั้งค่าห้องในแอป My Crypto Coins:

  1. สร้างเอนทิตี
@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