เขียนแอพแอนดรอยด์ตั้งแต่เริ่มต้น ตอนที่ 2 : Activity

Minseo Chayabanjonglerd
Ascend Developers
Published in
7 min readJan 4, 2023

--

หลังจากที่เราได้ลองสร้างแอพเปล่ามาลองรันบนเครื่องดูแล้ว ตอนนี้มาทำความรู้จักกับสิ่งสำคัญสิ่งแรกๆที่ Android Developer ต้องรู้ นั่นคือ Activity และ Fragment นั่นเอง

สวัสดีค่ะทุกคน บล็อกนี้เป็น 1 ใน content series สอนการเขียนแอพแอนดรอยด์ตั้งแต่เริ่มต้น จนพอเขียนได้เนอะ เหมาะสำหรับใครที่อยากลองเขียน Android Native เนอะ โดยใน content series ที่ชื่อว่า Basic Android Development จะมีดังนี้

ในบล็อกนี้เป็นหัวข้อ Activity เป็นหัวข้อพื้นฐานที่ต้องรู้ เป็นหัวใจสำคัญในการทำ Android Development เลยนะ

ก่อนอื่น Activity คืออะไรกันนะ?

สมมุติว่าเราเปิดหน้าแอพ TrueMoney ก็จะเจอหน้าตาแบบนี้เนอะ

Activity เป็นหนึ่งใน component สำคัญของแอพพลิเคชันบนระบบปฏิบัติการแอนดรอยด์ และเป็นสิ่งที่เป็นตัว launch แอพของเราขึ้นมา อย่างในรูปนี้ก็คือ user เปิดแอพ TrueMoney จะเจอหน้าแรกแบบนี้ โดยแอพพลิเคชันจะประกอบด้วยอย่างน้อย 1 Activity และต้อง declare ที่ AndroidManifest ไม่เช่นนั้นจะไม่สามารถรันแอฟพลิเคชั่นได้

และจะต้องมีการ declare ทุก ๆ Activity ในแอพ ใน AndroidManifest ไม่เช่นนั้น แอพพลิเคชันของเราจะ crash

โดยตัว Activity จะมี layout ที่เป็น XML มาคู่กันด้วยเสมอ

(ยกเว้นทำแบบ Compose ที่สามารถเขียน layout ต่างๆได้จากโค้ด จะคล้ายๆ Flutter หน่อยๆ แต่บล็อกซีรีส์นี้ไม่ได้กล่าว — อันนี้จะเขียนแยกสรุปสิ่งที่เรียนจาก Compose Camp อีกบล็อกนึง)

Activity Lifecycle

คือ วงจรชีวิตของ activity ใน application ของเรา

การที่เราทำอะไรต่างๆในแอปพลิเคชั่น ก็จะอยู่ใน Activity Lifecycle เสมอ ไม่ว่าจะเป็นการเข้ามาที่แอพ เปลี่ยนหน้าแอพ การออกจากแอพ

โดยตัว Lifecycle ของ Activity จะมีหน้าตาเป็นแบบนี้

ref : https://developer.android.com/guide/components/activities/activity-lifecycle
  • onCreate() Activity ของเรากำลังถูกสร้าง
  • onStart() เราเริ่มเห็นหน้า Activity ของเราแล้ว UI มาครบ
  • onResume() มีสองแบบ คือ ได้รับ focus จาก input กับกลับเข้าหน้า Activity นี้มาใหม่
  • onPause() หยุดการทำงานของ Activity นั้นไว้ก่อน
  • onStop() หยุดการทำงานของ Activity นั้นไปเลย
  • onDestroy() ทำลาย Activity นั้นซะ

แต่เราชอบรูปนี้มากกว่า เพราะมันสั้น และเข้าใจได้ง่ายกว่า

เมื่อเปิดแอพขึ้นมานั่นจะเข้า onCreate() เพื่อสร้าง activity หน้านั้นขึ้นมา แล้วเข้า onStart() เพื่อเริ่มต้น activity แล้วเข้า onResume() ซึ่งเราจะเห็น view ของ activity แล้วเพราะได้ render เสร็จแล้วนั่นเอง

เมื่อมีการพับหน้าจอลง เช่น มือถือเข้าสู่โหมดล็อกหน้าจอ ก็จะเข้า onPause()

แล้วเมื่อปลดล็อกหน้าจอออกมาแล้ว จะ onResume() กลับมาใหม่

เมื่อเรากดปุ่ม home ออกจากแอพ จะเข้า onPause() จากนั้นเข้า onStop() เพื่อหยุดการทำงานของ activity นั้นลงไปก่อน เมื่อกลับเข้ามาในแอพ จะทำการ onRestart() activity นั้นกลับมาใหม่ แล้วเข้าสู่ onStart() และ onResume() เพื่อ render หน้า activity นั้นกลับมาใหม่

และเมื่อเราออกจาก activity หน้านั้นโดยการกด back หรืออะไรก็ตามที่ทำให้ activity นั้นถูกทำลาย หรือออกจากแอพ จาก onResume() จะเข้า onPause() จากนั้นเข้า onStop() เพื่อหยุดการทำงานของ activity และเข้า onDestroy() เพื่อทำลาย activity นั้นซะ

อันนี้รวมทั้งหมดเนอะ

สำหรับการลองเล่นเกี่ยวกับ Actvity Lifecycle น้ัน เรามักจะใส่ toast ต่างๆไว้ใน lifecycle แต่อัน เพื่อทำการ check เนอะ ว่าตอนนี้เข้าอันไหนบ้าง

ตัว demo สามารถดูได้ที่คลิปนี้เลย

ส่วนโค้ดดูได้ที่นี่จ้า

แล้ว toast คืออะไรหล่ะ?

มันคือ ขนมปังปิ้งที่แสนน่ารับประทานยังไงหล่ะ

Photo by Seriously Low Carb on Unsplash

เดี๋ยวๆ toast คือ View ที่ใช้แจ้งเตือนอะไรบางอย่าง เล็กๆ อยู่ตรงด้านล่างจอ เช่น บอกว่ากำลังส่งข้อความอยู่นะ ที่ชื่อ toast ก็น่าจะเหมือนขนมปังปิ้งที่พอกรอบได้ที่แล้ว จะเด้งขึ้นมาจากเครื่องปิ้งขนมปังนั่นเอง จะว่าไปก็หิวแหะ

อ่านต่อได้ที่ https://developer.android.com/guide/topics/ui/notifiers/toasts

การใส่ toast ก็แสนจะง่าย หน้าตาจะเป็นแบบนี้ เดี๋ยวเราจะมาแนะนำแต่ละส่วนกัน

Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show()

ในส่วน makeText จะประกอบด้วย 3 parameter หลักๆ

  • application context: context ของหน้าที่ toast เราจะโชว์ ในที่นี้จะใช้ this เนื่องจากอยู่ใน Activity นะ
  • text: ข้อความที่เราต้องการให้แสดง
  • duration: อยากให้ toast ของเราโชว์นานเท่าไหร่ โชว์สั้นๆให้ใช้ Toast.LENGTH_SHORTชว์ยาวๆให้ใช้ Toast.LENGTH_LONG

และปิดท้ายด้วย show() เพื่อแสดง toast ที่เราสร้าง แสดงขึ้นมาเนอะ

ถ้าใครอยากลองเล่น เราสร้าง branch ชื่อว่า activity-lifecycle มาให้เล่นเพิ่มหล่ะ ไปลองกันได้เลยแบบง่ายๆ เพียง build app เสร็จแล้วดูผลลัพธ์ได้เลย

มาลองทำการสร้าง Activity ใหม่ และส่งค่าไปยัง Activity ใหม่กันเถอะ

จากอันนี้ เราจะมี Activity ทั้งสิ้น 1 อันด้วยกัน

ทีนี้เราจะมาสร้าง Activity อีกอันเพิ่มกันดีกว่า

โดยในตัวอย่างนี้ เราจะทำแบบนี้ เราจะส่งค่าจาก activity 1 แล้วเอาค่าที่ได้ ไปเปิดใน activity 2 แบบง่ายๆ

ก่อนอื่นเลย จะได้หน้า Activity เป็นหน้าเปล่าๆ ที่มีคำว่า “Hello World” มาใช่ป่าว เราจะมาเพิ่ม Button เพื่อเปิดไปหน้า Activity ใหม่กัน

โดยตัว layout ที่เราใช้นั้น ชื่อว่า ConstraintLayout นั่นเอง เป็น layout ที่ช่วยลดความซับซ้อนเวลาที่เราทำ UI ใหญ่ๆ และมีความซับซ้อน อีกทั้งเจ้า ConstraintLayout ยังอยู่ใน Andriod Jetpack ด้วยนะ

ในที่นี้เราวาง TextView ไว้กลางหน้าจอ เราก็บอกว่า ให้ด้านบน ด้านล่าง ด้านซ้าย ด้านขวา ยึดจาก parent นะ ทำให้มันอยู่ตรงกลางจอนั่นเอง

เดี๋ยวเราอธิบายแต่ละส่วนคร่าวๆกัน

  • parent view ของ view นี้ คือ ConstraintLayout มี child view คือ TextView
  • TextView เรากำหนด id ของ view นี้ ชื่อว่า textview ก็แล้วกัน เดี๋ยวได้ใช้นะทุกคน
  • เรากำหนดความกว้างของเจ้า TextView ด้วย layout_width เป็น wrap_content ก็คือให้ความกว้างของมัน เท่ากับ content ข้างในนั่นเอง
  • ส่วนความสูงก็เช่นกัน กำหนดด้วย layout_height เป็น wrap_content ก็คือให้ความสูงของมัน เท่ากับ content ข้างในเช่นกัน
  • ส่วน parent ด้านนอกที่เป็น ConstraintLayout จะมีความกว้างและความสูง เป็น match_parent คือให้มีขนาดเต็มหน้าจอนั่นเอง
  • layout_constraintStart_toStartOf อันนี้บอกว่า เจ้า textview นี้ จุด start หรือด้านซ้ายของ view ของเรา ไปอยู่กับ start เดียวกับ view ที่เรา reference ในที่นี้เรา reference view ของเราคือ parent นั่นเอง
  • ส่วน layout_constraintEnd_toEndOf , layout_constraintTop_toTopOf และ layout_constraintBottom_toBottomOf ความหมายคล้ายกับ layout_constraintStart_toStartOf แค่เปลี่ยนแนวที่เรา reference เช่น end หรือด้านขวา, ด้านบน, ด้านล่าง
  • text เราสามารถกำหนดว่าจะใส่คำว่าอะไรให้กับ TextView ซึ่ง attribute พวกนี้ สามารถกำหนดค่าได้ผ่าน layout XML แบบนี้ หรือผ่าน code ก็ได้ อดใจรอ เดี๋ยวมีให้ทำแน่นอน

ต่อมาเราจะมาเพิ่ม Button กัน โดยเราจะให้มันอยู่ใต้ TextView และอยู่ตรงกลางของด้านความกว้าง แบบในรูปนี้เลย

มาเพิ่ม TextView ตามนี้เลย เดี๋ยวเราจะมาอธิบายแต่ละส่วนกัน

  • เราเพิ่ม Button ไว้ที่ด้านล่างของ TextView แต่ไม่ได้หมายความว่า Button อยู่ใต้ TextView นะ แค่วางในที่ที่เรากลับมาอ่าน layout ได้ง่ายเฉย ๆ ว่าวางอะไรตรงไหน
  • layout_marginTop บอกว่า เจ้า Button ตัวนี้ ห่างจากข้างบนเท่าไหร่ ในที่นี้ห่าง 16dp ซึ่ง dp เป็นหน่วยที่ใช้ในการกำหนดขนาดของ view นั้น ๆ นั่นเอง
  • layout_constraintTop_toBottomOf อันนี้บอกว่าด้านบน Button ของเรา reference กับด้านล่างของ View ไหน ในที่นี้คือ TextView จึงใส่ id ของ TextView ลงไป นั่นคือ textview นั่นเอง
  • ส่วน layout_constraintStart_toStartOf และ layout_constraintEnd_toEndOf เราจะอิงกับ TextView นั่นเอง

ต่อมา เรามาสร้าง Activity ใหม่กัน อันนี้ก็ลอกจากของเดิมมาเลยแหละ

อธิบายเพิ่มเติม

  • อย่าลืมเพิ่ม SecondActivity ที่ไฟล์ AndroidManifest.xml ด้วยนะ ไม่งั้นพอเราเพิ่มปุ๊บ กดปุ่มปั้บ แอพ crash จ้า เพราะไม่ได้กำหนด Activity เพิ่มในนี้อ่ะ
  • textSize เป็น attribute ที่เอามากำหนดขนาดของตัวหนังสือ ซึ่งเราจะใช้เป็นหน่วย sp นะ

จากนั้นมาเพิ่มการทำงานของ Button ที่เราเพิ่งสร้างกัน โดยเราจะให้กดปุ่มนี้ แล้วไปหน้า SecondActivity กัน ที่ MainActivity นะ

เราทำการใช้ event listener ในการกดปุ่ม มีชื่อว่า setOnClickListener โดยเรียกจาก View ที่เราต้องการ ในที่นี้คือ Button นั่นเอง

binding.button.setOnClickListener {
//TBC
}

ตอนนี้เราก็สามารถกดปุ่มในแอพเพื่อทำอะไรต่างๆได้แล้ว แต่เราก็ได้แค่กดปุ่ม ยังทำอะไรมากกว่านี้ไม่ได้ ถัดมาทำส่วนไปยังหน้า SecondActivity โดยการสร้าง Intent ขึ้นมาใหม่ แบบนี้

val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)

ตัว Intent จะรับ 2 parameter คือ context ของจุดตั้งต้น ในที่นี้เป็น this เนอะ และ Class คือ หน้าไหนที่เราจะไป รับเป็น class เข้ามาเนอะ

รวมร่างกัน

เมื่อลองรันแอพดู ผลที่ได้คือ กดปุ่มแล้วไปหน้าที่สองแล้วนะ แบบนี้

แต่เรายังไปได้อีก เราจะทำการเปลี่ยน TextView ของ MainActivity เป็น EditText เพื่อให้เราลองพิมพ์อะไรก็ได้ แล้วไปโชว์ที่ SecondActivity

ในไฟล์ activity_main.xml เราจะเปลี่ยนจาก TextView เป็น EditText กัน

โดยเจ้า EditText เราจะตั้งชื่อมันว่า edittext จะเปลี่ยนความกว้าง layout_width จาก wrap_content เป็น match_parent และเพิ่ม margin แนวนอน layout_marginHorizontal เป็น 24dp เพื่อให้เรากดพิมพ์ได้ง่ายขึ้นนั่นเอง

แล้วก็อย่าลืมเปลี่ยน id ที่เชื่อมความสัมพันธ์ จาก textview เป็น edittext ด้วยนะ ไม่งั้นมันจะ error เพราะไม่มี id textview นะ

จากนั้นเราจะทำการนำค่าที่เราพิมพ์จาก EditText นำไปส่งต่อไปยัง SecondActivity ผ่าน Intent นั่นเอง

การนำ text ที่เราพิมพ์บน EditText ออกมาใช้นั้น เราจะทำการอ้างอิง View ที่เราจะใช้ก่อน ในที่นี้คือ EditText เนอะ จากนั้นเราใช้ text ซึ่งเราสามารถใช้ในการ setText หรือ getText ก็ได้ แต่การที่เราจะ getText จาก EditText มันจะคืนเป็น Editable ออกมา

จึงต้องใช้ toString() ตามหลังเพื่อให้ได้ text จริงๆออกมา

binding.edittext.text.toString()

การส่งค่า เราจะแนบไปกับ Intent โดยใส่ค่าที่เราจะส่งไปกับ putExtra() โดย parameter แรก คือชื่อในการอ้างอิงค่านั้นๆ ในที่นี้เป็นคำว่า “text” และ parameter ที่สอง คือ ค่าที่เราจะส่งเข้าไปนั่นเอง

intent.putExtra("text", binding.edittext.text.toString())

เมื่อส่งค่าไปแล้ว อย่าลืมไปรับค่ามาใช้งานกันต่อนะ ไปที่ SecondActivity ทำการประกาศตัวแปร ชื่อว่า text และใช้ getStringExtra() ในการรับค่ามา โดยใช้เจ้าชื่อในการอ้างอิงเหมือนกันกับตอนส่งนะ

private val text by lazy {
intent.getStringExtra("text")
}

สุดท้าย นำ text ที่ได้ มาแสดงที่ TextView

binding.textview.text = text

และแล้วเราก็ได้ตามนี้แล้วแหละ เย้ๆ

โค้ดทั้งหมด

สุดท้ายเรามา refactor code กันในแบบที่ Android Developer ใช้กันนะ เนื่องจากในการทำงานจริง เช่น ลูกค้าซื้อของต่างๆลงตระกร้า แล้วต้องการ checkout เราก็จะไปเปิด activity หน้า checkout ซึ่งมีหลากหลายเส้นทางในการเปิดหน้านี้

ดังนั้นเราจะทำ function นึงขึ้นมา ชื่อว่า newIntent() ในการส่งค่าต่างๆที่เราต้องการไปยัง Activity น้ันๆ โดยอยู่ใน companion object ด้วยความที่ Kotlin มันไม่มี static จึงใส่ไว้ในนี้ เพื่อให้ class อื่น ๆ สามารถเรียกใช้ function นี้ได้นั่นเอง

แล้วก็เจ้าชื่อในการอ้างอิงอ่ะ ควรเป็นตัวแปรแบบ constant เนอะ

companion object {
const val EXTRA_TEXT = "SecondActivity:EXTRA_TEXT"

fun newIntent(context: Context, text: String): Intent {
return Intent(context, SecondActivity::class.java).apply {
putExtra(EXTRA_TEXT, text)
}
}
}

การนำไปใช้งาน

startActivity(SecondActivity.newIntent(
this,
binding.edittext.text.toString()
))

โค้ดที่ผ่านการ refactor มาแล้ว

อันที่เสร็จแล้วจะตามนี้เลย เรา commit ตามแต่ละขั้นตอนไว้ให้นะ

จริง ๆ แล้ว Intent มีกี่ประเภทกันนะ ?

Intent มี 2 ประเภท คือ

  • Explicit Intent: เป็น intent ที่เรียก package app ที่เราต้องการ หรือเรียกใช้ class แบบเจาะจง โดยที่เราทำกันเมื่อกี้ จะเป็นแบบ Explicit เนอะ
  • Implicit Intent: เป็น intent ที่ไม่ได้เรียกใช้แบบเฉพาะเจาะจง แต่จะต้องตรงกับเงื่อนไข ก็จะมี Action, Type และ Category
ref: https://developer.android.com/guide/components/intents-filters

หน้าตาโค้ดในการเรียกใช้ Implicit Intent จะเป็นแบบนี้เนอะ

val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(
Intent.EXTRA_TEXT,
"Hello Android Fragmentation World"
)
intent.type = "text/plain"
startActivity(intent)

ผลที่ได้ก็จะประมาณนี้อ่ะเนอะ

ตัวอย่างในชีวิตประจำวัน คือ เราอ่านข่าวจากเว็บแล้วเอ้ออออน่าสนใจมาก อยากแชร์ให้เพื่อนรับรู้ เราก็กดแชร์ลง Facebook อะไรงี้

ในรูปนี้ จากตัวอย่างเมื่อกี้ Activity A มาจากแอพ Chrome ส่ง Implicit Intent ไปให้ Android System แล้วโยนเจ้า Intent ไปที่ Activity B ของ Facebook

https://developer.android.com/guide/components/intents-filters#Types

การรับส่งข้อมูล

ถ้าใช้ Parcelable จะเร็วกว่า Serializable เนอะ ซึ่งเราใช้ data ที่เป็น Parcelable ตลอดเลย แหะ สามารถอ่านเพิ่มเติมได้ที่บล็อกพี่เอกน้า

https://akexorcist.dev/parcelable-vs-serializable/

ในส่วนของ Activity ก็น่าจะประมาณนี้เนอะ บล็อกตอนต่อไป มาเรียนรู้เรื่อง Fragment กันเถอะ

Reference :

เนื่องจากบางเว็บ ทาง Medium ไม่ได้ generate ตัว link มาให้สวยงามทุกอัน เลยขอเป็นในรูปแบบ bullet ก็แล้วกัน กันความน้อยใจ 555

--

--

Android Developer | Content Creator AKA. MikkiPastel | Web2 & Web3 Contributor