เมื่อไม่กี่ปีก่อน Google ได้ประกาศ DataStore ซึ่งมาแทนที่ SharedPreferences ที่พยายามและเป็นจริง ป>
หากคุณใช้หรือเคยใช้ SharedPreferences ในแอปพลิเคชันของคุณ คุณอาจกำลังคิดที่จะเปลี่ยน แต่เช่นเดียวกับทุกสิ่งทุกอย่าง คำถามหลักก็คือ:ต้นทุนในการพัฒนาจะเป็นอย่างไร
การใช้ DataStore มีประโยชน์มากมาย แต่จะมีเฉพาะ Proto DataStore เท่านั้น ช่วยให้คุณสามารถบันทึกวัตถุในขณะที่ให้ความปลอดภัยประเภท ป>
หากคุณดูเอกสารประกอบของ Proto DataStore คุณจะพบว่าเอกสารนี้ค่อนข้างล้าสมัยและขาดขั้นตอนสำคัญบางประการเมื่อใช้งาน นั่นคือเหตุผลว่าทำไมในบทความนี้ เราจะอธิบายวิธีการรวม Proto DataStore เข้ากับแอปพลิเคชันของคุณ และแสดงให้เห็นว่าการใช้งานมันไม่ยุ่งยากมากนัก
DataStore คืออะไร
Jetpack DataStore มีสองรูปแบบ:
- การตั้งค่า DataStore
- โปรโตดาต้าสโตร์
เราจะไม่พูดถึงเรื่องแรก เนื่องจากมีความคล้ายคลึงกับ SharedPreferences และข้อเท็จจริงที่ได้รับการกล่าวถึงอย่างกว้างขวาง ตอนนี้เรามาทำความเข้าใจว่า Proto ใน Proto DataStore หมายถึงอะไร
Proto คือชื่อที่ Google เลือกให้เป็นตัวแทน Protocol Buffers สิ่งเหล่านี้เป็นกลไก (ของ Google) ที่ช่วยคุณจัดเรียงข้อมูลที่มีโครงสร้างเป็นอนุกรม ไม่ใช่การเขียนโค้ดเฉพาะภาษา และโดยทั่วไป คุณจะกำหนดประเภทของข้อมูลที่คุณต้องการใช้งาน จากนั้นโค้ดจะถูกสร้างขึ้นเพื่อช่วยให้คุณอ่านและเขียนข้อมูลของคุณ
✋ เราจะใช้เวอร์ชัน Proto 3 ในบทความนี้
คำจำกัดความนั้นมีลักษณะอย่างไร
message MyItem {
string itemName = 1;
int32 itemId = 2;
}
ขั้นแรก คุณกำหนดออบเจ็กต์ด้วยคีย์เวิร์ดข้อความ ภายในนั้น คุณจะแสดงรายการฟิลด์ที่เกี่ยวข้องกับออบเจ็กต์นั้น ตัวเลขที่ท้ายแต่ละฟิลด์ใช้เพื่อระบุฟิลด์นั้น และ ไม่สามารถเปลี่ยนแปลงได้เมื่อตั้งค่าแล้วและมีการใช้ออบเจ็กต์ .
แต่จะเกิดอะไรขึ้นถ้าเราต้องการให้มีหลายอ็อบเจ็กต์ในไฟล์ .proto ของเรา? สมมติว่าวัตถุมีความสัมพันธ์กัน คุณสามารถทำได้โดยการเพิ่มวัตถุข้อความเพิ่มเติม:
message MyItem {
string itemName = 1;
int32 itemId = 2;
}
message MyListOfItems {
repeated MyItem items = 1;
}
โปรดสังเกตว่าด้านบนเราได้เพิ่มวัตถุข้อความอื่นที่อาศัยวัตถุ MyItem ที่กำหนดไว้ข้างต้น หากคุณต้องการกำหนดรายการวัตถุ คุณต้องใช้ ซ้ำ คำสำคัญ.
วิธีการตั้งค่า Proto DataStore
ในการเริ่มต้น คุณจะต้องเพิ่มการขึ้นต่อกันต่อไปนี้ให้กับ build.gradle ระดับแอปพลิเคชันของคุณ:
implementation "androidx.datastore:datastore-preferences:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
จากนั้น คุณจะต้องสร้างไดเร็กทอรีโปรโตภายในโปรเจ็กต์ของคุณ ไดเร็กทอรีนี้จำเป็นต้องเป็นไดเร็กทอรีพี่น้องของโฟลเดอร์ Java ในโครงสร้างโปรเจ็กต์ของคุณ ป>
ภายในไดเร็กทอรี proto คุณจะต้องสร้างไฟล์ .proto ไฟล์นี้มีหน้าที่สร้างประเภทข้อมูลที่คุณต้องการเก็บไว้ใน Proto DataStore
ภายในไดเร็กทอรี proto ให้สร้างไฟล์ที่มีนามสกุล .proto ไฟล์ .proto ของเราจะเก็บวัตถุที่แสดงรายการสิ่งที่ต้องทำ (อะไรอีก) ดังนั้นเราจะเรียกไฟล์ของเราว่า todo.proto และจะมีลักษณะดังนี้:
syntax = "proto3";
option java_package = "com.yourPackageName.todo";
option java_multiple_files = true;
message TodoItem {
string itemId = 1;
string itemDescription = 2;
}
message TodoItems {
repeated TodoItem items = 1;
}
โปรดสังเกตว่าเรากำหนดวัตถุข้อความสองรายการอย่างไร:
- TodoItem – ที่กำหนดรายการสิ่งที่ต้องทำ
- TodoItems – ที่กำหนดรายการของออบเจ็กต์ TodoItem
ถัดไป สร้างโปรเจ็กต์เพื่อให้คลาสถูกสร้างขึ้นสำหรับ TodoItem และ TodoItems
หลังจากที่วัตถุข้อมูลของเราถูกกำหนดแล้ว เราจำเป็นต้องสร้างคลาสเพื่อทำให้เป็นอนุกรม คลาสนี้จะบอก DataStore ถึงวิธีการอ่าน/เขียนอ็อบเจ็กต์ของเรา
// 1
object TodoItemSerializer: Serializer<TodoItems> {
// 2
override val defaultValue: TodoItems = TodoItems.getDefaultInstance()
// 3
override suspend fun readFrom(input: InputStream): TodoItems {
try {
return TodoItems.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
// 3
override suspend fun writeTo(
t: TodoItems,
output: OutputStream
) = t.writeTo(output)
}
มาทบทวนสิ่งที่เรามีในชั้นเรียนนี้:
- เมื่อเราประกาศคลาส เราจำเป็นต้องใช้ Serializer เชื่อมต่อกับวัตถุของเราเป็นประเภท (T)
- เรากำหนดค่าเริ่มต้นสำหรับซีเรียลไลเซอร์ในกรณีที่ไฟล์ไม่ได้ถูกสร้างขึ้น
- เราแทนที่เมธอด readFrom/writeTo และเราตรวจสอบให้แน่ใจว่าออบเจ็กต์ของเราเป็นประเภทข้อมูลอยู่ที่นั่น
เรามีไฟล์ .proto พร้อมด้วยประเภทข้อมูลและซีเรียลไลเซอร์ ดังนั้นขั้นตอนต่อไปคือการสร้างอินสแตนซ์ DataStore เราทำสิ่งนี้โดยใช้ตัวแทนคุณสมบัติที่สร้างโดย dataStore ซึ่งจำเป็นต้องระบุชื่อไฟล์ที่จะบันทึกข้อมูลของเราและคลาสซีเรียลไลเซอร์ของเรา (ซึ่งเรากำหนดไว้ข้างต้น)
private const val DATA_STORE_FILE_NAME = "todo.pb"
private val Context.todoItemDatastore: DataStore<TodoItems> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = TodoItemSerializer,
)
โค้ดชิ้นนี้จะต้องอยู่ที่ด้านบนของคลาสที่คุณเลือก เหนือคำจำกัดความของคลาสนั้น นั่นคือ:
private const val DATA_STORE_FILE_NAME = "todo.pb"
private val Context.todoItemDatastore: DataStore<TodoItems> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = TodoItemSerializer,
)
class YourClassName {
}
เพื่อเข้าถึงวัตถุนี้ในส่วนที่เหลือของแอปพลิเคชันของเรา เราจำเป็นต้องใช้บริบท ตัวอย่างคือการใช้บริบทของแอปพลิเคชันในคลาส viewmodel ของคุณ:
class MyViewModel(application: Application): AndroidViewModel(application) {
val todoDataStore = application.todoItemDataStore
//...
}
วิธีใช้ Kotlin Flow
ตอนนี้เราได้ผ่านการตั้งค่าทุกสิ่งที่เราต้องการสำหรับ DataStore ของเราแล้ว เราจะหารือกันว่าเราจะโต้ตอบกับมันอย่างไร เราจะต้องการอ่านและเขียนข้อมูลไปยัง/จากข้อมูลนั้น แต่วิธีที่เราสามารถทำได้นั้นแตกต่างไปจากสิ่งที่คุณอาจคุ้นเคยจาก SharedPreferences
DataStore ที่เรากำหนดไว้ข้างต้นมีฟิลด์ข้อมูลที่เปิดเผยโฟลว์สำหรับคุณสมบัติที่เรากำหนดไว้ใน DataStore ของเรา
🚰 หากคุณไม่คุ้นเคยกับกระแส นี่เป็นจุดเริ่มต้นที่ดี
val todoItemFlow: Flow<TodoItems> = todoItemDataStore.data
.catch { exception ->
if (exception is IOException) {
emit(TodoItems.getDefaultInstance())
} else {
throw exception
}
}
โค้ดด้านบนแสดงวิธีที่คุณสามารถกำหนด Flow ที่รวบรวมข้อมูลจาก Proto DataStore มีการเพิ่ม catch block ในกรณีที่มีข้อยกเว้นเกิดขึ้น คุณสามารถวางตรรกะนี้ในคลาสที่คุณกำหนด DataStore ของคุณและใช้มันเช่นนั้นในโมเดลมุมมองของคุณ:
val todoItemsFlow: LiveData<TodoItems> = todoItemsRepository.todoItemFlow.asLiveData()
สังเกตว่าเราแปลง Flow เป็น LiveData อย่างไร เราทำสิ่งนี้ด้วยเหตุผลสองประการ:
- โฟลว์สามารถคงความเคลื่อนไหวได้โดยไม่คำนึงถึงกิจกรรม/ส่วนที่ใช้งาน
- LiveData เป็นสิ่งที่นักพัฒนาหลายคนคุ้นเคย และฉันต้องการทำให้ตัวอย่างนี้เข้าถึงได้ง่ายที่สุด
เพื่อให้สามารถทำเช่นนี้ได้ คุณจะต้องเพิ่มการพึ่งพาต่อไปนี้ให้กับไฟล์ build.gradle ของคุณ:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
ในคลาสกิจกรรม/แฟรกเมนต์ของคุณ คุณสามารถสังเกตข้อมูลสดนี้ได้ดังนี้:
myViewModel.todoItemFlow.observe(LocalLifecycleOwner.current) { todoItems ->
// Logic to access data from DataStore
}
ทำไมและเมื่อใดจึงควรใช้ DataStore
หลังจากรีวิวทุกอย่างแล้ว ก็ถึงเวลามาพูดถึงช้างในห้องกัน คุณควรใช้ DataStore (เช่น Preferences หรือ Proto) ในโปรเจ็กต์ที่มีอยู่หรือโปรเจ็กต์ถัดไปไหม
ในความคิดของฉัน คำตอบควรเป็น ใช่ . นอกเหนือจากข้อเท็จจริงที่ว่า Google กำลังย้ายออกจาก SharedPreferences แล้ว DataStore ยังมอบสิทธิประโยชน์มากมายที่จะช่วยให้คุณมุ่งเน้นไปที่แอปพลิเคชันของคุณ ไม่ใช่ความคงอยู่ของข้อมูลของคุณ ป>
ปลอดภัยในการโต้ตอบกับ DataStore จากเธรด UI (เนื่องจากจะย้ายงานไปที่ I/O โดยอัตโนมัติ) และจะบังคับให้คุณใช้ Flow (หากคุณยังไม่ได้ทำ) และเพลิดเพลินกับสิทธิประโยชน์ทั้งหมดภายใน นอกจากนี้ยังมีตัวเลือกในการโยกย้ายจาก SharedPreferences ไปยัง Preferences DataStore ได้อย่างง่ายดาย
หากคุณกำลังใคร่ครวญที่จะใช้ Room แทน Proto DataStore นั่นก็ขึ้นอยู่กับกรณีการใช้งานของคุณ หากปริมาณข้อมูลที่คุณจะบันทึก (หรือคงอยู่) ค่อนข้างน้อยและไม่จำเป็นต้องอัปเดตบางส่วน Proto DataStore คือคำตอบที่เหมาะสม หากคุณมีชุดข้อมูลขนาดใหญ่หรือชุดที่อาจซับซ้อน คุณควรเลือกใช้ห้องแทน
หากคุณต้องการดูว่าโค้ดทั้งหมดนี้มีลักษณะอย่างไรในแอปพลิเคชัน คุณสามารถดูได้ที่นี่:
หากคุณต้องการอ่านบทความอื่นๆ ที่ฉันเขียน คุณสามารถดูได้ที่นี่:
ขอบคุณสำหรับการอ่าน!
อ้างอิง:
- เอกสารบัฟเฟอร์โปรโตคอล (โปรโตคอล 3)
- การทำงานกับ Proto DataStore Codelab
- เอกสาร DataStore
เรียนรู้การเขียนโค้ดฟรี หลักสูตรโอเพ่นซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานในตำแหน่งนักพัฒนา เริ่มต้น