วิธีสร้าง Backend สำหรับแอป Android
ในบทความนี้ เราจะพูดถึง Android ซึ่งเป็นหนึ่งในระบบปฏิบัติการบนมือถือที่ได้รับความนิยมมากที่สุด เราจะพิจารณาข้อดีและข้อเสียของการพัฒนาแอปบน Android ตัวเลือกแบ็กเอนด์สำหรับแอปมือถือ และสอนวิธีสร้างแบ็กเอนด์บนมือถือของคุณเอง
Contents
ประเด็นสำคัญ
- ความโดดเด่นของ Android: Android ในฐานะระบบปฏิบัติการแบบโอเพ่นซอร์ส มีการใช้งานอยู่ประมาณ 70% ของอุปกรณ์มือถือทั้งหมด และมีผู้ใช้งานมากกว่า 3 พันล้านคน
- ตัวเลือกแบ็กเอนด์สำหรับแอป: ในการทำแบ็กเอนด์สำหรับแอปมือถือ นักพัฒนาสามารถเลือกใช้ Infrastructure as a Service (IaaS), Platform as a Service (PaaS) หรือ Backend as a Service (BaaS)
- ขั้นตอนในการสร้างแบ็กเอนด์: เราจะอธิบายขั้นตอนโดยละเอียดเกี่ยวกับการสร้าง Android แบ็กเอนด์โดยใช้แพลตฟอร์ม BaaS และมีโค้ดตัวอย่างในตอนท้ายของบทความ
Android คืออะไร?
Android เป็นระบบปฏิบัติการฟรีและโอเพ่นซอร์สที่พัฒนาขึ้นบน Linux เดิมทีถูกออกแบบมาสำหรับอุปกรณ์พกพา เช่น สมาร์ทโฟนและแท็บเล็ต แต่ในปัจจุบันก็ถูกใช้บนสมาร์ททีวี ระบบฝังตัว เครื่องเล่นเกมคอนโซล และอื่น ๆ อีกมากมาย
การพัฒนา Android เริ่มขึ้นในปี 2003 โดยบริษัท Android Inc. เดิมทีมีเป้าหมายที่จะสร้างระบบปฏิบัติการสำหรับกล้องดิจิทัล แต่ได้ปรับเปลี่ยนไปสู่การพัฒนาระบบปฏิบัติการมือถือเพื่อเข้าถึงตลาดที่กว้างขึ้น
ในปี 2005 บริษัทถูกซื้อกิจการโดย Google พร้อมทั้งพนักงานทั้งหมด โดย Android เวอร์ชันแรกถูกปล่อยออกมาในปี 2008
แม้ว่า Android จะเป็นโอเพ่นซอร์ส แต่บนอุปกรณ์พกพาส่วนใหญ่จะรันเวอร์ชันที่เป็นกรรมสิทธิ์ของ Google ซึ่งมาพร้อมกับซอฟต์แวร์อย่าง Google Chrome, YouTube, Google TV และ Gmail
เนื่องจาก Android สามารถปรับแต่งได้อย่างมาก หลายผู้ผลิตจึงปรับแต่งหน้าตา Android ให้สอดคล้องกับแบรนด์ของตน นั่นเป็นเหตุผลที่ Android ของ OnePlus มีหน้าตาที่ต่างจาก Android ของ Pixel
Android เป็นระบบปฏิบัติการที่ได้รับความนิยมที่สุดตั้งแต่ปี 2013 โดยมีการใช้งานประมาณ 70% ของอุปกรณ์พกพา และมีผู้ใช้งานมากกว่า 3 พันล้านคนต่อเดือน
นอกจากนี้ Google Play Store ยังมีแอปให้เลือกดาวน์โหลดมากกว่าสามล้านแอป อ่านต่อเพื่อเรียนรู้วิธีสร้างแบ็กเอนด์สำหรับแอป Android
ข้อดีของการพัฒนา Android
เรามาดูข้อดีบางประการของการพัฒนาแอปสำหรับ Android กัน
รองรับข้ามแพลตฟอร์ม
เมื่อพัฒนาแอปสำหรับ Android คุณจะสามารถเข้าถึงอุปกรณ์ได้หลากหลาย ทั้งโทรศัพท์มือถือ อุปกรณ์สวมใส่ สมาร์ททีวี เครื่องเล่นเกมคอนโซล และอื่น ๆ อีกมากมาย หากคุณมีความรู้ในการพัฒนาแอปบนมือถือ คุณก็จะพัฒนาแอปบนอุปกรณ์สวมใส่หรือสมาร์ททีวีได้ไม่ยาก
ตลาดใหญ่
อย่างที่กล่าวไปก่อนหน้านี้ Android มีส่วนแบ่งตลาดประมาณ 70% เมื่อเทียบกับ iOS ที่ 30% ดังนั้นการพัฒนาแอปบน Android จึงทำให้คุณเข้าถึงผู้ใช้ได้กว้างขวางยิ่งขึ้น
นอกจากนี้ แอปบน Android ยังมีต้นทุนการพัฒนาต่ำและให้ผลตอบแทน (ROI) ที่ค่อนข้างดี และต่างจาก iOS ตรงที่ไม่มีค่าธรรมเนียมรายปีสำหรับนักพัฒนา
ปรับแต่งได้สูง
อุปกรณ์ Android สามารถปรับแต่งได้อย่างยืดหยุ่นเมื่อเทียบกับระบบปฏิบัติการอื่น ๆ คุณสามารถปรับเปลี่ยนเกือบทุกอย่างที่คุณนึกออก
นอกจากนี้ Android ยังอนุญาตให้ผสานการทำงานกับบริการของบุคคลที่สาม (3rd-party) ได้อย่างง่ายดาย
ชุมชนขนาดใหญ่
Android ได้รับการสนับสนุนจากชุมชนโอเพ่นซอร์สขนาดใหญ่ อีกทั้งยังมาพร้อมกับเครื่องมือที่เป็นมิตรต่อเหล่านักพัฒนา เช่น Android Studio, ADB และ Logcat ซึ่งช่วยให้คุณเขียนโค้ดแอปได้สะดวก
ถ้าคุณติดปัญหาใด ๆ คุณสามารถขอความช่วยเหลือได้จากหลายแพลตฟอร์ม ไม่ว่าจะเป็น GitHub, StackOverflow, Reddit และชุมชนที่มุ่งเน้น Android อีกมากมาย
ข้อจำกัดของการพัฒนา Android
นี่คือข้อเสียบางประการของการพัฒนา Android
ความปลอดภัย
Android มีความปลอดภัยน้อยกว่า iOS และไม่ได้บังคับใช้มาตรการความปลอดภัยที่เข้มงวด เนื่องจาก Android เป็นโอเพ่นซอร์ส ทำให้มีการค้นพบช่องโหว่ความปลอดภัยบ่อยครั้ง ซึ่งเปิดโอกาสให้แฮกเกอร์เข้าถึงช่องโหว่ก่อนที่จะได้รับการแก้ไข
อุปกรณ์ Android ยังสามารถรูท (root) เพื่อให้ได้สิทธิ์ superuser ซึ่งทำให้เครื่องมีความสามารถมากขึ้น แต่ก็เสี่ยงเนื่องจากมาตรการความปลอดภัยบางอย่างอาจถูกปิดใช้งาน
ความซับซ้อน
อุปกรณ์ Android มีหลายขนาดและหลายรูปแบบ แม้ว่าจะเป็นข้อได้เปรียบ แต่ก็ทำให้เกิดความยุ่งยากในการพัฒนาแอปให้รองรับอุปกรณ์หลายประเภท
เพื่อให้แอปรองรับอุปกรณ์ได้มากที่สุด คุณจำเป็นต้องคำนึงถึงการออกแบบให้ตอบสนอง (responsive design) และลักษณะฮาร์ดแวร์ของแต่ละอุปกรณ์ เป็นต้น
แอปแบบพรีเมียม
แอปแบบพรีเมียมประสบความสำเร็จน้อยกว่าบน Google Play Store เมื่อเทียบกับ App Store เป็นที่ทราบกันดีว่าผู้ใช้ Android มักใช้จ่ายกับแอปน้อยกว่า iOS ถ้าคุณกำลังพัฒนาแอปที่เน้นโมเดลพรีเมียมเป็นหลัก Android อาจเป็นตัวเลือกที่สอง
ตัวเลือกแบ็กเอนด์สำหรับแอป Android
แล้วแบ็กเอนด์แบบไหนที่เหมาะกับแอป Android? แบ็กเอนด์สำหรับแอปมือถือสามารถโฮสต์บนโครงสร้างพื้นฐานของตนเองหรือบนบริการคลาวด์ โดยโมเดลยอดนิยมของระบบคลาวด์สำหรับแบ็กเอนด์มือถือได้แก่:
- Infrastructure as a Service (IaaS)
- Platform as a Service (PaaS)
- Backend as a Service (BaaS)
แต่ละตัวเลือกจะมีระดับการทำงานที่ครอบคลุมความซับซ้อต่างกัน ตามที่แสดงในภาพด้านล่าง
มาดูแต่ละตัวเลือกแบบละเอียด เพื่อช่วยให้คุณเลือกโมเดลคลาวด์ที่เหมาะสมสำหรับแบ็กเอนด์แอปของคุณ
Infrastructure as a Service (IaaS)
Infrastructure as a Service (IaaS) เป็นโมเดลการประมวลผลแบบคลาวด์ที่มีการครอบคลุมน้อยที่สุด กล่าวคือ ผู้ให้บริการจะมอบทรัพยากรเสมือน (Virtualized Resources) เช่น เซิร์ฟเวอร์ ระบบเครือข่าย พื้นที่เก็บข้อมูล และระบบปฏิบัติการ ผ่าน API ระดับสูงหรือแดชบอร์ดที่เข้าใจง่าย
จุดเด่นของ IaaS คือผู้ใช้จะควบคุมโครงสร้างพื้นฐานได้อย่างสมบูรณ์ แม้ว่าจะยืดหยุ่นและขยายขนาดได้สูงสุด แต่ก็ต้องใช้การดูแลจัดการมากที่สุด หากคุณเลือก IaaS คุณอาจต้องมีผู้ดูแลระบบ (SysAdmin) อย่างน้อยหนึ่งคน
ตัวอย่างของ IaaS ได้แก่ AWS, Google Cloud Platform และ Azure
Platform as a Service (PaaS)
Platform as a Service (PaaS) เป็นโมเดลคลาวด์ที่ถูกออกแบบมาเพื่อช่วยนักพัฒนาสร้าง จัดการ และปรับใช้แอปพลิเคชัน
นอกจากโครงสร้างพื้นฐานแล้ว PaaS ยังมาพร้อมเครื่องมือที่ใช้งานง่ายสำหรับการพัฒนา การปรับแต่ง และการทดสอบแอปพลิเคชัน
การใช้ PaaS ทำให้คุณสามารถโฟกัสกับซอฟต์แวร์และประสบการณ์ผู้ใช้ของคุณโดยไม่ต้องกังวลเกี่ยวกับโครงสร้างพื้นฐานเบื้องหลัง
นอกจากนี้ PaaS ยังช่วยให้แอปของคุณปรับขนาดตามความต้องการ จัดการข้อมูลสำรอง และอื่น ๆ อย่างไรก็ตามข้อเสียคือการควบคุมที่ลดลง ความเสี่ยงจากการล็อกอินกับผู้ให้บริการ และต้นทุนที่สูงกว่าเมื่อเทียบกับ IaaS
แพลตฟอร์ม PaaS ที่ได้รับความนิยมที่สุด ได้แก่ Heroku, Render และ Digital Ocean App Platform
Backend as a Service (BaaS)
Backend as a Service (BaaS) เป็นบริการที่ช่วยลดภาระในการพัฒนาแบ็กเอนด์ลงอย่างมาก เพราะผู้ให้บริการจะมีแบ็กเอนด์สำเร็จรูปให้อย่างครบถ้วน
ฟีเจอร์ของ BaaS ได้แก่ การจัดการผู้ใช้ ฐานข้อมูล การยืนยันตัวตน (Authentication) การเชื่อมต่อกับโซเชียล Push Notifications, SDKs และอื่น ๆ อีกมากมาย
BaaS ให้ข้อดีทั้งหมดของ IaaS และ PaaS พร้อมทั้งฟีเจอร์เพิ่มเติม ทีมที่ใช้ BaaS มักจะปล่อยผลิตภัณฑ์ได้เร็วขึ้น ประหยัดค่าใช้จ่ายทางวิศวกรรม และพัฒนาซอฟต์แวร์ที่มีคุณภาพดีขึ้น
ข้อเสียของ BaaS คือมีการควบคุมและการปรับแต่งได้น้อยกว่า และมีความเสี่ยงจากการล็อกอินกับผู้ให้บริการในกรณีที่แพลตฟอร์มไม่เป็นโอเพ่นซอร์ส
BaaS ที่เป็นตัวอย่าง ได้แก่ Back4app, AWS Amplify และ Firebase
วิธีสร้างแบ็กเอนด์สำหรับแอป Android
ในส่วนนี้ของบทความ เราจะมาดูวิธีสร้างแอปแบ็กเอนด์โดยใช้ Back4app และวิธีเชื่อมต่อกับแอป Android ของเรา
สิ่งที่ต้องมีล่วงหน้า
- มีประสบการณ์ใช้ Kotlin และการพัฒนา Android
- เข้าใจพื้นฐานของ Mobile Backend as a Service
- ติดตั้ง Java และ Android Studio ไว้บนเครื่อง
Back4app คืออะไร?
Back4app เป็นแพลตฟอร์มที่ยอดเยี่ยมสำหรับการสร้างแบ็กเอนด์สำหรับแอปสมัยใหม่ และยังช่วยเร่งกระบวนการพัฒนาได้เป็นอย่างดี
มาพร้อมกับฟีเจอร์ฝั่งเซิร์ฟเวอร์ที่เป็นประโยชน์ เช่น การจัดการผู้ใช้ ฐานข้อมูลเรียลไทม์ การเชื่อมต่อโซเชียล Cloud Code, Push Notifications, APIs และอื่น ๆ อีกมากมาย
เมื่อใช้ Back4app คุณจะสามารถลดงานส่วนแบ็กเอนด์ลง และให้ความสำคัญกับฟีเจอร์หลักของแอปได้ รวมถึงช่วยเร่งการพัฒนาแบ็กเอนด์ของแอป Android ได้อีกด้วย
คุณไม่ต้องกังวลเกี่ยวกับโครงสร้างพื้นฐานของแบ็กเอนด์ เซิร์ฟเวอร์ การขยายตัว การดูแลรักษา และอื่น ๆ
ที่สำคัญ Back4app ยังมีแพ็กเกจฟรีที่เหมาะสำหรับการทดลองหรือทดสอบแพลตฟอร์ม และเมื่อแอปของคุณเติบโตขึ้น ก็สามารถอัปเกรดเป็นแพลนแบบพรีเมียมได้
แนะนำโปรเจกต์ที่จะสร้าง
ในบทความนี้ เราจะสร้างแอปจดบันทึก (notes app) อย่างง่าย โดยผู้ใช้สามารถเพิ่ม แก้ไข และลบโน้ตได้ โน้ตเหล่านั้นจะถูกเก็บไว้ในฐานข้อมูลของ Back4app และเราจะสื่อสารกับฐานข้อมูลผ่าน Parse SDK
เราจะใช้ภาษา Kotlin ในการพัฒนาแอป และสำหรับการออกแบบ UI จะใช้ Jetpack Compose ซึ่งเป็นเครื่องมือสมัยใหม่ของ Android สำหรับการสร้าง UI แบบเนทีฟ
สร้างแอปบน Back4app
ขั้นตอนแรกในการพัฒนาแบ็กเอนด์สำหรับแอป Android คือคุณต้องมีบัญชี Back4app ถ้าคุณยังไม่มี ให้ ลงทะเบียน ก่อน
เมื่อคุณเข้าสู่ระบบ Back4app แล้ว คุณจะถูกนำไปยังหน้ารวมแอป คลิก “Build new app” เพื่อเริ่มการสร้างแอป
บน Back4app มีสองโซลูชันหลักให้เลือก:
- Backend as a Service (BaaS) – โซลูชันแบ็กเอนด์พร้อมใช้
- Containers as a Service (CaaS) – แพลตฟอร์มสำหรับจัดการคอนเทนเนอร์ (โดยเฉพาะเว็บแอปพลิเคชัน)
เนื่องจากเรากำลังสร้างแบ็กเอนด์สำหรับแอปบนมือถือ เราจึงเลือก “Backend as a Service”
ตั้งชื่อแอปของคุณให้เหมาะสม เลือก “NoSQL” เป็นฐานข้อมูล และคลิก “Create” นี่เป็นขั้นตอนสำคัญในการดีพลอยแบ็กเอนด์สำหรับแอป Android
Back4app จะใช้เวลาสักครู่ในการเตรียมทุกอย่างที่จำเป็นสำหรับแอปของคุณ ซึ่งรวมถึงฐานข้อมูล การจัดการผู้ใช้ การขยายขนาด การตั้งค่า และอื่น ๆ เมื่อแอปของคุณพร้อมใช้งานแล้ว คุณจะถูกนำไปยังหน้า “Database” ของแอป
ออกแบบฐานข้อมูล
ขั้นต่อไป เราจะออกแบบฐานข้อมูล ซึ่งเป็นขั้นตอนที่จำเป็นในการพัฒนาแบ็กเอนด์สำหรับแอป Android
เนื่องจากเราจะสร้างแอปจดบันทึกง่าย ๆ เราจึงต้องการเพียง Class เดียว ให้คลิก “Create a class” ตั้งชื่อว่า Note
เปิดสิทธิ์ “Public Read and Write” แล้วคลิก “Create class & add columns”
หลังจากนั้น ให้คุณเพิ่ม 3 ฟิลด์ต่อไปนี้ลงในคลาสที่เพิ่งสร้าง:
+-------------+-------------+--------------------+----------+
| Data type | Name | Default value | Required |
+-------------+-------------+--------------------+----------+
| String | icon | <leave blank> | yes |
+-------------+-------------+--------------------+----------+
| String | title | <leave blank> | yes |
+-------------+-------------+--------------------+----------+
| String | content | <leave blank> | yes |
+-------------+-------------+--------------------+----------+
สุดท้าย คลิกปุ่ม “Add row” แล้วใส่ข้อมูลตัวอย่างในฐานข้อมูลของคุณ ถ้าคุณนึกไม่ออกว่าจะใส่อะไร ก็สามารถนำเข้าไฟล์ data dump นี้ได้
เยี่ยม เท่านี้ส่วนแบ็กเอนด์ของเราก็พร้อมแล้ว
เขียนโค้ดฝั่ง Frontend
ในส่วนนี้ของบทความ เราจะสร้าง Android แอปตั้งแต่เริ่มต้น ตั้งค่า ViewModel ออกแบบ UI ติดตั้งและตั้งค่า Parse SDK และสุดท้ายจะดึงข้อมูลจากฐานข้อมูลเรียลไทม์บน Back4app
สร้างโปรเจกต์
ตามที่กล่าวไว้ในหัวข้อก่อนหน้า คุณต้องติดตั้ง Android Studio เพื่อทำตามขั้นตอนต่อไป ถ้าคุณยังไม่ได้ติดตั้ง ให้ ดาวน์โหลด ได้เลย
เริ่มจากเปิด Android Studio และคลิกปุ่ม “New Project”
จากนั้นให้เลือก “Empty Activity” เป็นโครงโปรเจกต์ แล้วคลิก “Next”
เพื่อสร้างโปรเจกต์ คุณต้องตั้งชื่อโปรเจกต์และชื่อแพ็กเกจ แนะนำให้ตั้งชื่อโปรเจกต์เป็น AndroidApp
และใช้รูปแบบ reverse domain name เป็นชื่อแพ็กเกจ
หลังจากที่คุณตั้งค่าทุกอย่างเรียบร้อยแล้ว ให้คลิก “Finish” เพื่อสร้างโปรเจกต์
Android Studio จะใช้เวลาประมาณสองนาทีในการเตรียมไฟล์และติดตั้ง Gradle รวมถึงการติดตั้งไลบรารีต่าง ๆ
เมื่อโปรเจกต์พร้อมแล้ว ให้คุณเปิดไฟล์ MainActivity.kt และแทนที่เนื้อหาทั้งหมดด้วยโค้ดด้านล่าง:
// app/src/main/java.<your_package_name>/MainActivity.kt
package <your.package.name>
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
Text(text = "Back4app rocks!", fontSize = 18.sp)
}
}
}
}
}
อย่าลืมแทนที่
<your_package_name>
ด้วยชื่อแพ็กเกจจริง ๆ ของคุณ
สุดท้าย ลองรันแอปด้วยการคลิกปุ่ม play สีเขียว หรือกด Shift + F10
ถ้าทุกอย่างถูกต้อง เครื่องจำลอง (emulator) จะรันขึ้นมาและแสดงข้อความ “Back4app rocks” บนหน้าจอ
ViewModel
เพื่อจัดการ State แบบ Global ของแอป เราจะใช้ ViewModel
แต่ก่อนอื่นเราต้องสร้างคลาส Note
สำหรับเก็บข้อมูลที่มีโครงสร้างเหมือนกับในฐานข้อมูลของ Back4app
ให้สร้างไฟล์ดาต้าคลาสชื่อว่า Note
:
// app/src/main/java.<your_package_name>/Note.kt
package <your.package.name>
data class Note(
var objectId: String? = null,
var icon: String,
var title: String,
var content: String,
) {
companion object {
fun generateObjectId(): String {
val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
return (1..10).map { chars.random() }.joinToString("")
}
}
}
- เราได้กำหนดเมธอด
generateObjectId()
ไว้เพื่อสร้าง Object ID แบบ Parse - ฟิลด์
objectId
ถูกกำหนดให้เป็น null ได้ (nullable) เพราะถ้าobjectId == null
แสดงว่ายังไม่ได้ถูกบันทึกลงในฐานข้อมูล
หลังจากนั้น สร้างคลาส AppViewModel
โดยใส่โค้ดดังนี้:
// app/src/main/java.<your_package_name>/AppViewModel.kt
package <your.package.name>
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class AppViewModel : ViewModel() {
val notes: MutableState<Map<String, Note>> = mutableStateOf(mapOf(
"7IggsqFAKt" to Note(
objectId = "7IggsqFAKt",
icon = "\uD83D\uDE80",
title = "Deploy app",
content = "Go ahead and deploy your backend to Back4app.",
),
"eFRNm0hTat" to Note(
objectId = "eFRNm0hTat",
icon = "\uD83C\uDFA8",
title = "Design website",
content = "Design the website for the conference.",
),
"uC7hTQmG5F" to Note(
objectId = "uC7hTQmG5F",
icon = "\uD83D\uDC42",
title = "Attend meeting",
content = "Attend meeting with the team to discuss the conference.",
),
))
companion object {
@Volatile
private var instance: AppViewModel? = null
fun getInstance(): AppViewModel {
return instance ?: synchronized(this) {
instance ?: AppViewModel().also { instance = it }
}
}
}
}
- เราใช้
MutableState
จาก Compose เพื่อให้ UI อัปเดตเมื่อข้อมูลเปลี่ยน MutableState
เก็บMap
ที่แมปจากobjectId
ไปเป็นNote
พร้อมใส่ข้อมูลตัวอย่างไว้- เพื่อให้มั่นใจว่าจะมีแค่
AppViewModel
อินสแตนซ์เดียว เราใช้ Singleton Pattern
เราสามารถเข้าถึง AppViewModel
ได้ใน Activity ต่าง ๆ ผ่าน AppViewModel.getInstance()
Main Activity
ต่อมา ให้แก้ไข MainActivity.kt ด้วยโค้ดต่อไปนี้เพื่อแสดงโน้ต:
// app/src/main/java.<your_package_name>/MainActivity.kt
package <your.package.name>
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
class MainActivity : ComponentActivity() {
private val viewModel = AppViewModel.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val context = this as Context
setContent {
MainActivityContent(
viewModel = viewModel,
onNoteListItemClick = {
// TODO: Open note edit form
},
onNoteAddClick = {
// TODO: Open note create form
},
)
}
}
}
@Composable
fun MainActivityContent(
viewModel: AppViewModel,
onNoteListItemClick: (note: Note) -> Unit,
onNoteAddClick: () -> Unit,
) {
val notes = viewModel.notes.value
MaterialTheme {
Scaffold(
topBar = { TopAppBar(title = { Text("My Notes") }) },
floatingActionButton = {
ExtendedFloatingActionButton(
onClick = { onNoteAddClick() },
icon = { Icon(Icons.Filled.Add, contentDescription = "Add") },
text = { Text("Add") }
)
},
) { contentPadding ->
Box(modifier = Modifier.padding(contentPadding)) {
NoteList(notes, onNoteListItemClick = { onNoteListItemClick(it) })
}
}
}
}
@Composable
fun NoteListItem(note: Note, onNoteListItemClick: (note: Note) -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { onNoteListItemClick(note) })
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(text = note.icon, fontSize = 32.sp, modifier = Modifier.size(48.dp))
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(text = note.title, fontSize = 18.sp)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = note.content,
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@Composable
fun NoteList(notes: Map, onNoteListItemClick: (note: Note) -> Unit) {
LazyColumn {
items(notes.entries.toList()) { (_, note) ->
NoteListItem(note = note, onNoteListItemClick = onNoteListItemClick)
}
}
}
notes
ถูกนำมาจากAppViewModel
- เราไม่กำหนด event คลิกไว้ใน Composable โดยตรง แต่ส่งผ่านเป็น argument แทน
- เราใช้ Compose ในการออกแบบ UI โดยใช้
MaterialTheme
ร่วมกับเลย์เอาต์Scaffold
เพื่อให้ UI ดูสวยงามขึ้น
ถ้าคุณรันแอปตอนนี้ คุณจะเห็นรายการโน้ตที่เรากำหนดแบบฮาร์ดโค้ดไว้แสดงบนหน้าจอ
Note Form Activity
ในส่วนนี้ เราจะสร้าง Activity ใหม่เพื่อให้ผู้ใช้สามารถเพิ่มและแก้ไขโน้ตได้
เริ่มจากสร้างคลาสชื่อ FormActivity
และใส่โค้ดต่อไปนี้:
// app/src/main/java.<your_package_name>/FormActivity.kt
package <your.package.name>
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
class FormActivity : ComponentActivity() {
private val viewModel = AppViewModel.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val objectId = intent.getStringExtra("objectId")
val note = if (objectId !== null) viewModel.notes.value[objectId] else null
setContent {
FormActivityContent(
note,
onNoteAddClick = { icon: String, title: String, content: String ->
if (note !== null) return@FormActivityContent
val newNote = Note(
icon = icon,
title = title,
content = content,
)
newNote.objectId = Note.generateObjectId()
viewModel.notes.value += (newNote.objectId!! to newNote)
finish()
},
onNoteSaveClick = { icon: String, title: String, content: String ->
if (note === null) return@FormActivityContent
val updatedNote = note.copy()
updatedNote.icon = icon
updatedNote.title = title
updatedNote.content = content
viewModel.notes.value += (updatedNote.objectId!! to updatedNote)
finish()
},
onNoteDeleteClick = {
if (note === null) return@FormActivityContent
viewModel.notes.value = viewModel.notes.value.filter {
it.value.objectId != note.objectId
}
finish()
},
)
}
}
}
@Composable
fun FormActivityContent(
note: Note?,
onNoteAddClick: (icon: String, title: String, content: String) -> Unit,
onNoteSaveClick: (icon: String, title: String, content: String) -> Unit,
onNoteDeleteClick: () -> Unit,
) {
MaterialTheme {
Scaffold(
topBar = {
TopAppBar(title = { Text(note?.let { "Edit Note" } ?: ("Add Note")) })
},
floatingActionButton = {
if (note !== null) {
ExtendedFloatingActionButton(
onClick = { onNoteDeleteClick() },
icon = { Icon(Icons.Filled.Delete, "Delete") },
text = { Text("Delete") },
)
}
},
) { contentPadding ->
Box(modifier = Modifier.padding(contentPadding)) {
NoteForm(note = note, onNoteSave = { icon, title, content ->
if (note === null) {
onNoteAddClick(icon, title, content)
} else {
onNoteSaveClick(icon, title, content)
}
})
}
}
}
}
@Composable
fun NoteForm(
note: Note?,
onNoteSave: (icon: String, title: String, content: String) -> Unit
) {
var icon by remember { mutableStateOf(TextFieldValue(note?.icon ?: "")) }
var title by remember { mutableStateOf(TextFieldValue(note?.title ?: "")) }
var content by remember { mutableStateOf(TextFieldValue(note?.content ?: "")) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
TextField(
label = { Text(text = "Icon") },
value = icon,
onValueChange = { icon = it },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
label = { Text(text = "Title") },
value = title,
onValueChange = { title = it },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
label = { Text(text = "Content") },
value = content,
onValueChange = { content = it },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { onNoteSave(icon.text, title.text, content.text) },
Modifier.fillMaxWidth()
) {
Text(text = "Save")
}
}
}
- ฟอร์มนี้ใช้ได้ทั้งการเพิ่มและแก้ไขโน้ต โดยดูจาก
objectId
ถ้าobjectId == null
แสดงว่าเป็นการเพิ่มโน้ต แต่ถ้ามีobjectId
แสดงว่าเป็นการแก้ไข - ถ้า
objectId !== null
เราจะดึงnote
จาก State มาตั้งค่าเริ่มต้นในฟอร์ม - เพื่อให้ทำงานได้กับ
MutableState
เราต้องปฏิบัติต่อข้อมูลใน State เหมือนเป็น Immutable จึงมีการcopy()
แทนการแก้ไขโดยตรง - เราใช้
mutableStateOf()
เพื่อจัดการ State ของฟอร์มใน Compose
ต่อไป ให้ลงทะเบียน FormActivity
ในส่วน application
ของไฟล์ AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<!-- ... -->
<activity android:name=".FormActivity"/>
</application>
</manifest>
จากนั้น ไปแก้ไขเมธอด onNoteListItemClick
และ onNoteAddClick
ใน MainActivity.kt เพื่อเรียก Intent
ในการเปิด FormActivity
:
// app/src/main/java.<your_package_name>/MainActivity.kt
class MainActivity : ComponentActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
setContent {
MainActivityContent(
viewModel = viewModel,
onNoteListItemClick = {
// triggers the edit action, since `objectId` is provided
val intent = Intent(context, FormActivity::class.java)
intent.putExtra("objectId", it.objectId)
context.startActivity(intent)
},
onNoteAddClick = {
// triggers the add action, since no `objectId` is present
val intent = Intent(context, FormActivity::class.java)
context.startActivity(intent)
},
)
}
}
}
อย่าลืมนำเข้า Intent
ด้านบนไฟล์ด้วย:
import android.content.Intent
ถ้าคุณรันแอปตอนนี้ คุณจะได้แอปที่สามารถเพิ่ม แก้ไข และลบโน้ตได้ แต่ถ้าปิดและเปิดแอปใหม่ ข้อมูลจะกลับไปเป็นค่าที่ฮาร์ดโค้ดไว้
ในหัวข้อต่อไป เราจะเชื่อมแอปกับแบ็กเอนด์บน Back4app เพื่อให้ข้อมูลถูกเก็บและซิงก์ระหว่างอุปกรณ์หลายเครื่อง
ติดตั้ง Parse SDK
อย่างที่หลายคนทราบ Back4app พัฒนาบน Parse platform ดังนั้นหากเราต้องการติดต่อกับฐานข้อมูลหรือบริการของ Back4app เราต้องติดตั้ง Parse SDK
เริ่มจากเพิ่ม JitPack repository ไปที่ไฟล์ settings.gradle:
// settings.gradle
pluginManagement {
repositories {
// ...
maven { url "https://jitpack.io" }
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
// ...
maven { url "https://jitpack.io" }
}
}
จากนั้นเพิ่ม Android Parse SDK ลงใน build.gradle ของโมดูลแอป:
// app/build.gradle
dependencies {
// ...
implementation "com.github.parse-community.Parse-SDK-Android:parse:4.2.0"
}
จากนั้นซิงค์ Gradle และตรวจสอบว่าไม่มีเออเรอร์
ตั้งค่า Parse SDK
เพื่อเชื่อมต่อกับแบ็กเอนด์ Back4app คุณต้องระบุ Application ID และ Client Key ให้กับ Parse SDK ในการรับค่าดังกล่าว ให้เข้าแอปของคุณใน Back4app แล้วเลือกเมนู “App Settings > Security & Keys”
จากนั้นนำค่าที่ได้มาใส่ในไฟล์ strings.xml แบบนี้:
<!-- app/src/main/res/values/strings.xml -->
<resources>
<string name="app_name">back4app-android-app</string>
<string name="back4app_server_url">https://parseapi.back4app.com/</string>
<string name="back4app_app_id">your_parse_app_id</string>
<string name="back4app_client_key">your_parse_client_key</string>
</resources>
อย่าลืมแทนที่
your_parse_app_id
และyour_parse_client_key
ด้วยค่าจริงของคุณ
จากนั้นแก้ไขไฟล์ AndroidManifest.xml เพื่ออนุญาตการเข้าถึงอินเทอร์เน็ตและเพิ่ม Metadata สำหรับ Parse:
<!-- app/src/main/AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application>
<!-- ... -->
<meta-data
android:name="com.parse.SERVER_URL"
android:value="@string/back4app_server_url" />
<meta-data
android:name="com.parse.APPLICATION_ID"
android:value="@string/back4app_app_id" />
<meta-data
android:name="com.parse.CLIENT_KEY"
android:value="@string/back4app_client_key" />
</application>
</manifest>
สุดท้าย เราต้อง Initialize Parse ก่อนที่ Activity ใด ๆ จะถูกเรียกใช้ เราจึงสร้างคลาสใหม่ชื่อ App
ซึ่งสืบทอดจากคลาส Application
สร้างไฟล์ App.kt และกำหนดค่า Parse ดังนี้:
// app/src/main/java/<your_package_name>/App.kt
package <your.package.name>
import android.app.Application
import com.parse.Parse
class App : Application() {
override fun onCreate() {
super.onCreate()
Parse.initialize(
Parse.Configuration.Builder(this)
.applicationId(getString(R.string.back4app_app_id))
.clientKey(getString(R.string.back4app_client_key))
.server(getString(R.string.back4app_server_url))
.build()
)
}
}
จากนั้นลงทะเบียน App
ใน AndroidManifest.xml:
<!-- app/src/main/AndroidManifest.xml -->
<application
android:name=".App"
...
>
<!-- ... -->
</application>
ลองรันแอปอีกครั้ง และสังเกตใน Logcat ว่าไม่มีเออเรอร์ใด ๆ หากไม่พบข้อผิดพลาด แสดงว่าแอปได้เชื่อมต่อกับ Back4app เรียบร้อยแล้ว
โหลดโน้ตจาก Back4app
ขั้นตอนสุดท้ายก่อนที่แอปของเราจะสมบูรณ์คือการโหลดโน้ตจากฐานข้อมูล Back4app และศึกษาการใช้งาน Parse ในทางปฏิบัติ
เริ่มจากไปที่คลาส AppViewModel
ลบข้อมูลตัวอย่างที่ใส่ใน map
ออก แล้วเพิ่ม init
block ดังนี้:
// app/src/main/java/<your_package_name>/AppViewModel.kt
package <your.package.name>
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class AppViewModel : ViewModel() {
val notes: MutableState<Map<String, Note>> = mutableStateOf(mapOf())
init {
val query = com.parse.ParseQuery.getQuery<com.parse.ParseObject>("Note")
query.orderByDescending("createdAt")
query.findInBackground { notes, e ->
if (e == null) {
for (parseNote in notes) {
val note = Note(
objectId = parseNote.objectId,
icon = parseNote.getString("icon")!!,
title = parseNote.getString("title")!!,
content = parseNote.getString("content")!!,
)
this.notes.value += (note.objectId!! to note)
}
} else {
println("Error: ${e.message}")
}
}
}
companion object {
// ...
}
}
โค้ดดังกล่าวจะใช้ Parse SDK ดึงโน้ตจากฐานข้อมูล แปลงเป็น Note
และนำไปใส่ใน Map ของ State
จากนั้น เพิ่มเมธอดสามตัวต่อไปนี้ใน Note
:
// app/src/main/java/<your_package_name>/Note.kt
package <your.package.name>
import com.parse.ParseObject
import com.parse.ParseQuery
data class Note(
var objectId: String? = null,
var icon: String,
var title: String,
var content: String,
) {
fun addToParse(callback: (objectId: String) -> Unit) {
if (objectId !== null) throw Exception("Note is already saved to Parse!")
val parseNote = ParseObject("Note")
parseNote.put("icon", icon)
parseNote.put("title", title)
parseNote.put("content", content)
parseNote.saveInBackground {
if (it !== null) throw Exception("Error: ${it.message}")
objectId = parseNote.objectId
callback(parseNote.objectId)
}
}
fun updateToParse(callback: (objectId: String) -> Unit) {
if (objectId === null) throw Exception("Note hasn't been saved to Parse yet!")
val query = ParseQuery.getQuery<ParseObject>("Note")
val parseNote = query.get(objectId)
parseNote.put("icon", icon)
parseNote.put("title", title)
parseNote.put("content", content)
parseNote.saveInBackground {
if (it !== null) throw Exception("Error: ${it.message}")
callback(parseNote.objectId)
}
}
fun deleteFromParse(callback: () -> Unit) {
if (objectId === null) throw Exception("Note hasn't been saved to Parse yet!")
val query = ParseQuery.getQuery<ParseObject>("Note")
val parseNote = query.get(objectId)
parseNote.deleteInBackground {
if (it !== null) throw Exception("Error: ${it.message}")
callback()
}
}
}
addToParse()
ใช้เพื่อเพิ่มโน้ตใหม่ลง Parse Server พร้อมข้อมูลที่เกี่ยวข้อง ถ้าสำเร็จจะอัปเดต objectId และแจ้ง callback เมื่อเสร็จupdateToParse()
ใช้เพื่ออัปเดตข้อมูลโน้ตบน Parse Server ถ้าสำเร็จจะแจ้ง callback เมื่อเสร็จdeleteFromParse()
ใช้เพื่อลบโน้ตออกจาก Parse Server ถ้าสำเร็จจะแจ้ง callback เช่นกัน
สุดท้าย ให้แก้ไขเมธอดคลิกใน FormActivity
เพื่อเรียกเมธอดใหม่ที่เราสร้างไว้:
// app/src/main/java/<your_package_name>/NoteFormActivity.kt
class FormActivity : ComponentActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
setContent {
FormActivityContent(
note,
onNoteAddClick = { icon: String, title: String, content: String ->
if (note !== null) return@FormActivityContent
val newNote = Note(
icon = icon,
title = title,
content = content,
)
newNote.addToParse {
viewModel.notes.value += (it to newNote)
finish()
}
},
onNoteSaveClick = { icon: String, title: String, content: String ->
if (note === null) return@FormActivityContent
val updatedNote = note.copy()
updatedNote.icon = icon
updatedNote.title = title
updatedNote.content = content
updatedNote.updateToParse {
viewModel.notes.value += (it to updatedNote)
finish()
}
},
onNoteDeleteClick = {
if (note === null) return@FormActivityContent
viewModel.notes.value = viewModel.notes.value.filter {
it.value.objectId != note.objectId
}
note.deleteFromParse {
finish()
}
},
)
}
}
}
เรียบร้อย!
ตอนนี้แอปของเราทำงานได้สมบูรณ์ และซิงก์กับแบ็กเอนด์บน Back4app แล้ว ลองแก้ไขข้อมูลในแอป และตรวจสอบในหน้า Database ของ Back4app ดูว่าข้อมูลเปลี่ยนตามหรือไม่
สรุป
ในบทความนี้ เราได้สร้างแบ็กเอนด์สำหรับแอปมือถือและเชื่อมต่อกับแอป Android โดยใช้บริการ BaaS จาก Back4app และใช้ Kotlin ร่วมกับ Jetpack Compose ฝั่ง Frontend
ตอนนี้คุณน่าจะเข้าใจหลักการทำงานของแบ็กเอนด์สำหรับแอปมือถือ และสามารถสร้างแบ็กเอนด์ของคุณเองได้ สามารถดูซอร์สโค้ดฉบับสมบูรณ์ได้ที่ back4app-android-app repo