ในยุคที่ AI API มีราคาถูกลงอย่างต่อเนื่อง การพัฒนาแอป Android ที่เชื่อมต่อกับ AI อย่าง HolySheep AI สามารถทำได้ง่ายและประหยัดมาก บทความนี้จะสอนการสร้าง AI แชทอินเตอร์เฟซด้วย Jetpack Compose ตั้งแต่เริ่มต้นจนถึง production-ready พร้อมวิธีประหยัดค่าใช้จ่าย高达 85%

เปรียบเทียบต้นทุน AI API ปี 2026

ก่อนเริ่มพัฒนา มาดูต้นทุนจริงของแต่ละ AI Provider ในปี 2026 กัน:

ค่าใช้จ่ายสำหรับ 10 ล้าน tokens/เดือน

สรุป: ใช้ DeepSeek V3.2 ผ่าน HolySheep AI ประหยัดกว่า GPT-4.1 ถึง 95% และยังได้ latency ต่ำกว่า 50ms พร้อมระบบชำระเงินด้วย WeChat/Alipay รองรับอัตรา ¥1=$1

ตั้งค่า Gradle Dependencies

เพิ่ม dependencies ที่จำเป็นในไฟล์ build.gradle.kts (Module: app):

plugins {
    id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0"
}

dependencies {
    // Jetpack Compose
    implementation(platform("androidx.compose:compose-bom:2024.06.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.activity:activity-compose:1.9.0")
    implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.2")
    
    // Kotlin Coroutines
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
    
    // HTTP Client - Ktor
    implementation("io.ktor:ktor-client-core:2.3.12")
    implementation("io.ktor:ktor-client-android:2.3.12")
    implementation("io.ktor:ktor-client-content-negotiation:2.3.12")
    implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
    
    // JSON Serialization
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
    
    // ViewModel
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2")
    
    // Debug
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
}

สร้าง Chat Data Models และ API Service

กำหนด data models และ service สำหรับเรียก AI API:

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class Message(
    val role: String,
    val content: String
)

@Serializable
data class ChatRequest(
    val model: String,
    val messages: List,
    val max_tokens: Int = 2048,
    val temperature: Float = 0.7f
)

@Serializable
data class ChatResponse(
    val id: String,
    val choices: List
)

@Serializable
data class Choice(
    val message: Message
)

object ApiConfig {
    const val BASE_URL = "https://api.holysheep.ai/v1"
    const val API_KEY = "YOUR_HOLYSHEEP_API_KEY"
}

val json = Json {
    ignoreUnknownKeys = true
    isLenient = true
}

สร้าง ChatViewModel จัดการ State

ViewModel จัดการสถานะของแชทและเรียก API:

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.android.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

data class ChatMessage(
    val id: Long = System.currentTimeMillis(),
    val content: String,
    val isUser: Boolean,
    val isLoading: Boolean = false
)

class ChatViewModel : ViewModel() {
    private val client = HttpClient(Android) {
        install(ContentNegotiation) {
            json(json)
        }
    }
    
    private val _messages = MutableStateFlow>(emptyList())
    val messages: StateFlow> = _messages.asStateFlow()
    
    private val _isLoading = MutableStateFlow(false)
    val isLoading: StateFlow = _isLoading.asStateFlow()

    fun sendMessage(userMessage: String) {
        if (_isLoading.value) return
        
        viewModelScope.launch {
            val userMsg = ChatMessage(content = userMessage, isUser = true)
            _messages.value = _messages.value + userMsg
            
            _isLoading.value = true
            _messages.value = _messages.value + 
                ChatMessage(content = "", isUser = false, isLoading = true)
            
            try {
                val response: ChatResponse = client.post("${ApiConfig.BASE_URL}/chat/completions") {
                    contentType(ContentType.Application.Json)
                    header("Authorization", "Bearer ${ApiConfig.API_KEY}")
                    setBody(ChatRequest(
                        model = "deepseek-v3.2",
                        messages = listOf(
                            Message(role = "system", content = "คุณคือผู้ช่วย AI ที่เป็นมิตร"),
                            Message(role = "user", content = userMessage)
                        )
                    ))
                }.body()
                
                val aiResponse = response.choices.firstOrNull()?.message?.content ?: "ขอโทษครับ ไม่สามารถตอบได้"
                
                _messages.value = _messages.value.dropLast(1) + 
                    ChatMessage(content = aiResponse, isUser = false)
                    
            } catch (e: Exception) {
                _messages.value = _messages.value.dropLast(1) + 
                    ChatMessage(content = "เกิดข้อผิดพลาด: ${e.message}", isUser = false)
            } finally {
                _isLoading.value = false
            }
        }
    }
    
    override fun onCleared() {
        super.onCleared()
        client.close()
    }
}

สร้าง ChatScreen UI ด้วย Compose

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun ChatScreen(viewModel: ChatViewModel = viewModel()) {
    val messages by viewModel.messages.collectAsStateWithLifecycle()
    val isLoading by viewModel.isLoading.collectAsStateWithLifecycle()
    var inputText by remember { mutableStateOf("") }
    val listState = rememberLazyListState()
    
    LaunchedEffect(messages.size) {
        if (messages.isNotEmpty()) {
            listState.animateScrollToItem(messages.size - 1)
        }
    }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(0xFFF5F5F5))
            .padding(16.dp)
    ) {
        Text(
            text = "AI Chat - DeepSeek V3.2",
            fontSize = 20.sp,
            fontWeight = FontWeight.Bold,
            color = Color(0xFF333333),
            modifier = Modifier.padding(bottom = 16.dp)
        )
        
        LazyColumn(
            state = listState,
            modifier = Modifier.weight(1f),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            items(messages, key = { it.id }) { message ->
                ChatBubble(message = message)
            }
        }
        
        Spacer(modifier = Modifier.height(8.dp))
        
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(8.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            OutlinedTextField(
                value = inputText,
                onValueChange = { inputText = it },
                modifier = Modifier.weight(1f),
                placeholder = { Text("พิมพ์ข้อความ...") },
                enabled = !isLoading,
                shape = RoundedCornerShape(24.dp),
                colors = OutlinedTextFieldDefaults.colors(
                    focusedBorderColor = Color(0xFF6366F1),
                    unfocusedBorderColor = Color(0xFFE0E0E0)
                )
            )
            
            Button(
                onClick = {
                    if (inputText.isNotBlank()) {
                        viewModel.sendMessage(inputText)
                        inputText = ""
                    }
                },
                enabled = !isLoading && inputText.isNotBlank(),
                colors = ButtonDefaults.buttonColors(
                    containerColor = Color(0xFF6366F1)
                ),
                shape = RoundedCornerShape(24.dp)
            ) {
                Text(if (isLoading) "รอ..." else "ส่ง", color = Color.White)
            }
        }
    }
}

@Composable
fun ChatBubble(message: ChatMessage) {
    val isUser = message.isUser
    
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = if (isUser) Arrangement.End else Arrangement.Start
    ) {
        Box(
            modifier = Modifier
                .widthIn(max = 280.dp)
                .background(
                    color = if (isUser) Color(0xFF6366F1) else Color.White,
                    shape = RoundedCornerShape(
                        topStart = 16.dp,
                        topEnd = 16.dp,
                        bottomStart = if (isUser) 16.dp else 4.dp,
                        bottomEnd = if (isUser) 4.dp else 16.dp
                    )
                )
                .padding(12.dp)
        ) {
            if (message.isLoading) {
                CircularProgressIndicator(
                    modifier = Modifier.size(20.dp),
                    strokeWidth = 2.dp,
                    color = Color(0xFF6366F1)
                )
            } else {
                Text(
                    text = message.content,
                    color = if (isUser) Color.White else Color(0xFF333333),
                    fontSize = 14.sp
                )
            }
        }
    }
}

รวมทุกอย่างใน MainActivity

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.yourapp.chatbot.ui.theme.ChatBotTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            ChatBotTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    ChatScreen()
                }
            }
        }
    }
}

ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

กรณีที่ 1: Network Security Config Error

// ปัญหา: Cleartext HTTP traffic not permitted
// ไฟล์: android/app/src/main/res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">api.holysheep.ai</domain>
    </domain-config>
</network-security-config>

วิธีแก้: เพิ่ม android:networkSecurityConfig="@xml/network_security_config" ใน <application> tag ของ AndroidManifest.xml

กรณีที่ 2: kotlinx.serialization.MissingFieldException

// ปัญหา: ได้รับ JSON response ที่ไม่ตรงกับ data class

// วิธีแก้ 1: เพิ่ม default values ใน data class
@Serializable
data class ChatResponse(
    val id: String = "",
    val choices: List<Choice> = emptyList()
)

@Serializable
data class Choice(
    val message: Message = Message("", "")
)

// วิธีแก้ 2: ใช้ Json builder ที่ lenient
val json = Json {
    ignoreUnknownKeys = true  // ข้าม fields ที่ไม่รู้จัก
    isLenient = true          // ยอมรับ JSON ที่ไม่สมบูรณ์
    coerceInputValues = true  // ใช้ค่า default ถ้า field หายไป
}

กรณีที่ 3: API Key Invalid หรือ Quota Exceeded

// ปัญหา: 401 Unauthorized หรือ 429 Rate Limit

// วิธีแก้: เพิ่ม error handling ใน ViewModel
try {
    val response: ChatResponse = client.post("...") {
        // ... request setup
    }.body()
} catch (e: HttpResponseException) {
    when (e.response.status.value) {
        401 -> "API Key ไม่ถูกต้อง กรุณาตรวจสอบที่ https://www.holysheep.ai/register"
        429 -> "Quota หมดแล้ว รอสักครู่หรืออัพเกรดแพ็คเกจ"
        else -> "เกิดข้อผิดพลาด: ${e.message}"
    }
} catch (e: Exception) {
    "เชื่อมต่อไม่ได้ ตรวจสอบอินเทอร์เน็ตของคุณ"
}

// วิธีป้องกัน: ตรวจสอบ quota ล่วงหน้า
val quotaCheck = client.get("${ApiConfig.BASE_URL}/quota") {
    header("Authorization", "Bearer ${ApiConfig.API_KEY}")
}
val remaining = quotaCheck.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: 0

สรุป

การพัฒนา AI แชทอินเตอร์เฟซด้วย Jetpack Compose ไม่ใช่เรื่องยาก สิ่งสำคัญคือการเลือก API Provider ที่เหมาะสมกับงบประมาณ โดย HolySheep AI เสนอราคาที่ประหยัดกว่า 85% เมื่อเทียบกับ OpenAI พร้อม latency ต่ำกว่า 50ms และรองรับหลายโมเดล AI ชั้นนำ

ด้วยโค้ดในบทความนี้ คุณสามารถสร้าง AI แชทบอทที่ใช้งานได้จริงภายในเวลาไม่กี่ชั่วโมง พร้อมระบบ error handling ที่ครบถ้วน

👉 สมัคร HolySheep AI — รับเครดิตฟรีเมื่อลงทะเบียน