Dialogflow 行動開發整合:Android 與 Flutter 完整教學

Dialogflow 行動開發整合:Android 與 Flutter 完整教學
想在你的 App 中加入 AI 對話功能嗎?
不論是客服助理、語音助手還是互動式導覽,Dialogflow 都可以幫你實現。這篇文章教你如何在 Android 和 Flutter 中整合 Dialogflow,打造具備自然語言理解能力的行動應用。
如果你還不熟悉 Dialogflow,建議先閱讀 Dialogflow 完整指南。
行動 App 整合方式比較
在 App 中整合 Dialogflow 有兩種主要方式,各有優缺點。
直接 API 呼叫
App 直接呼叫 Dialogflow API:
App → Dialogflow API → 回應
優點:
- 架構簡單
- 延遲較低
- 不需要自建後端
缺點:
- API 金鑰暴露在 App 中(資安風險)
- 無法加入額外商業邏輯
- 難以記錄對話歷史
透過後端轉發
App 透過你的後端呼叫 Dialogflow:
App → 你的後端 → Dialogflow API → 你的後端 → App
優點:
- API 金鑰安全保存在後端
- 可以加入商業邏輯(驗證、記錄、過濾)
- 容易整合其他系統
缺點:
- 需要建置和維護後端
- 延遲稍高
- 架構較複雜
優缺點分析與選擇建議
| 方式 | 適用場景 |
|---|---|
| 直接 API | POC 驗證、學習用途、內部工具 |
| 後端轉發 | 正式產品、需要資安、企業應用 |
建議:正式上線的 App 一律使用後端轉發方式。
插圖:兩種整合架構比較圖
場景描述: 一張並排比較圖,左側顯示「直接 API 呼叫」架構(App → Dialogflow),右側顯示「後端轉發」架構(App → Backend → Dialogflow)。每個架構下方列出優缺點。
視覺重點:
- 主要內容清晰呈現
必須出現的元素:
- 依據描述中的關鍵元素
需要顯示的中文字: 無
顏色調性: 專業、清晰
避免元素: 抽象圖形、齒輪、發光特效
Slug:
dialogflow-mobile-integration-architecture
Android Studio 整合
專案設定
Step 1:新增依賴套件
在 app/build.gradle 中加入:
dependencies {
implementation 'com.google.cloud:google-cloud-dialogflow:4.0.0'
implementation 'io.grpc:grpc-okhttp:1.56.1'
implementation 'com.google.auth:google-auth-library-oauth2-http:1.19.0'
}
Step 2:設定網路權限
在 AndroidManifest.xml 中加入:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
Gradle 依賴套件
完整的 build.gradle 設定:
android {
compileSdk 34
defaultConfig {
minSdk 24
targetSdk 34
}
packagingOptions {
exclude 'META-INF/INDEX.LIST'
exclude 'META-INF/DEPENDENCIES'
}
}
dependencies {
implementation 'com.google.cloud:google-cloud-dialogflow:4.0.0'
implementation 'io.grpc:grpc-okhttp:1.56.1'
implementation 'io.grpc:grpc-stub:1.56.1'
}
Service Account 設定
Step 1:取得金鑰檔案
- 前往 Google Cloud Console > IAM > Service Accounts
- 建立或選擇服務帳戶
- 下載 JSON 金鑰檔案
Step 2:放置金鑰(開發用)
將金鑰檔案放到 app/src/main/res/raw/credentials.json
注意:這只適合開發測試。正式環境請使用後端轉發。
DetectIntent API 呼叫
建立 Dialogflow 客戶端類別:
class DialogflowClient(context: Context) {
private val sessionsClient: SessionsClient
private val session: SessionName
private val projectId = "your-project-id"
private val sessionId = UUID.randomUUID().toString()
init {
// 載入憑證
val stream = context.resources.openRawResource(R.raw.credentials)
val credentials = GoogleCredentials.fromStream(stream)
.createScoped(listOf("https://www.googleapis.com/auth/cloud-platform"))
val settings = SessionsSettings.newBuilder()
.setCredentialsProvider { credentials }
.build()
sessionsClient = SessionsClient.create(settings)
session = SessionName.of(projectId, sessionId)
}
suspend fun detectIntent(text: String): String {
return withContext(Dispatchers.IO) {
val textInput = TextInput.newBuilder()
.setText(text)
.setLanguageCode("zh-TW")
.build()
val queryInput = QueryInput.newBuilder()
.setText(textInput)
.build()
val request = DetectIntentRequest.newBuilder()
.setSession(session.toString())
.setQueryInput(queryInput)
.build()
val response = sessionsClient.detectIntent(request)
response.queryResult.fulfillmentText
}
}
fun close() {
sessionsClient.close()
}
}
範例程式碼
完整的 Activity 範例:
class ChatActivity : AppCompatActivity() {
private lateinit var dialogflowClient: DialogflowClient
private lateinit var messageAdapter: MessageAdapter
private val messages = mutableListOf<Message>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
dialogflowClient = DialogflowClient(this)
messageAdapter = MessageAdapter(messages)
recyclerView.adapter = messageAdapter
sendButton.setOnClickListener {
val text = inputEditText.text.toString()
if (text.isNotEmpty()) {
sendMessage(text)
inputEditText.text.clear()
}
}
}
private fun sendMessage(text: String) {
// 顯示使用者訊息
messages.add(Message(text, isUser = true))
messageAdapter.notifyItemInserted(messages.size - 1)
// 呼叫 Dialogflow
lifecycleScope.launch {
try {
val response = dialogflowClient.detectIntent(text)
messages.add(Message(response, isUser = false))
messageAdapter.notifyItemInserted(messages.size - 1)
recyclerView.scrollToPosition(messages.size - 1)
} catch (e: Exception) {
messages.add(Message("抱歉,發生錯誤", isUser = false))
messageAdapter.notifyItemInserted(messages.size - 1)
}
}
}
override fun onDestroy() {
super.onDestroy()
dialogflowClient.close()
}
}
Flutter 整合
套件選擇
Flutter 有幾個 Dialogflow 相關套件:
| 套件 | 說明 | 維護狀態 |
|---|---|---|
dialogflow_grpc | 官方 gRPC 協定 | 活躍 |
flutter_dialogflow | 社群套件 | 較少更新 |
dialogflow_flutter | 簡化版本 | 較少更新 |
建議:使用 dialogflow_grpc 或直接用 HTTP API。
跨平台實作
Step 1:新增依賴
在 pubspec.yaml 中:
dependencies:
flutter:
sdk: flutter
http: ^1.1.0
uuid: ^4.0.0
Step 2:建立 API 服務
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:uuid/uuid.dart';
class DialogflowService {
final String projectId;
final String accessToken; // 從後端取得
final String sessionId = Uuid().v4();
DialogflowService({required this.projectId, required this.accessToken});
Future<String> detectIntent(String text) async {
final url = Uri.parse(
'https://dialogflow.googleapis.com/v2/projects/$projectId/agent/sessions/$sessionId:detectIntent'
);
final response = await http.post(
url,
headers: {
'Authorization': 'Bearer $accessToken',
'Content-Type': 'application/json',
},
body: jsonEncode({
'queryInput': {
'text': {
'text': text,
'languageCode': 'zh-TW',
},
},
}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['queryResult']['fulfillmentText'];
} else {
throw Exception('Dialogflow API 錯誤: ${response.statusCode}');
}
}
}
範例 Widget
class ChatScreen extends StatefulWidget {
@override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final TextEditingController _controller = TextEditingController();
final List<ChatMessage> _messages = [];
late DialogflowService _dialogflow;
bool _isLoading = false;
@override
void initState() {
super.initState();
_dialogflow = DialogflowService(
projectId: 'your-project-id',
accessToken: 'your-access-token', // 實際應從後端取得
);
}
void _sendMessage() async {
final text = _controller.text.trim();
if (text.isEmpty) return;
setState(() {
_messages.add(ChatMessage(text: text, isUser: true));
_isLoading = true;
});
_controller.clear();
try {
final response = await _dialogflow.detectIntent(text);
setState(() {
_messages.add(ChatMessage(text: response, isUser: false));
});
} catch (e) {
setState(() {
_messages.add(ChatMessage(text: '抱歉,發生錯誤', isUser: false));
});
} finally {
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AI 助理')),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _messages.length,
itemBuilder: (context, index) {
final message = _messages[index];
return ChatBubble(
text: message.text,
isUser: message.isUser,
);
},
),
),
if (_isLoading) LinearProgressIndicator(),
_buildInputArea(),
],
),
);
}
Widget _buildInputArea() {
return Container(
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(hintText: '輸入訊息...'),
onSubmitted: (_) => _sendMessage(),
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: _sendMessage,
),
],
),
);
}
}
狀態管理整合
使用 Provider 管理對話狀態:
class ChatProvider extends ChangeNotifier {
final List<ChatMessage> _messages = [];
bool _isLoading = false;
List<ChatMessage> get messages => _messages;
bool get isLoading => _isLoading;
Future<void> sendMessage(String text) async {
_messages.add(ChatMessage(text: text, isUser: true));
_isLoading = true;
notifyListeners();
try {
final response = await _dialogflow.detectIntent(text);
_messages.add(ChatMessage(text: response, isUser: false));
} catch (e) {
_messages.add(ChatMessage(text: '發生錯誤', isUser: false));
} finally {
_isLoading = false;
notifyListeners();
}
}
}
插圖:Flutter 聊天介面截圖
場景描述: 一張手機模擬器截圖,顯示 Flutter 開發的聊天介面。畫面有對話氣泡(使用者在右、AI 在左)、底部輸入框和發送按鈕。介面設計簡潔現代。
視覺重點:
- 主要內容清晰呈現
必須出現的元素:
- 依據描述中的關鍵元素
需要顯示的中文字: 無
顏色調性: 專業、清晰
避免元素: 抽象圖形、齒輪、發光特效
Slug:
flutter-dialogflow-chat-interface
語音助理功能
讓 App 支援語音輸入和輸出。
Speech-to-Text 整合
Android(使用 SpeechRecognizer):
class VoiceInputManager(private val context: Context) {
private var speechRecognizer: SpeechRecognizer? = null
private var onResult: ((String) -> Unit)? = null
fun startListening(onResult: (String) -> Unit) {
this.onResult = onResult
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(context)
speechRecognizer?.setRecognitionListener(object : RecognitionListener {
override fun onResults(results: Bundle?) {
val matches = results?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
matches?.firstOrNull()?.let { onResult(it) }
}
override fun onError(error: Int) {
// 處理錯誤
}
// 其他必要的 override 方法...
})
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
putExtra(RecognizerIntent.EXTRA_LANGUAGE, "zh-TW")
}
speechRecognizer?.startListening(intent)
}
fun stopListening() {
speechRecognizer?.stopListening()
speechRecognizer?.destroy()
}
}
Flutter(使用 speech_to_text):
import 'package:speech_to_text/speech_to_text.dart' as stt;
class VoiceInput {
final stt.SpeechToText _speech = stt.SpeechToText();
bool _isListening = false;
Future<void> initialize() async {
await _speech.initialize();
}
void startListening(Function(String) onResult) {
_speech.listen(
onResult: (result) {
if (result.finalResult) {
onResult(result.recognizedWords);
}
},
localeId: 'zh_TW',
);
_isListening = true;
}
void stopListening() {
_speech.stop();
_isListening = false;
}
}
Text-to-Speech 回放
import 'package:flutter_tts/flutter_tts.dart';
class VoiceOutput {
final FlutterTts _tts = FlutterTts();
Future<void> initialize() async {
await _tts.setLanguage('zh-TW');
await _tts.setSpeechRate(0.5);
}
Future<void> speak(String text) async {
await _tts.speak(text);
}
Future<void> stop() async {
await _tts.stop();
}
}
持續對話模式
實現類似 Siri 的持續對話體驗:
class ContinuousDialogue {
final VoiceInput _voiceInput;
final VoiceOutput _voiceOutput;
final DialogflowService _dialogflow;
bool _isActive = false;
void startConversation() async {
_isActive = true;
while (_isActive) {
// 語音輸入
final userText = await _listenForInput();
if (userText == '結束對話') {
_isActive = false;
break;
}
// 呼叫 Dialogflow
final response = await _dialogflow.detectIntent(userText);
// 語音輸出
await _voiceOutput.speak(response);
// 短暫等待後繼續監聽
await Future.delayed(Duration(milliseconds: 500));
}
}
}
安全性考量
行動 App 的安全性尤其重要,API 金鑰一旦外洩就無法收回。
API 金鑰保護策略
絕對不要做的事:
// ❌ 把金鑰寫死在程式碼中
val apiKey = "AIzaSyXXXXXXXXXXXXXXX"
建議做法:
1. 使用後端 Proxy(推薦)
App → 你的後端(驗證 + 呼叫 Dialogflow)→ App
後端保存 API 金鑰,App 只需要跟後端通訊。
2. 使用 Firebase Remote Config
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener { task ->
if (task.isSuccessful) {
val apiKey = remoteConfig.getString("dialogflow_api_key")
}
}
3. 使用 Android Keystore
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
// 安全儲存和讀取金鑰
使用者認證整合
確保只有登入的使用者才能使用 AI 功能:
class SecureDialogflowClient(private val authService: AuthService) {
suspend fun detectIntent(text: String): String {
// 確認使用者已登入
val user = authService.currentUser
?: throw UnauthorizedException("請先登入")
// 取得存取權杖
val token = authService.getIdToken()
// 呼叫你的後端(不是直接呼叫 Dialogflow)
return apiService.chat(
token = token,
text = text
)
}
}
敏感資料處理
不要記錄敏感對話:
// ❌ 記錄完整對話
Log.d("Chat", "User said: $userInput")
// ✓ 只記錄必要資訊
Log.d("Chat", "User sent message, length: ${userInput.length}")
清除本地對話歷史:
fun clearChatHistory() {
// 清除記憶體
messages.clear()
// 清除本地儲存
sharedPreferences.edit().remove("chat_history").apply()
}
插圖:安全架構圖
場景描述: 一張安全架構圖,展示推薦的後端 Proxy 模式。App(帶鎖圖示)→ HTTPS → 後端(帶防火牆圖示)→ Dialogflow。標示金鑰只存在後端,App 不接觸敏感資訊。
視覺重點:
- 主要內容清晰呈現
必須出現的元素:
- 依據描述中的關鍵元素
需要顯示的中文字: 無
顏色調性: 專業、清晰
避免元素: 抽象圖形、齒輪、發光特效
Slug:
dialogflow-mobile-security-architecture
擔心 API 金鑰安全?行動 App 的資安問題很容易被忽略,一旦出事就很麻煩。預約架構諮詢,讓我們幫你設計安全的整合架構。
發布注意事項
App Store / Play Store 審核
隱私政策要求:
如果 App 會收集對話內容,需要在隱私政策中說明:
- 收集什麼資料
- 資料如何使用
- 資料保存多久
- 使用者如何刪除資料
權限說明:
如果使用麥克風權限,需要說明為什麼需要:
- 「用於語音輸入,讓您可以用說的與 AI 助理對話」
效能優化
減少啟動時間:
// 懶載入 Dialogflow 客戶端
private val dialogflowClient by lazy {
DialogflowClient(applicationContext)
}
背景處理:
// 在背景執行緒處理
viewModelScope.launch(Dispatchers.IO) {
val response = dialogflowClient.detectIntent(text)
withContext(Dispatchers.Main) {
updateUI(response)
}
}
離線處理
當沒有網路時的處理:
suspend fun detectIntent(text: String): String {
if (!isNetworkAvailable()) {
return "目前沒有網路連線,請稍後再試。"
}
return try {
dialogflowClient.detectIntent(text)
} catch (e: IOException) {
"網路連線不穩定,請稍後再試。"
}
}
更多 API 整合細節,請參考 Dialogflow Fulfillment 與 API 整合教學。Intent 設計請參考 Dialogflow Intent 與 Context 教學。費用估算請參考 Dialogflow 費用完整解析。
下一步
完成行動整合後,你可以:
- 優化對話設計:Dialogflow Intent 與 Context 完整教學
- 開發後端整合:Dialogflow Fulfillment 與 API 整合教學
- 控制成本:Dialogflow 費用完整解析
想在 App 中加入 AI 對話功能?
在 App 中整合 AI 對話涉及很多技術細節:API 整合、安全性、效能優化、審核合規...
如果你需要:
- 在現有 App 中加入 AI 客服功能
- 開發具備語音助理的新 App
- 設計安全可靠的整合架構
- 跨平台(iOS + Android)解決方案
預約 AI 導入諮詢,讓有經驗的團隊幫你規劃和實作。
我們已協助多個 App 成功整合 AI 對話功能,諮詢完全免費。
相關文章
Dialogflow CX vs ES 完整比較:2026 版本選擇指南
Dialogflow CX 和 ES 到底差在哪?本文詳細比較功能、費用、適用場景,附決策流程圖,幫你選對版本不踩雷、不花冤枉錢。
DialogflowDialogflow Fulfillment 與 API 整合完整教學
Dialogflow Webhook 開發完整教學:Cloud Functions 部署、DetectIntent API 呼叫、第三方 API 整合。含 Node.js、Python 範例程式碼與 GitHub 專案連結。
DialogflowDialogflow 完整指南 2026:從入門到實戰的 AI 對話機器人開發
完整解析 Google Dialogflow CX 與 ES 版本差異、Generative AI Agents、Vertex AI 整合、費用計算、LINE Bot 整合教學。從零開始打造企業級 AI 客服機器人,含 2026 最新生成式 AI 功能與實戰範例。