UI Preview บน Android Studio นั้นเป็นหนึ่งในเครื่องมือสำคัญที่ขาดไปไม่ได้สำหรับนักพัฒนาแอปบนแอนดรอยด์ เพราะจะช่วยให้นักพัฒนาสามารถเห็นผลลัพธ์จากการเขียนโค้ดสำหรับ UI ได้รวดเร็ว โดยไม่ต้อง Build App เพื่อรอดูผลลัพธ์ให้เสียเวลา

บทความในชุดเดียวกัน

แน่นอนว่า Android Studio ก็มี UI Preview สำหรับ Jetpack Compose ด้วยเช่นกัน และจะมีการทำงานที่แตกต่างไปจาก Android Views ที่จะช่วยให้นักพัฒนาสามารถสร้าง UI ด้วย Jetpack Compose ได้อย่างมีประสิทธิภาพมากขึ้น

ดังนั้นการเข้าใจวิธีใช้งาน UI Preview สำหรับ Jetpack Compose จึงเป็นสิ่งสำคัญที่จะช่วยให้นักพัฒนาสามารถใช้ประโยชน์ของ Jetpack Compose ได้อย่างคุ้มค่า

การสร้าง UI Preview สำหรับ Jetpack Compose

UI Preview ของ Jetpack Compose จะต่างจาก Android Views ตรงที่จะไม่ได้แสดงในทันทีที่สร้าง Composable ขึ้นมา แต่นักพัฒนาจะต้องสร้าง Composable เพื่อทำหน้าที่เป็น Preview Composable โดยใช้ Annotation ที่ชื่อว่า @Preview

import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun ImageCatalogue( /* ... */ ) { /* ... */ }

@Preview
@Composable
private fun ImageCataloguePreview() { 
    SleepingForLessTheme {
        ImageCatalogue( /* ... */ )
    }
}
SleepingForLessTheme คือ Theme Composable ที่ Android Studio สร้างขึ้นมาให้อัตโนมัติในตอนที่สร้างโปรเจคขึ้นมา และการสร้าง Preview Composable ก็ควรมี Theme Composable เสมอ เพื่อให้แสดงสีได้ถูกต้องตามที่กำหนดไว้ใน Theme Composable

โดยมีเงื่อนไขสำคัญว่า Preview Composable จะต้องเป็น Composable Function ที่เป็น Empty Parameter หรือก็คือเป็น Function ที่ไม่มี Function Parameter

แนะนำให้ทำ Preview Composable เป็น Private Function เสมอ เพื่อไม่ให้ Preview Composable ไปโผล่ใน Code Completion เวลาเขียนโค้ดในคลาสอื่น

และเนื่องจากนักพัฒนาสามารถสร้าง Composable หลายตัวในไฟล์เดียวกันได้ จึงสร้าง Preview Composable หลายตัวในไฟล์เดียวได้เช่นกัน

ไม่ใช่ทุกอย่างที่จะ Preview บน Android Studio ได้

เนื่องจาก UI Preview ของ Jetpack Compose เป็นการใช้ JVM เพื่อแสดงผลลัพธ์บน Android Studio ดังนั้นถ้าเป็น UI Component ที่จำเป็นต้องทำงานบนอุปกรณ์แอนดรอยด์จริง ๆ ก็จะไม่สามารถแสดงได้ ยกตัวอย่างเช่นการแสดงผลของ Google Maps เป็นต้น

ดังนั้นเพื่อไม่ให้ UI Component ดังกล่าวมีปัญหาบน UI Preview ก็สามารถใช้ LocalInspectionMode เพื่อสร้าง Composable ตัวอื่นมาแสดงผลใน UI Preview แทนชั่วคราวได้

if (LocalInspectionMode.current) {
    // For UI Preview
    Box( /* ... */ ) { /* ... */ }
} else {
    GoogleMap( /* ... */ )
}

และ Composable ที่มี ViewModel ก็ไม่สามารถแสดงบน UI Preview ได้เช่นกัน

Parameter ต่าง ๆ ของ @Preview

นักพัฒนาสามารถกำหนดค่าต่าง ๆ ใน @Preview เพื่อกำหนดรูปแบบในการแสดงผลของ UI Preview สำหรับ Composable แต่ละตัวได้ตามใจชอบ ซึ่งจะมี Parameter ดังต่อไปนี้

name: String
group: String
apiLevel: Int
widthDp: Int
heightDp: Int
locale: String
fontScale: Float
showSystemUi: Boolean
showBackground: Boolean
backgroundColor: Long
uiMode: Int
device: String
wallpaper: Int

นักพัฒนาสามารถใช้ประโยชน์ของ @Preview เพื่อช่วยให้มั่นใจได้ว่า Composable หรือ UI ที่สร้างขึ้นมาจะรองรับการกับแสดงผลใน Device Configuration ต่างได้อย่างถูกต้อง

ยกตัวอย่างเช่น เจ้าของบล็อกมี Composable ที่รองรับการแสดงข้อความได้ 2 ภาษา เจ้าของบล็อกจึงสร้าง Preview Composable สำหรับ 2 ภาษาแยกกันแบบนี้

@Preview(
    locale = "en",
    /* ... */
)
@Composable
private fun AddImageButtonEnglishPreview() { /* ... */ }

@Preview(
    locale = "th",
    /* ... */
)
@Composable
private fun AddImageButtonThaiPreview() { /* ... */ }

หรือ Composable ที่จะต้องรองรับการแสดงผลทั้ง Light Theme และ Dark Theme ก็สร้าง Preview Composable ที่กำหนด Theme ต่างกัน

@Preview
@Composable
private fun LightThemePreview() {
    AppTheme(darkTheme = false) { /* ... */ }
}

@Preview
@Composable
private fun DarkThemePreview() {
    AppTheme(darkTheme = true) { /* ... */ }
}

แสดง Preview Composable หลายตัวพร้อมกันด้วย Multipreview

จากตัวอย่างโค้ดก่อนหน้าจะเห็นว่าเจ้าของบล็อกต้องสร้าง Preview Composable หลายตัวเพื่อใช้ @Preview ที่กำหนดค่าแตกต่างกัน

@Preview(
    locale = "en",
    /* ... */
)
@Composable
private fun EnglishPreview() { /* ... */ }

@Preview(
    locale = "th",
    /* ... */
)
@Composable
private fun ThaiPreview() { /* ... */ }

แต่ในความเป็นจริงนั้น UI Preview ของ Jetpack Compose รองรับ Multipreview ด้วย ทำให้นักพัฒนาใส่ @Preview ใน Preview Composable ได้มากกว่าหนึ่งตัว ซึ่งจะได้โค้ดออกมาเป็นแบบนี้แทน

@Preview(
    name = "English",
    widthDp = 160,
    heightDp = 160,
    locale = "en",
)

@Preview(
    name = "Thai",
    widthDp = 160,
    heightDp = 160,
    locale = "th",
)
@Composable
private fun AddImageButtonPreview() {
    ComposeUIPreviewTheme(darkTheme = true) {
        Surface(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp),
            color = MaterialTheme.colorScheme.background,
        ) {
            AddImageButton()
        }
    }
}

Preview Composable ที่ใช้ Multipreview จะแสดงผลตามจำนวน @Preview ที่กำหนด และแสดงค่า name ให้เห็นใน UI Preview อีกด้วย

แต่ Theming (Light Theme กับ Dark Theme) จะเป็นการกำหนดผ่าน Theme Composable จึงใช้ Multipreview ไม่ได้

การใช้ Multipreview จึงมีประโยชน์มากเมื่อต้องการแสดง Preview Composable หลายตัวที่มีโค้ดเหมือนกัน แต่ต้องการกำหนดค่าใน @Preview ต่างกัน

กำหนดข้อมูลให้กับ Preview Composable ด้วย @PreviewParameter

สำหรับ Composable ที่มีแสดงผลแตกต่างไปตามข้อมูลที่ส่งเข้าไป นักพัฒนาสามารถใช้ @PreviewParameter เพื่อกำหนดข้อมูลสำหรับเงื่อนไขต่าง ๆ เพื่อแสดงผลลัพธ์​เป็น Preview Composable หลายตัวได้เช่นกัน

ยกตัวอย่างเช่น เจ้าของบล็อกสร้าง Composable ที่ชื่อว่า ImageCatalogue เพื่อแสดง UI ที่แตกต่างกันไปตามจำนวนข้อมูลที่ส่งเข้าไป

@Composable
fun ImageCatalogue(urls: List<Uri>) { /* ... */ }

แทนที่จะสร้าง Preview Composable หลายตัวตามเงื่อนไขต่าง ๆ ก็ให้สร้าง Preview Parameter Provider ขึ้นมาแทน

class ImageCataloguePreviewParameterProvider : PreviewParameterProvider<List<Uri>> {
    override val values = sequenceOf(
        listOf(),
        listOf(
            Uri.parse("https://picsum.photos/300/300"),
            Uri.parse("https://picsum.photos/250/300"),
            Uri.parse("https://picsum.photos/200/250"),
            Uri.parse("https://picsum.photos/300/350"),
        ),
        listOf(
            Uri.parse("https://picsum.photos/300/300"),
            Uri.parse("https://picsum.photos/250/300"),
            Uri.parse("https://picsum.photos/200/250"),
            Uri.parse("https://picsum.photos/300/350"),
            Uri.parse("https://picsum.photos/300/250"),
            Uri.parse("https://picsum.photos/450/300"),
            Uri.parse("https://picsum.photos/250/350"),
        )
    )
}

จะเห็นว่า Preview Paramter Provider หรือ ImageCataloguePreviewParameterProvider จะมี Override Member ที่ชื่อว่า values เพื่อกำหนดข้อมูลเป็น Sequence และข้อมูลเหล่านั้นก็จะถูกส่งให้ Preview Composable และ UI Preview ก็จะแสดงผลลัพธ์ตามจำนวนข้อมูลที่กำหนดไว้ใน Sequence นั่นเอง

จากนั้นให้โยนเข้าไปใน Preview Composable ด้วย @PreviewParameter แบบนี้ได้เลย

@Composable
private fun ImageCataloguePreview(
    @PreviewParameter(ImageCataloguePreviewParameterProvider::class)
    urls: List<Uri>,
) {
    SleepingForLessTheme {
        ImageCatalogue(urls)
    }
}

แล้ว UI Preview ก็จะแสดงผลตามจำนวนข้อมูลที่กำหนดไว้ใน Sequence ให้ทันที

Interactive Mode และ Run Preview

Interactive Mode เป็นหนึ่งในความสามารถของ UI Preview บน Android Studio ที่ทำให้ Jetpack Compose เป็นที่น่าสนใจสำหรับนักพัฒนาแอนดรอยด์ เพราะว่านักพัฒนาสามารถทดสอบการทำงานของ Composable ที่สร้างขึ้นมาได้ทันที ไม่ต้องรอ Build App แล้วทดสอบบนอุปกรณ์แอนดรอยด์ ซึ่งจะช่วยลดระยะเวลาในการพัฒนาแอปในส่วนของ UI ให้รวดเร็วมากขึ้น

Preview Composable ทุกตัวจะมีปุ่มให้กดตรงมุมขวาบนเพื่อเข้าสู้ Interactive Mode ซึ่งจะช่วยให้นักพัฒนาสามารถเขียนโค้ดเพื่อใช้ทดสอบการทำงานได้ โดยที่โค้ดสำหรับทดสอบก็จะอยู่แค่ใน Preview Composable เท่านั้น

0:00
/

และถ้าต้องการทดสอบบนอุปกรณ์แอนดรอยด์ก็สามารถกดปุ่ม Run Preview ที่อยู่ขวามือของปุ่ม Interactive Mode ก็ได้เช่นกัน ซึ่งจะเป็นการ Build App ที่ Android Studio จะสร้าง Activity ขึ้นมาสำหรับแสดง Preview Composable ตัวนั้นโดยเฉพาะ แล้วติดตั้งลงบนอุปกรณ์แอนดรอยด์เพื่อทดสอบได้ทันที ซึ่งจะมีความรวดเร็วกว่าการ Build App แบบปกติ

สรุป

ถึงแม้ว่า UI Preview สำหรับ Jetpack Compose จะต่างจาก Android Views ที่นักพัฒนาคุ้นเคยกันมาอย่างนาวนาน แต่ทว่าความสามารถของ UI Preview และ Preview Composable นั้นจะช่วยให้นักพัฒนาสามารถสร้าง UI ได้อย่างรวดเร็วมากขึ้น เพราะสามารถเห็นผลลัพธ์หรือ Feedback ให้กับนักพัฒนาได้เร็วกว่าการใช้ Android Views

ดังนั้นเพื่อให้สร้าง UI ด้วย Jetpack Compose ได้อย่างรวดเร็วและมีประสิทธิภาพก็อย่าลืมใช้ความสามารถของ UI Preview ที่มีให้ใน Jetpack Compose กันด้วยล่ะ