هوش مصنوعی در Flutter؛ آموزش ساخت اپلیکیشن AI با Dart و API درواره
در این آموزش عملی، از هوش مصنوعی برای کدنویسی Flutter استفاده میکنیم و یک اپلیکیشن چت واقعی با Dart، معماری MVVM، Backend پایتون و API درواره برای Android، iOS و Web میسازیم.
Flutter یکی از محبوبترین فریمورکها برای ساخت اپلیکیشنهای چندسکویی است. با یک کدبیس مشترک میتوانید برای Android، iOS، وب، ویندوز، macOS و لینوکس اپلیکیشن بسازید. ترکیب Flutter با مدلهای هوش مصنوعی این امکان را فراهم میکند که محصولاتی مانند دستیار هوشمند، چتبات، خلاصهساز، مترجم، تولیدکننده محتوا و تحلیلگر متن را با رابط کاربری یکپارچه روی چند پلتفرم ارائه کنید.
عبارت «هوش مصنوعی در Flutter» معمولاً به دو کاربرد متفاوت اشاره دارد:
- استفاده از ابزارهای هوش مصنوعی برای نوشتن، توضیح، دیباگ و تست کدهای Flutter و Dart
- اتصال اپلیکیشن Flutter به مدلهای هوش مصنوعی از طریق API
در کاربرد اول، هوش مصنوعی نقش دستیار برنامهنویس را دارد. در کاربرد دوم، هوش مصنوعی یکی از قابلیتهای اصلی محصول شما است و کاربران اپلیکیشن مستقیماً از آن استفاده میکنند.
در این راهنمای عملی هر دو مسیر را بررسی میکنیم و سپس یک اپلیکیشن چت کامل میسازیم که:
- با Flutter و Dart توسعه داده میشود.
- روی Android، iOS و Web قابل اجرا است.
- معماری قابل توسعه دارد.
- پیام کاربر را به Backend اختصاصی میفرستد.
- Backend به API هوش مصنوعی درواره متصل میشود.
- پاسخ مدل را در رابط چت نمایش میدهد.
- وضعیت Loading، خطا و ارسال دوباره را مدیریت میکند.
- کلید API را داخل اپلیکیشن قرار نمیدهد.
Flutter چیست؟
Flutter فریمورک توسعه رابط کاربری است که با زبان Dart کار میکند. در Flutter، رابط کاربری از Widgetها ساخته میشود و تغییر State باعث بازسازی بخشهای وابسته رابط میشود.
مهمترین مزایای Flutter عبارتاند از:
- یک کدبیس برای چند پلتفرم
- توسعه سریع رابط کاربری
- Hot Reload
- Widgetهای قابل ترکیب
- پشتیبانی از Material و Cupertino
- اکوسیستم گسترده Packageها
- عملکرد مناسب برای اپلیکیشنهای موبایل
- امکان توسعه Web و Desktop
Flutter برای ساخت رابط اپلیکیشن بسیار مناسب است؛ اما اجرای مدلهای زبانی بزرگ معمولاً روی خود دستگاه انجام نمیشود. اپلیکیشن از طریق شبکه به Backend متصل میشود و Backend درخواست را برای مدل ارسال میکند.
هوش مصنوعی چه کاربردی در توسعه Flutter دارد؟
هوش مصنوعی میتواند در مراحل مختلف توسعه Flutter به برنامهنویس کمک کند.
| مرحله | کاربرد هوش مصنوعی |
|---|---|
| تحلیل نیازمندی | تبدیل ایده به صفحه، Feature و User Flow |
| طراحی پروژه | پیشنهاد معماری و ساختار پوشهها |
| ساخت رابط | تولید Widget و Layout |
| مدیریت State | پیشنهاد ViewModel، Provider، Riverpod یا BLoC |
| شبکه | تولید Service و مدل JSON |
| دیباگ | تحلیل Exception و Stack Trace |
| تست | تولید Unit Test و Widget Test |
| ریفکتور | تقسیم Widgetهای بزرگ و کاهش تکرار |
| مستندسازی | تولید README و Doc Comment |
| انتشار | تهیه چکلیست Build و تنظیمات محیط |
نمونه پرامپت برای تولید صفحه Flutter:
یک صفحه چت با Flutter و Material 3 بساز.
نیازمندیها:
- پیامهای کاربر در سمت راست باشند.
- پیامهای دستیار در سمت چپ باشند.
- متن فارسی و RTL پشتیبانی شود.
- پیامها با ListView.builder نمایش داده شوند.
- پایین صفحه TextField و دکمه ارسال باشد.
- هنگام دریافت پاسخ Loading نمایش داده شود.
- رابط از Dark Mode پشتیبانی کند.
- منطق شبکه داخل Widget قرار نگیرد.
ابزارهای هوش مصنوعی مناسب برنامهنویسی Flutter
برای توسعه Flutter میتوانید از گروههای مختلف ابزارهای هوش مصنوعی استفاده کنید.
دستیارهای گفتوگویی
مانند ChatGPT، Claude و Gemini برای این کارها مناسباند:
- یادگیری Dart
- توضیح Widgetها
- تحلیل خطا
- طراحی معماری
- مقایسه روشهای مدیریت State
- تولید نمونهکد
- نوشتن تست
ابزارهای داخل IDE
ابزارهایی مانند GitHub Copilot، Cursor، Cline، Roo Code و سایر Coding Agentها میتوانند با Context بیشتری از پروژه کار کنند:
- تکمیل کد در فایل
- ویرایش چند فایل
- تحلیل ساختار پروژه
- تولید تست
- اصلاح Importها
- اجرای Command
- بررسی خطاهای Analyzer
- ساخت Feature چندمرحلهای
API هوش مصنوعی
API زمانی استفاده میشود که میخواهید قابلیت هوش مصنوعی را به خود اپلیکیشن اضافه کنید.
برای مثال:
- دستیار پاسخگویی
- تولید متن
- خلاصهسازی
- استخراج اطلاعات
- تحلیل بازخورد
- ترجمه
- اصلاح نگارش
- ساخت برنامه روزانه
- پاسخگویی براساس اسناد
آیا میتوان مستقیماً Flutter را به API هوش مصنوعی متصل کرد؟
از نظر فنی میتوانید یک درخواست HTTP مستقیم از Flutter ارسال کنید، اما قراردادن کلید API در اپلیکیشن منتشرشده روش مناسبی نیست.
اگر کلید در این محلها قرار بگیرد، همچنان امکان استخراج آن وجود دارد:
- فایل Dart
- فایل
.envداخل اپلیکیشن dart-define- Native Code
- فایل Asset
- Build Configuration
- APK یا IPA
معماری پیشنهادی:
اپلیکیشن Flutter
↓
Backend اختصاصی شما
↓
API هوش مصنوعی درواره
↓
مدل انتخابی
Backend مسئول نگهداری کلید، احراز هویت، محدودیت مصرف، مدیریت هزینه و اعتبارسنجی درخواستها است.
معماری پیشنهادی اپلیکیشن Flutter
مستندات رسمی Flutter برای پروژههای قابل توسعه، جداسازی مسئولیتها و معماری لایهای را توصیه میکند. در الگوی پیشنهادی، View و ViewModel در لایه UI و Repository و Service در لایه داده قرار میگیرند. راهنمای رسمی معماری Flutter
در پروژه این مقاله از ساختار زیر استفاده میکنیم:
lib/
├── main.dart
├── models/
│ └── chat_message.dart
├── services/
│ └── chat_api_service.dart
├── repositories/
│ └── chat_repository.dart
├── view_models/
│ └── chat_view_model.dart
└── views/
├── chat_page.dart
└── widgets/
├── message_bubble.dart
└── message_input.dart
مسئولیت اجزا:
| جزء | مسئولیت |
|---|---|
| Model | نمایش ساختار داده |
| Service | ارتباط مستقیم با Backend |
| Repository | منبع داده و مدیریت نتیجه Service |
| ViewModel | مدیریت State و منطق رابط |
| View | نمایش State و دریافت Event کاربر |
| Widget | اجزای قابل استفاده مجدد رابط |
این ساختار برای یک نمونه آموزشی ساده است و میتوانید آن را براساس نیاز پروژه توسعه دهید.
پیشنیازهای آموزش
برای اجرای پروژه به این موارد نیاز دارید:
- Flutter SDK
- Dart SDK همراه Flutter
- Android Studio یا VS Code
- Android Emulator، دستگاه واقعی یا مرورگر
- Python برای Backend
- حساب درواره
- کلید API درواره
- Model ID یکی از مدلهای متنی
برای شروع به درواره مراجعه کنید. شناسه مدلها و قیمت بهروز آنها در صفحه مدلهای درواره قرار دارد.
در کدهای مقاله از این مقادیر استفاده میکنیم:
YOUR_DARVAREH_API_KEY
YOUR_MODEL_ID
نصب و بررسی Flutter
پس از نصب Flutter، دستور زیر را اجرا کنید:
flutter doctor
این دستور وضعیت Flutter SDK، Android Toolchain، مرورگر، IDE و دستگاههای متصل را بررسی میکند.
سپس دستگاهها را نمایش دهید:
flutter devices
نسخه Flutter:
flutter --version
اگر flutter doctor خطایی نمایش میدهد، قبل از ادامه موارد اصلی Android Toolchain یا SDK را برطرف کنید.
ساخت پروژه Flutter
دستور زیر را اجرا کنید:
flutter create darvareh_ai_app
cd darvareh_ai_app
پروژه را در VS Code باز کنید:
code .
یا پوشه را از Android Studio باز کنید.
اجرای اولیه:
flutter run
پیش از اضافهکردن قابلیت هوش مصنوعی، مطمئن شوید پروژه نمونه بدون خطا اجرا میشود.
افزودن Packageهای موردنیاز
برای ارتباط HTTP و مدیریت ساده State، Packageهای زیر را اضافه کنید:
flutter pub add http
flutter pub add provider
در این مقاله از Package رسمی و رایج http برای درخواست شبکه استفاده میکنیم. راهنمای Flutter نیز برای ارسال داده به سرور، اضافهکردن http، ارسال درخواست و تبدیل پاسخ JSON به Object را توضیح میدهد. آموزش رسمی ارسال داده در Flutter
بخش اول: ساخت Backend با FastAPI
ابتدا Backend را ایجاد میکنیم تا کلید درواره خارج از اپلیکیشن باقی بماند.
پوشه Backend:
mkdir flutter-ai-backend
cd flutter-ai-backend
ساخت محیط مجازی:
python -m venv .venv
فعالسازی در ویندوز:
.venv\Scripts\Activate.ps1
فعالسازی در لینوکس و macOS:
source .venv/bin/activate
نصب وابستگیها:
pip install fastapi uvicorn openai python-dotenv
فایل .env:
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL=YOUR_MODEL_ID
DARVAREH_BASE_URL=https://api.darvareh.ir/v1
فایل .gitignore:
.env
.venv/
__pycache__/
*.pyc
فایل main.py:
import os
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from openai import (
APIConnectionError,
AuthenticationError,
OpenAI,
RateLimitError,
)
from pydantic import BaseModel, Field
load_dotenv()
api_key = os.getenv("DARVAREH_API_KEY")
model_id = os.getenv("DARVAREH_MODEL")
base_url = os.getenv(
"DARVAREH_BASE_URL",
"https://api.darvareh.ir/v1",
)
if not api_key:
raise RuntimeError(
"DARVAREH_API_KEY is not configured."
)
if not model_id:
raise RuntimeError(
"DARVAREH_MODEL is not configured."
)
client = OpenAI(
api_key=api_key,
base_url=base_url,
timeout=30.0,
max_retries=2,
)
app = FastAPI(
title="Flutter AI Backend",
version="1.0.0",
)
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000",
"http://localhost:5000",
],
allow_credentials=False,
allow_methods=["GET", "POST"],
allow_headers=["Content-Type"],
)
class ChatRequest(BaseModel):
message: str = Field(
min_length=1,
max_length=4000,
)
class ChatResponse(BaseModel):
answer: str
@app.get("/health")
def health():
return {
"status": "ok",
}
@app.post(
"/chat",
response_model=ChatResponse,
)
def chat(request: ChatRequest):
try:
response = client.chat.completions.create(
model=model_id,
messages=[
{
"role": "system",
"content": (
"تو یک دستیار فارسی دقیق و کاربردی هستی. "
"پاسخ را متناسب با سؤال کاربر بنویس. "
"اگر اطلاعات کافی نیست، آن را اعلام کن."
),
},
{
"role": "user",
"content": request.message,
},
],
temperature=0.3,
max_tokens=800,
)
answer = response.choices[0].message.content
if not answer:
raise HTTPException(
status_code=502,
detail=(
"مدل پاسخ قابل استفادهای تولید نکرد."
),
)
return ChatResponse(answer=answer)
except AuthenticationError:
raise HTTPException(
status_code=500,
detail=(
"تنظیمات سرویس هوش مصنوعی نامعتبر است."
),
)
except RateLimitError:
raise HTTPException(
status_code=429,
detail=(
"تعداد درخواستها بیش از حد مجاز است."
),
)
except APIConnectionError:
raise HTTPException(
status_code=503,
detail=(
"ارتباط با سرویس هوش مصنوعی برقرار نشد."
),
)
اجرای Backend:
uvicorn main:app --reload --host 0.0.0.0 --port 8000
بررسی سلامت:
http://127.0.0.1:8000/health
مستندات تعاملی:
http://127.0.0.1:8000/docs
نمونه Request:
{
"message": "Flutter را در یک پاراگراف معرفی کن."
}
نمونه Response:
{
"answer": "Flutter فریمورکی برای ساخت اپلیکیشنهای چندسکویی با زبان Dart است."
}
تنظیم CORS برای Flutter Web
CORS بیشتر برای نسخه وب اهمیت دارد. در محیط Production نباید هر Origin را بدون محدودیت مجاز کنید.
در مثال قبلی فقط چند Origin محلی قرار داده شدهاند:
allow_origins=[
"http://localhost:3000",
"http://localhost:5000",
]
پورت واقعی اجرای Flutter Web ممکن است متفاوت باشد. هنگام اجرا، آدرس نمایشدادهشده در ترمینال را بررسی و همان Origin را به تنظیمات Development اضافه کنید.
برای اجرای روی یک پورت مشخص:
flutter run -d chrome --web-port 5000
در این حالت Origin محلی برابر است با:
http://localhost:5000
بخش دوم: Permission اینترنت در Android
فایل زیر را باز کنید:
android/app/src/main/AndroidManifest.xml
Permission اینترنت را اضافه کنید:
<uses-permission android:name="android.permission.INTERNET" />
نمونه:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="darvareh_ai_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<!-- Flutter activity -->
</application>
</manifest>
ارتباط HTTP محلی در Android
برای اجرای Development با HTTP محلی ممکن است لازم باشد در تگ application این گزینه را موقتاً اضافه کنید:
android:usesCleartextTraffic="true"
نمونه:
<application
android:usesCleartextTraffic="true"
android:label="darvareh_ai_app"
...>
این گزینه برای Production مناسب نیست. در نسخه نهایی، Backend باید HTTPS داشته باشد و Cleartext Traffic غیرفعال شود.
آدرس Backend در هر پلتفرم
آدرس localhost در تمام پلتفرمها رفتار یکسانی ندارد.
| محیط اجرا | آدرس معمول Backend محلی |
|---|---|
| Android Emulator | http://10.0.2.2:8000 |
| iOS Simulator | http://127.0.0.1:8000 |
| Flutter Web | http://127.0.0.1:8000 |
| Windows Desktop | http://127.0.0.1:8000 |
| macOS Desktop | http://127.0.0.1:8000 |
| دستگاه واقعی | IP شبکه کامپیوتر یا دامنه HTTPS |
در Android Emulator، این آدرس اشتباه است:
http://127.0.0.1:8000
زیرا به خود Emulator اشاره میکند. از این آدرس استفاده کنید:
http://10.0.2.2:8000
بخش سوم: ساخت مدل پیام
فایل زیر را ایجاد کنید:
lib/models/chat_message.dart
محتوا:
enum MessageRole {
user,
assistant,
}
class ChatMessage {
const ChatMessage({
required this.id,
required this.text,
required this.role,
required this.createdAt,
});
final String id;
final String text;
final MessageRole role;
final DateTime createdAt;
bool get isUser => role == MessageRole.user;
}
بخش چهارم: ساخت Chat API Service
فایل:
lib/services/chat_api_service.dart
کد:
import 'dart:convert';
import 'package:http/http.dart' as http;
class ChatApiException implements Exception {
const ChatApiException(this.message);
final String message;
@override
String toString() => message;
}
class ChatApiService {
ChatApiService({
required this.baseUrl,
http.Client? client,
}) : _client = client ?? http.Client();
final String baseUrl;
final http.Client _client;
Future<String> sendMessage(String message) async {
final uri = Uri.parse('$baseUrl/chat');
try {
final response = await _client
.post(
uri,
headers: const {
'Content-Type':
'application/json; charset=UTF-8',
},
body: jsonEncode({
'message': message,
}),
)
.timeout(
const Duration(seconds: 40),
);
final decodedBody = _decodeBody(response.body);
if (response.statusCode == 200) {
final answer = decodedBody['answer'];
if (answer is! String || answer.trim().isEmpty) {
throw const ChatApiException(
'پاسخ سرور معتبر نیست.',
);
}
return answer.trim();
}
final detail = decodedBody['detail'];
if (detail is String && detail.isNotEmpty) {
throw ChatApiException(detail);
}
throw ChatApiException(
'خطای سرور با کد ${response.statusCode}',
);
} on ChatApiException {
rethrow;
} on FormatException {
throw const ChatApiException(
'پاسخ سرور قابل پردازش نیست.',
);
} on Exception {
throw const ChatApiException(
'ارتباط با سرور برقرار نشد.',
);
}
}
Map<String, dynamic> _decodeBody(String body) {
if (body.trim().isEmpty) {
return {};
}
final decoded = jsonDecode(body);
if (decoded is! Map<String, dynamic>) {
throw const FormatException(
'Invalid response format',
);
}
return decoded;
}
void dispose() {
_client.close();
}
}
چرا Timeout تعریف کردیم؟
درخواست مدل ممکن است چند ثانیه طول بکشد. اگر هیچ Timeoutی نداشته باشیم، برنامه میتواند مدت زیادی منتظر بماند.
.timeout(
const Duration(seconds: 40),
)
مقدار مناسب به مدل، طول پاسخ، نوع درخواست و شرایط شبکه بستگی دارد. Timeout بسیار کوتاه باعث شکست درخواستهای سالم و Timeout بسیار بلند باعث تجربه کاربری نامناسب میشود.
بخش پنجم: ساخت Repository
فایل:
lib/repositories/chat_repository.dart
کد:
import '../services/chat_api_service.dart';
abstract interface class ChatRepository {
Future<String> sendMessage(String message);
}
class RemoteChatRepository
implements ChatRepository {
RemoteChatRepository({
required ChatApiService service,
}) : _service = service;
final ChatApiService _service;
@override
Future<String> sendMessage(
String message,
) {
return _service.sendMessage(message);
}
}
Repository به ViewModel اجازه میدهد بدون وابستگی مستقیم به HTTP با داده کار کند. در تست نیز میتوانیم نسخه Fake آن را جایگزین کنیم.
بخش ششم: ساخت ViewModel
فایل:
lib/view_models/chat_view_model.dart
کد:
import 'package:flutter/foundation.dart';
import '../models/chat_message.dart';
import '../repositories/chat_repository.dart';
class ChatViewModel extends ChangeNotifier {
ChatViewModel({
required ChatRepository repository,
}) : _repository = repository;
final ChatRepository _repository;
final List<ChatMessage> _messages = [];
List<ChatMessage> get messages =>
List.unmodifiable(_messages);
bool _isLoading = false;
bool get isLoading => _isLoading;
String? _errorMessage;
String? get errorMessage => _errorMessage;
Future<void> sendMessage(
String rawMessage,
) async {
final message = rawMessage.trim();
if (message.isEmpty || _isLoading) {
return;
}
_errorMessage = null;
_messages.add(
ChatMessage(
id: _createId(),
text: message,
role: MessageRole.user,
createdAt: DateTime.now(),
),
);
_isLoading = true;
notifyListeners();
try {
final answer =
await _repository.sendMessage(message);
_messages.add(
ChatMessage(
id: _createId(),
text: answer,
role: MessageRole.assistant,
createdAt: DateTime.now(),
),
);
} on Exception catch (error) {
_errorMessage = error.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
void clearError() {
if (_errorMessage == null) {
return;
}
_errorMessage = null;
notifyListeners();
}
void clearConversation() {
if (_isLoading) {
return;
}
_messages.clear();
_errorMessage = null;
notifyListeners();
}
String _createId() {
return DateTime
.now()
.microsecondsSinceEpoch
.toString();
}
}
بخش هفتم: ساخت Message Bubble
فایل:
lib/views/widgets/message_bubble.dart
کد:
import 'package:flutter/material.dart';
import '../../models/chat_message.dart';
class MessageBubble extends StatelessWidget {
const MessageBubble({
required this.message,
super.key,
});
final ChatMessage message;
@override
Widget build(BuildContext context) {
final colorScheme =
Theme.of(context).colorScheme;
final alignment = message.isUser
? Alignment.centerRight
: Alignment.centerLeft;
final backgroundColor = message.isUser
? colorScheme.primaryContainer
: colorScheme.surfaceContainerHighest;
return Align(
alignment: alignment,
child: Container(
constraints: const BoxConstraints(
maxWidth: 560,
),
margin: const EdgeInsets.symmetric(
vertical: 5,
),
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 11,
),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(18),
),
child: SelectableText(
message.text,
textDirection: TextDirection.rtl,
style: Theme.of(context)
.textTheme
.bodyLarge,
),
),
);
}
}
از SelectableText استفاده کردهایم تا کاربر بتواند پاسخ را انتخاب و کپی کند.
بخش هشتم: ساخت Message Input
فایل:
lib/views/widgets/message_input.dart
کد:
import 'package:flutter/material.dart';
class MessageInput extends StatefulWidget {
const MessageInput({
required this.isLoading,
required this.onSend,
super.key,
});
final bool isLoading;
final Future<void> Function(String message)
onSend;
@override
State<MessageInput> createState() =>
_MessageInputState();
}
class _MessageInputState
extends State<MessageInput> {
final _controller = TextEditingController();
bool get _canSend {
return _controller.text.trim().isNotEmpty &&
!widget.isLoading;
}
Future<void> _submit() async {
final message = _controller.text.trim();
if (message.isEmpty || widget.isLoading) {
return;
}
_controller.clear();
setState(() {});
await widget.onSend(message);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: Material(
color: Theme.of(context)
.colorScheme
.surface,
elevation: 8,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.end,
children: [
Expanded(
child: TextField(
controller: _controller,
enabled: !widget.isLoading,
textDirection:
TextDirection.rtl,
minLines: 1,
maxLines: 5,
onChanged: (_) {
setState(() {});
},
onSubmitted: (_) {
if (_canSend) {
_submit();
}
},
decoration:
const InputDecoration(
hintText:
'پیام خود را بنویسید...',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
IconButton.filled(
onPressed:
_canSend ? _submit : null,
tooltip: 'ارسال',
icon: widget.isLoading
? const SizedBox(
width: 20,
height: 20,
child:
CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(Icons.send),
),
],
),
),
),
);
}
}
بخش نهم: ساخت صفحه Chat
فایل:
lib/views/chat_page.dart
کد:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../view_models/chat_view_model.dart';
import 'widgets/message_bubble.dart';
import 'widgets/message_input.dart';
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
@override
State<ChatPage> createState() =>
_ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
final _scrollController =
ScrollController();
void _scrollToBottom() {
WidgetsBinding.instance
.addPostFrameCallback((_) {
if (!_scrollController.hasClients) {
return;
}
_scrollController.animateTo(
_scrollController
.position
.maxScrollExtent,
duration:
const Duration(milliseconds: 250),
curve: Curves.easeOut,
);
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final viewModel =
context.watch<ChatViewModel>();
_scrollToBottom();
return Scaffold(
appBar: AppBar(
title: const Text(
'دستیار هوشمند',
),
actions: [
IconButton(
onPressed: viewModel.isLoading
? null
: viewModel.clearConversation,
tooltip: 'پاککردن گفتگو',
icon: const Icon(
Icons.delete_outline,
),
),
],
),
body: Column(
children: [
Expanded(
child: viewModel.messages.isEmpty
? const _EmptyConversation()
: ListView.builder(
controller:
_scrollController,
padding:
const EdgeInsets.all(
12,
),
itemCount:
viewModel.messages.length,
itemBuilder: (
context,
index,
) {
return MessageBubble(
message:
viewModel
.messages[index],
);
},
),
),
if (viewModel.errorMessage != null)
_ErrorBanner(
message:
viewModel.errorMessage!,
onClose:
viewModel.clearError,
),
MessageInput(
isLoading:
viewModel.isLoading,
onSend:
viewModel.sendMessage,
),
],
),
);
}
}
class _EmptyConversation
extends StatelessWidget {
const _EmptyConversation();
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.auto_awesome,
size: 48,
color: Theme.of(context)
.colorScheme
.primary,
),
const SizedBox(height: 16),
Text(
'گفتگو را شروع کنید',
style: Theme.of(context)
.textTheme
.headlineSmall,
),
const SizedBox(height: 8),
const Text(
'سؤال خود را به زبان فارسی بنویسید.',
textAlign: TextAlign.center,
),
],
),
),
);
}
}
class _ErrorBanner
extends StatelessWidget {
const _ErrorBanner({
required this.message,
required this.onClose,
});
final String message;
final VoidCallback onClose;
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context)
.colorScheme
.errorContainer,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
child: Row(
children: [
Expanded(
child: Text(
message,
textDirection:
TextDirection.rtl,
),
),
IconButton(
onPressed: onClose,
icon: const Icon(Icons.close),
),
],
),
),
);
}
}
بخش دهم: تنظیم فایل main.dart
فایل lib/main.dart:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'repositories/chat_repository.dart';
import 'services/chat_api_service.dart';
import 'view_models/chat_view_model.dart';
import 'views/chat_page.dart';
void main() {
final baseUrl =
_resolveDevelopmentBaseUrl();
final service = ChatApiService(
baseUrl: baseUrl,
);
final repository = RemoteChatRepository(
service: service,
);
runApp(
DarvarehAiApp(
repository: repository,
),
);
}
String _resolveDevelopmentBaseUrl() {
if (kIsWeb) {
return 'http://127.0.0.1:8000';
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return 'http://10.0.2.2:8000';
case TargetPlatform.iOS:
case TargetPlatform.macOS:
case TargetPlatform.windows:
case TargetPlatform.linux:
return 'http://127.0.0.1:8000';
default:
return 'http://127.0.0.1:8000';
}
}
class DarvarehAiApp
extends StatelessWidget {
const DarvarehAiApp({
required this.repository,
super.key,
});
final ChatRepository repository;
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ChatViewModel(
repository: repository,
),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Darvareh AI App',
themeMode: ThemeMode.system,
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(
seedColor:
const Color(0xFF6750A4),
brightness: Brightness.light,
),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme:
ColorScheme.fromSeed(
seedColor:
const Color(0xFF8B7CF6),
brightness: Brightness.dark,
),
useMaterial3: true,
),
home: const Directionality(
textDirection: TextDirection.rtl,
child: ChatPage(),
),
),
);
}
}
اجرای اپلیکیشن
ابتدا Backend را اجرا کنید:
uvicorn main:app --reload --host 0.0.0.0 --port 8000
سپس در پروژه Flutter:
flutter pub get
flutter run
اجرای نسخه وب:
flutter run -d chrome --web-port 5000
اجرای روی Emulator مشخص:
flutter devices
flutter run -d DEVICE_ID
پس از اجرا یک پیام فارسی بفرستید. اپلیکیشن باید پیام را نمایش دهد، وارد حالت Loading شود و پاسخ Backend را در سمت مقابل نشان دهد.
خطاهای رایج اتصال Flutter به Backend
خطای SocketException
دلایل احتمالی:
- Backend اجرا نشده است.
- Base URL اشتباه است.
- پورت اشتباه است.
- Android Emulator به
localhostمتصل شده است. - Firewall مانع اتصال شده است.
- دستگاه واقعی و کامپیوتر در یک شبکه نیستند.
خطای Cleartext Traffic
در Android Development، تنظیم زیر را بررسی کنید:
android:usesCleartextTraffic="true"
برای Production از HTTPS استفاده کنید.
خطای CORS در Flutter Web
نشانه خطا معمولاً در Console مرورگر دیده میشود. Origin واقعی Flutter Web را به تنظیمات CORS Backend اضافه کنید.
از این تنظیم عمومی در Production استفاده نکنید:
allow_origins=["*"]
Originهای واقعی وبسایت خود را بهصورت دقیق تعریف کنید.
خطای 422
Backend انتظار این ساختار را دارد:
{
"message": "متن پیام"
}
نام فیلد، نوع داده و خالینبودن پیام را بررسی کنید.
خطای 401 یا 500 Backend
کلید درواره باید در فایل .env سمت Backend تنظیم شود:
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
کلید را داخل Flutter قرار ندهید.
خطای Model Not Found
مقدار زیر را با شناسه واقعی جایگزین کنید:
DARVAREH_MODEL=YOUR_MODEL_ID
Model ID را از صفحه مدلهای درواره دریافت کنید.
نمایش نشدن پاسخ فارسی
بررسی کنید:
- پاسخ با UTF-8 پردازش شود.
- از Font سازگار با فارسی استفاده شود.
TextDirection.rtlدر بخش مناسب فعال باشد.- JSON بهدرستی Decode شود.
نگهداری تاریخچه مکالمه
پروژه فعلی پیامها را فقط در حافظه اپلیکیشن نگه میدارد. پس از بستهشدن برنامه، تاریخچه از بین میرود.
برای ذخیرهسازی محلی میتوانید از ابزارهایی مانند اینها استفاده کنید:
- SQLite
- Drift
- Isar
- Hive
- SharedPreferences برای تنظیمات ساده
اما برای همگامسازی چند دستگاه بهتر است تاریخچه روی Backend ذخیره شود.
مدل پیشنهادی Backend:
Conversation
- id
- user_id
- title
- created_at
- updated_at
Message
- id
- conversation_id
- role
- content
- created_at
در این حالت اپلیکیشن همراه پیام، conversation_id را نیز میفرستد.
ارسال تاریخچه به مدل
برای مکالمه چندمرحلهای، Backend باید پیامهای مرتبط را برای مدل ارسال کند:
messages = [
{
"role": "system",
"content": "تو یک دستیار فارسی هستی.",
},
{
"role": "user",
"content": "Flutter چیست؟",
},
{
"role": "assistant",
"content": "Flutter یک فریمورک چندسکویی است.",
},
{
"role": "user",
"content": "چه زبانی استفاده میکند؟",
},
]
تاریخچه نباید بدون محدودیت رشد کند. برای مدیریت Context:
- فقط پیامهای اخیر را ارسال کنید.
- پیامهای قدیمی را خلاصه کنید.
- پیامهای نامرتبط را حذف کنید.
- برای هر گفتگو سقف مصرف تعریف کنید.
- تعداد توکن را ثبت کنید.
اضافهکردن Streaming
در نسخه فعلی پاسخ پس از کاملشدن نمایش داده میشود. برای تجربه مشابه چت زنده میتوانید Streaming را اضافه کنید.
روشهای متداول:
- Server-Sent Events
- WebSocket
- HTTP Stream
در Flutter میتوانید Chunkهای دریافتی را به یک پیام در حال ساخت اضافه و notifyListeners را فراخوانی کنید.
ساختار State پیشنهادی:
class ChatMessage {
const ChatMessage({
required this.id,
required this.text,
required this.role,
this.isStreaming = false,
});
final String id;
final String text;
final MessageRole role;
final bool isStreaming;
}
در زمان Streaming، متن همان پیام دستیار بهروزرسانی میشود. پس از دریافت پایان Stream، isStreaming به false تغییر میکند.
پیادهسازی Streaming باید این موارد را مدیریت کند:
- قطع اتصال
- دریافت Chunk ناقص
- لغو درخواست
- ارسال دوباره
- ذخیره پیام نهایی
- جلوگیری از اجرای همزمان چند Stream
- نمایش پاسخ ناقص در صورت قطع شبکه
ساخت خروجی JSON در اپلیکیشن
اگر قابلیت شما نیازمند داده ساختاریافته است، پاسخ آزاد متنی کافی نیست.
فرض کنید اپلیکیشن برنامه روزانه میسازد:
{
"title": "برنامه امروز",
"tasks": [
{
"title": "مطالعه Flutter",
"duration_minutes": 45
}
]
}
در این حالت باید:
- Schema خروجی را در Backend تعریف کنید.
- از مدل سازگار با Structured Output استفاده کنید.
- پاسخ را در Backend اعتبارسنجی کنید.
- فقط JSON معتبر را برای Flutter بفرستید.
- در Dart مدل داده مشخص بسازید.
- خطای Parse را مدیریت کنید.
به پاسخ مدل مستقیماً اعتماد نکنید. اعتبارسنجی باید پیش از ذخیره یا اجرای عملیات انجام شود.
تست Service در Flutter
با MockClient میتوان درخواست شبکه را بدون Backend واقعی تست کرد.
نمونه مفهومی:
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test(
'returns answer when server responds 200',
() async {
final client = MockClient(
(request) async {
return http.Response(
'{"answer":"پاسخ آزمایشی"}',
200,
headers: {
'content-type':
'application/json',
},
);
},
);
final service = ChatApiService(
baseUrl: 'https://example.com',
client: client,
);
final result =
await service.sendMessage('سلام');
expect(result, 'پاسخ آزمایشی');
},
);
}
سناریوهای مهم تست:
- پاسخ موفق
- پاسخ خالی
- JSON نامعتبر
- وضعیت 422
- وضعیت 429
- وضعیت 500
- Timeout
- قطع شبکه
- پیام خطای Backend
تست ViewModel
نسخه Fake Repository:
class FakeChatRepository
implements ChatRepository {
FakeChatRepository({
this.answer = 'پاسخ آزمایشی',
this.error,
});
final String answer;
final Exception? error;
@override
Future<String> sendMessage(
String message,
) async {
if (error != null) {
throw error!;
}
return answer;
}
}
نمونه تست:
import 'package:flutter_test/flutter_test.dart';
void main() {
test(
'adds user and assistant messages',
() async {
final repository =
FakeChatRepository(
answer: 'سلام! چطور کمک کنم؟',
);
final viewModel = ChatViewModel(
repository: repository,
);
await viewModel.sendMessage('سلام');
expect(viewModel.messages.length, 2);
expect(
viewModel.messages.first.isUser,
isTrue,
);
expect(
viewModel.messages.last.isUser,
isFalse,
);
expect(viewModel.isLoading, isFalse);
expect(viewModel.errorMessage, isNull);
},
);
}
استفاده از هوش مصنوعی برای دیباگ Flutter
برای دریافت پاسخ دقیق، اطلاعات کامل ارائه کنید.
پرامپت پیشنهادی:
در یک پروژه Flutter با خطای زیر مواجه شدهام.
محیط:
- Flutter version:
- Dart version:
- Platform:
- Device:
- State management:
- Packageهای مرتبط:
دستور اجرا:
flutter run
خطای کامل:
[STACK_TRACE]
کد Service:
[CODE]
کد ViewModel:
[CODE]
رفتار مورد انتظار:
پس از ارسال پیام، پاسخ Backend نمایش داده شود.
رفتار فعلی:
Loading متوقف نمیشود.
لطفاً:
1. علتهای محتمل را به ترتیب احتمال بنویس.
2. مشخص کن کدام خطا Root Cause است.
3. برای هر فرضیه روش آزمایش ارائه کن.
4. حداقل Patch لازم را پیشنهاد بده.
5. تستی بنویس که مشکل را بازتولید کند.
پرامپتهای آماده برای توسعه Flutter
ساخت Widget
یک Widget قابل استفاده مجدد برای نمایش پیام چت در Flutter بساز.
شرایط:
- از Material 3 استفاده کند.
- فارسی و RTL را پشتیبانی کند.
- متن قابل انتخاب باشد.
- پیام User و Assistant ظاهر متفاوت داشته باشند.
- Widget بدون وابستگی به ViewModel باشد.
- Widget Test نیز اضافه کن.
ساخت ViewModel
برای صفحه چت یک ViewModel با ChangeNotifier طراحی کن.
State:
- messages
- isLoading
- errorMessage
رفتار:
- پیام خالی ارسال نشود.
- ارسال همزمان دو پیام مجاز نباشد.
- پیام کاربر بلافاصله نمایش داده شود.
- پاسخ Service به فهرست اضافه شود.
- خطا در State قرار بگیرد.
ViewModel نباید مستقیماً از http استفاده کند.
تحلیل معماری
ساختار این پروژه Flutter را بررسی کن.
مشخص کن:
- UI Logic کجا قرار دارد؟
- Data Source کدام است؟
- آیا Widgetها Business Logic دارند؟
- Source of Truth چیست؟
- وابستگیها چگونه Inject میشوند؟
- کدام بخشها تستپذیر نیستند؟
فعلاً کد را بازنویسی نکن.
مشکلات را براساس اثر و هزینه اصلاح اولویتبندی کن.
تولید تست
برای این ChatViewModel یک Test Matrix بنویس.
ستونها:
- سناریو
- ورودی
- Fake یا Mock لازم
- State میانی
- State نهایی
- خطای مورد انتظار
پس از Test Matrix، تستهای flutter_test را تولید کن.
کنترل هزینه API در اپلیکیشن Flutter
در یک اپلیکیشن عمومی، محدودیتهای UI کافی نیستند. کاربر میتواند Client را تغییر دهد یا مستقیماً به Endpoint Backend درخواست بفرستد. تمام کنترلهای اصلی باید روی سرور اعمال شوند.
راهکارهای مدیریت هزینه:
- احراز هویت کاربران
- محدودیت درخواست برای هر کاربر
- سقف مصرف روزانه
- محدودیت طول ورودی
- محدودیت طول خروجی
- انتخاب مدل متناسب با وظیفه
- ثبت توکن و هزینه هر درخواست
- Cache کردن پاسخهای تکراری
- حذف Context غیرضروری
- خلاصهسازی تاریخچه
- جلوگیری از درخواست تکراری
- بودجه کلی ماهانه
- غیرفعالکردن قابلیت پس از عبور از سقف
قیمت مدلها به نوع مدل و میزان مصرف بستگی دارد. برای اطلاعات بهروز به صفحه مدلهای درواره مراجعه کنید.
انتخاب مدل مناسب برای اپلیکیشن Flutter
| کاربرد | معیار انتخاب مدل |
|---|---|
| چت فارسی | کیفیت زبان فارسی، سرعت و هزینه |
| خلاصهسازی | Context Window و هزینه ورودی |
| تولید متن کوتاه | سرعت و قیمت |
| برنامهنویسی | توانایی Coding |
| استخراج داده | ثبات خروجی ساختاریافته |
| تحلیل تصویر | پشتیبانی از Vision |
| دستیار چندمرحلهای | Reasoning و Tool Calling |
| اپ پرترافیک | Latency، هزینه و پایداری |
قبل از انتخاب نهایی:
- حداقل ۲۰ تا ۵۰ ورودی واقعی آماده کنید.
- چند مدل را روی داده یکسان آزمایش کنید.
- کیفیت، زمان و هزینه را ثبت کنید.
- خروجیهای اشتباه را دستهبندی کنید.
- مدل را براساس نیاز واقعی انتخاب کنید.
کاربردهای عملی هوش مصنوعی در Flutter
اپلیکیشن دستیار فارسی
قابلیتها:
- گفتوگوی چندمرحلهای
- ذخیره تاریخچه
- انتخاب موضوع
- پاسخ Streaming
- کپی و اشتراک پاسخ
خلاصهساز متن
ورودی:
- مقاله
- یادداشت
- گزارش
- متن جلسه
خروجی:
- خلاصه کوتاه
- نکات اصلی
- اقدامهای بعدی
دستیار تولید محتوا
کاربر میتواند موضوع، لحن و طول را انتخاب کند و خروجیهایی مانند عنوان، کپشن، توضیحات محصول یا ایمیل دریافت کند.
اپلیکیشن یادداشت هوشمند
امکانات:
- اصلاح نگارش
- دستهبندی یادداشت
- استخراج کارها
- ساخت عنوان
- خلاصهسازی
- تبدیل متن به چکلیست
تحلیل بازخورد مشتری
خروجی ساختاریافته:
{
"sentiment": "negative",
"topic": "delivery",
"priority": "high",
"summary": "سفارش با تأخیر تحویل شده است."
}
دستیار فروش
میتواند اطلاعات محصول را به توضیح فروش، مزایا، سؤال متداول و پاسخ پیشنهادی تبدیل کند.
چکلیست Production
Flutter
- API Key داخل برنامه نباشد.
- Base URL از تنظیمات محیط دریافت شود.
- Backend از HTTPS استفاده کند.
- Loading و Error کامل باشند.
- Timeout مدیریت شود.
- ارسال تکراری کنترل شود.
- RTL آزمایش شود.
- Dark Mode بررسی شود.
- Widgetها روی صفحههای کوچک تست شوند.
- متن طولانی رابط را خراب نکند.
- وضعیت قطع اینترنت مشخص باشد.
- Logها اطلاعات حساس نداشته باشند.
Backend
- کلید فقط در متغیر محیطی باشد.
- کاربران احراز هویت شوند.
- Rate Limit اعمال شود.
- ورودی اعتبارسنجی شود.
- CORS فقط Originهای مجاز را بپذیرد.
- Timeout و Retry محدود باشند.
- هزینه هر کاربر ثبت شود.
- پاسخ مدل اعتبارسنجی شود.
- خطای داخلی مستقیماً افشا نشود.
- مدل از تنظیمات قابل تغییر باشد.
هوش مصنوعی
- Prompt نسخهبندی شود.
- مدل روی داده واقعی ارزیابی شود.
- طول Context محدود شود.
- خروجی ساختاریافته Validate شود.
- پاسخهای اشتباه ثبت و تحلیل شوند.
- مدل اقتصادی برای وظایف ساده استفاده شود.
- Fallback فقط در صورت نیاز طراحی شود.
اشتباهات رایج
قراردادن کلید در فایل .env اپلیکیشن
Packageهای .env در Flutter مقدار را از فایل داخل برنامه میخوانند. این روش کلید را به Secret غیرقابل استخراج تبدیل نمیکند.
فراخوانی مستقیم API مدل از موبایل
این کار کنترل مصرف، تعویض کلید و جلوگیری از سوءاستفاده را دشوار میکند. از Backend استفاده کنید.
نگهداری تمام منطق در Widget
ارتباط شبکه و Business Logic را از View جدا کنید. Widget باید بیشتر مسئول نمایش State و ارسال Event باشد.
استفاده از یک Boolean برای تمام وضعیتها
در پروژه بزرگتر، State را دقیقتر مدل کنید:
enum ChatStatus {
idle,
sending,
streaming,
success,
failure,
}
ارسال تاریخچه نامحدود
تاریخچه طولانی باعث افزایش زمان و هزینه میشود. پیامهای قدیمی را خلاصه یا حذف کنید.
Retry در اپ و Backend
اجرای Retry در چند لایه میتواند یک درخواست را چند بار تکرار کند. یک سیاست مشخص برای Retry داشته باشید.
نمایش مستقیم خطای داخلی
متن خام Exception یا پاسخ سرویس بالادستی را مستقیماً به کاربر نمایش ندهید. پیام قابل فهم بسازید و جزئیات فنی را در Log کنترلشده ثبت کنید.
پرسشهای متداول
هوش مصنوعی در Flutter چیست؟
این عبارت میتواند به استفاده از دستیار هوش مصنوعی برای کدنویسی Flutter یا افزودن قابلیتهای هوشمند به اپلیکیشن از طریق API اشاره کند.
آیا میتوان با Flutter اپلیکیشن هوش مصنوعی ساخت؟
بله. Flutter رابط چندسکویی را میسازد و Backend از طریق API به مدل هوش مصنوعی متصل میشود.
آیا Flutter برای ساخت چتبات مناسب است؟
بله. Flutter برای ساخت رابط چت روی Android، iOS، وب و Desktop مناسب است. برای پاسخگویی مدل به Backend و API نیاز دارید.
آیا باید کلید API درواره را در Flutter قرار دهیم؟
خیر. کلید باید فقط روی Backend نگهداری شود. اپلیکیشن Flutter به Backend اختصاصی شما متصل میشود.
Base URL درواره چیست؟
آدرس پایه API درواره:
https://api.darvareh.ir/v1
این آدرس همراه کلید در Backend تنظیم میشود.
Model ID را از کجا پیدا کنیم؟
فهرست مدلها، شناسه آنها و اطلاعات قیمت در صفحه مدلهای درواره قرار دارد.
چرا Android Emulator به localhost متصل نمیشود؟
در Android Emulator، localhost به خود Emulator اشاره میکند. برای اتصال به Backend کامپیوتر معمولاً از این آدرس استفاده کنید:
http://10.0.2.2:8000
برای Flutter Web چرا خطای CORS میگیرم؟
مرورگر فقط به Originهای مجاز اجازه دسترسی میدهد. Origin اجرای Flutter Web باید در تنظیمات CORS Backend ثبت شده باشد.
آیا میتوان پاسخ مدل را Streaming کرد؟
بله. میتوانید از SSE، WebSocket یا HTTP Stream استفاده کنید و متن پیام دستیار را بهتدریج در ViewModel بهروزرسانی کنید.
بهترین State Management برای اپلیکیشن هوش مصنوعی چیست؟
انتخاب به اندازه پروژه و تجربه تیم بستگی دارد. ChangeNotifier برای آموزش و پروژه ساده مناسب است. Provider، Riverpod و BLoC نیز برای پروژههای بزرگتر قابل استفادهاند. مهمتر از نام ابزار، داشتن State مشخص و جداسازی منطق از UI است.
آیا میتوان یک کدبیس را برای Android و iOS استفاده کرد؟
بله، یکی از مزایای Flutter اشتراک بخش بزرگی از کد میان پلتفرمهاست. بااینحال تنظیمات شبکه، Permissionها و برخی قابلیتهای Native باید برای هر پلتفرم جداگانه بررسی شوند.
چگونه هزینه اپلیکیشن را کنترل کنیم؟
محدودیت مصرف را در Backend اعمال کنید، طول ورودی و خروجی را محدود کنید، مدل مناسب انتخاب کنید، تاریخچه را مدیریت و مصرف هر کاربر را ثبت کنید.
آیا خروجی مدل را میتوان مستقیماً در دیتابیس ذخیره کرد؟
ابتدا خروجی را Parse و اعتبارسنجی کنید. برای دادههای ساختاریافته از Schema مشخص استفاده کنید و قواعد کسبوکار را نیز سمت Backend بررسی کنید.
جمعبندی
Flutter امکان ساخت رابط یکپارچه برای Android، iOS، وب و Desktop را فراهم میکند و API هوش مصنوعی قابلیتهای هوشمند را به این رابط اضافه میکند.
در این مقاله یاد گرفتیم چگونه:
- از هوش مصنوعی برای توسعه Flutter استفاده کنیم.
- ساختار پروژه را به لایههای View، ViewModel، Repository و Service تقسیم کنیم.
- Backend مبتنی بر FastAPI بسازیم.
- Backend را به API درواره متصل کنیم.
- با Package
httpاز Flutter درخواست ارسال کنیم. - رابط چت فارسی و RTL بسازیم.
- Loading، Error و Timeout را مدیریت کنیم.
- تفاوت آدرس Backend در Android، iOS و Web را در نظر بگیریم.
- از قراردادن API Key در اپلیکیشن جلوگیری کنیم.
- مسیر توسعه تاریخچه، Streaming و خروجی ساختاریافته را مشخص کنیم.
برای ساخت اولین اپلیکیشن هوش مصنوعی خود، در درواره ثبتنام کنید، کلید API بسازید و یک مدل متناسب با کیفیت، سرعت و بودجه پروژه از صفحه مدلها و قیمتها انتخاب کنید.
مقالات مرتبط
- ساخت اپلیکیشن با هوش مصنوعی؛ آموزش کامل Web App و Mobile App
- برنامهنویسی با ChatGPT؛ آموزش کدنویسی، دیباگ و ساخت پروژه
- بهترین ابزارهای برنامهنویسی با هوش مصنوعی؛ بخش اول
- بهترین ابزارهای برنامهنویسی با هوش مصنوعی؛ بخش دوم
- آموزش اتصال Cursor به API درواره
- آموزش اتصال Claude Code به درواره
- دیباگ کد و خطا با هوش مصنوعی
- ساخت Unit Test با هوش مصنوعی
- بررسی کد و Pull Request با هوش مصنوعی
- API هوش مصنوعی چیست؟ راهنمای دریافت و استفاده
- دریافت API هوش مصنوعی و ساخت API Key
- اتصال API هوش مصنوعی به اپلیکیشن
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.