Design Application มัก Cumbersome บน Whiteboard ลูกศรชี้จากจุดนี้ไปจุดนั่น App ที่คิดว่ามี 1-2 Activities กลายเป็น Multiple Flows, Fragments, User Interactions เยอะ ไม่น่าจะดีถ้าเอาบน Whiteboard มา Replicate ใน Code ได้ง่าย?
Say Hello to Navigation Component 👋
สำหรับมือใหม่ Navigation Component ไม่ใช่ UI Class 替代 Layout ของ Activity/Fragment คิดว่าเป็น Map: แทน Continents = Fragments ต้องมี Directions ไประหว่าง Continents แสดง Fragments และ Connections แบบ Top-down บทความนี้ครอบ Main Aspects และ Integration
Ready to Set Sail? ⛵️
Learning The Ropes
Navigation Component ใช้ได้ตั้งแต่ Android Studio 3.3+ เพิ่ม Dependencies:
android { ... }
dependencies {
implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.0.0'
}
สมมติ Design App โครงสร้าง:
- Start Fragment
- Fragment A
- Fragment B
User ไป Fragment A หรือ B จาก Start Fragment
โดยไม่มี Navigation Component: ใช้ Code เปิด Fragment ตอน Click Button ( 익숙한 Code)
val myFragment: MyFragment = MyFragment()
supportFragmentManager.beginTransaction().add(R.id.container, myFragment).commit()
ตัวอย่างเล็กนี้ Code สั้น Uncomplicated แต่ไม่ Scale หาก App ใหญ่ Complex User Flows
All Aboard
เริ่ม Navigation Component: สร้าง Navigation Graph Graph = Map Outline User Flow สร้าง: Right-click res Folder → New Resource File ชื่อ user_flow_graph.xml Type = Navigation
ทุกการเดินทางมี Home Base เราเรียกว่า NavHost Placeholder สำหรับ Destinations Swap เมื่อ User Interact UI Add NavHost ใน Activity Main Layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="https://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/user_flow_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
เพิ่ม Fragment Element ที่จะ Display/Swap Fragments สังเกต Attribute navGraph Link ไป XML ที่สร้าง
ต้อง Add Starting Destination ไม่งั้น Compile Error
เปิด user_flow_graph.xml คลิก Plus Icon 小さいใน Navigation Editor:
Menu Pop Up: สร้าง Placeholder (Fill Later) หรือเลือกจาก Fragments ที่มี:
User Flow เริ่มจาก Start Fragment เลือกมันก่อน
เพิ่ม Fragment A และ B
Connect Destinations: Click Dot ที่โผล่ Hover Destination Drag ไปอีกตัว
Connection ระหว่าง Start Fragment และ A, B เรียกว่า Actions
Shiver me Timbers
คิดว่า Connect Destinations แล้วเสร็จ Magic จะทำงาน? ไม่ได้
ต้องบอก Code Navigate ไป Destination ยังไง? มี Magic นิดหน่อย
1. Add Gradle Plugin Safe Args Ensure Type Safety Navigate ระหว่าง Destinations
buildscript { /... }
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.0.0"
} }
เพิ่ม Plugin ใน App build.gradle:
apply plugin: "androidx.navigation.safeargs.kotlin"
ตรวจสอบ android.useAndroidX=true ใน gradle.properties
ทำไมต้อง Config ทั้งนี้? ตอนสร้าง Actions Android Studio Generate Code ใช้ Activate Actions Code = Methods/Classes Represent Actions ใช้ Start Fragment Example Generated Code Actions: Class StartFragmentDirections Methods = Actions ที่สร้าง:
- StartFragmentDirections.actionStartFragmentToFragmentA()
- StartFragmentDirections.actionStartFragmentToFragmentB()
Actions → Code แล้ว ใช้งาน:
val action = StartFragmentDirections.actionStartFragmentToFragmentA()
Last Step: ใช้ NavController Manage Navigation ใน NavHost Access ด้วย 3 วิธี:
- Fragment.findNavController()
- View.findNavController()
- Activity.findNavController(viewId: Int)
รวมกัน:
fragmentABtn.setOnClickListener { button ->
val action = StartFragmentDirections.actionStartFragmentToFragmentA()
button.findNavController().navigate(action)
}
Trim Your Sails
อยาก Pass Data ระหว่าง Destinations? Scenario: User Click Item อยากทำอะไรกับ Item ใน Next Destination ใช้ Destination Arguments เปิด user_flow_graph.xml คลิก Fragment A ด้านขวา Attributes Menu มี Arguments
Add Argument: Click ➕ Popup Configure Argument: Name, Type, Default Value เพิ่ม Argument String Type ใน Fragment A = Message จาก Start Fragment
ใน Start Fragment ที่ Define Action เรียก Generated Method ส่ง Argument:
fragmentABtn.setOnClickListener { button ->
val action = StartFragmentDirections.actionStartFragmentToFragmentA("Hello From Start Fragment")
button.findNavController().navigate(action)
}
Access ใน Fragment A 2 วิธี:
- Access Bundle Get Message Value
class FragmentA: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val bundle = arguments
val root = inflater.inflate(R.layout.fragment_a, container, false)
val textView: TextView = root.findViewById(R.id.textView)
textView.text = bundle?.getString("message")
return root
}
}
- ใช้
navArgs(ถ้าใช้ -ktx Dependencies)
class FragmentA: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val args: FragmentAArgs by navArgs()
val root = inflater.inflate(R.layout.fragment_a, container, false)
val textView: TextView = root.findViewById(R.id.textView)
textView.text = args.message
return root
}
}
✋ ใช้ navArgs ต้อง Enable Java8 ใน build.gradle
โค้ดทั้งหมดอยู่ใน GitHub Repository นี้