بکاند اپلیکیشن فلاتر و ریاکتنیتیو؛ از اتصال تا ورود کاربر
کد واقعی اتصال اپ فلاتر و ریاکتنیتیو به بکاند آماده: درخواست اول، ورود کاربر، صفحهبندی cursor، آپلود فایل و رفتار درست روی شبکهی ناپایدار.
در این مطلب
بکاند، همان جایی است که پروژههای موبایل معمولاً گیر میکنند. اپ فلاتر یا ریاکتنیتیو را در چند روز تا حد قابل دمو جلو میبرید، و بعد دو ماه منتظر میمانید تا کسی API را بنویسد — یا خودتان مجبور میشوید بنویسید. این راهنما مسیر کوتاه را نشان میدهد: از اولین درخواست و ورود کاربر تا صفحهبندی، آپلود فایل، و چیزی که در آموزشهای خارجی نیست — رفتار درست روی شبکهی ناپایدار.
اپ موبایل دقیقاً به چه چیزی از بکاند نیاز دارد؟
تقریباً هر اپی، فارغ از موضوعش، همین فهرست را میخواهد:
- دیتابیس که دادهی کاربران بین دستگاهها مشترک و ماندگار بماند
- API برای خواندن و نوشتن، همراه با فیلتر، مرتبسازی و صفحهبندی
- حساب کاربری: ثبتنام، ورود، و اینکه هر کاربر فقط دادهی خودش را ببیند
- آپلود فایل برای عکس پروفایل و تصویر محصول
- اطلاعرسانی و اتصال به سرویسهای دیگر بعد از رویدادها
هیچکدام از اینها اپ شما را متمایز نمیکند، ولی همهشان لازماند. به همین دلیل بکاند آماده برای توسعهدهندهی موبایل بیشترین صرفه را دارد: چیزی که حذف میشود دقیقاً همان بخشی است که تکراری است.
اولین درخواست از فلاتر
هر پروژه یک آدرس پایهی اختصاصی دارد و از هر HTTP کلاینتی قابل مصرف است:
import 'dart:convert';
import 'package:http/http.dart' as http;
const base = 'https://api.fikaro.ir/my-shop/v1';
Future<List<dynamic>> fetchProducts() async {
final res = await http.get(
Uri.parse('$base/product?filter[status]=active&sort=-created_at&limit=20'),
headers: {'Authorization': 'Bearer $apiKey'},
).timeout(const Duration(seconds: 15));
if (res.statusCode != 200) {
// خطاها قالب استاندارد Problem Details دارند؛ به code تکیه کنید نه به متن
final err = jsonDecode(utf8.decode(res.bodyBytes));
throw ApiException(err['code'] as String);
}
return jsonDecode(utf8.decode(res.bodyBytes))['data'] as List<dynamic>;
}
در فلاتر همیشه utf8.decode(res.bodyBytes) را به res.body ترجیح دهید. برای
پاسخهای فارسی، خواندن مستقیم body در بعضی پیکربندیها متن را خراب
برمیگرداند.
اولین درخواست از ریاکتنیتیو
const BASE = "https://api.fikaro.ir/my-shop/v1";
export async function fetchProducts({ cursor } = {}) {
const url = new URL(`${BASE}/product`);
url.searchParams.set("filter[status]", "active");
url.searchParams.set("limit", "20");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!res.ok) throw new ApiError((await res.json()).code);
const { data, pagination } = await res.json();
return { items: data, nextCursor: pagination.nextCursor };
}
عملگرهای فیلتر (eq، gte، like، in و بقیه) و قالب کامل پاسخ در مستندات کار با API آمده است.
ثبتنام و ورود کاربران اپ
لازم نیست لایهی احراز هویت را بنویسید؛ هر پروژه اندپوینتهای آماده دارد:
Future<String> login(String phone, String password) async {
final res = await http.post(
Uri.parse('$base/auth/login'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'phone': phone, 'password': password}),
);
final token = jsonDecode(utf8.decode(res.bodyBytes))['token'] as String;
await secureStorage.write(key: 'token', value: token); // Keychain / Keystore
return token;
}
از این به بعد بهجای کلید API، توکن کاربر را میفرستید و هر کاربر فقط رکوردهای خودش را میبیند. دو نکتهی مهم:
- کلید Production را هرگز داخل باندل اپ نگذارید. فایل اپ قابل باز کردن است؛ هر چیزی داخلش عمومی است. برای دسترسی کاربران از توکن استفاده کنید.
- توکن را در حافظهی امن سیستمعامل نگه دارید — Keychain در iOS، Keystore در اندروید — نه در فایل ساده.
جزئیات کامل این لایه و تنظیم دسترسی «فقط رکوردهای خودم» در احراز هویت با JWT بدون کدنویسی آمده است.
لیست بینهایت با صفحهبندی cursor
برای لیستهای موبایل، صفحهبندی مبتنی بر cursor دقیقاً چیزی است که میخواهید: وقتی کاربر در حال اسکرول است و همزمان رکورد جدیدی درج میشود، هیچ آیتمی جا نمیافتد و هیچ آیتمی دو بار نمیآید — اتفاقی که با offset رایج است.
GET /v1/order?limit=20
→ { "data": [...], "pagination": { "limit": 20, "nextCursor": "eyJ..." } }
GET /v1/order?limit=20&cursor=eyJ...
وقتی nextCursor برابر null شد، به انتهای لیست رسیدهاید. برای رکوردهای مرتبط هم لازم نیست درخواست دوم بزنید:
GET /v1/order?include=order_item
آپلود فایل
فیلد از نوع فایل در مدل داده تعریف میشود و آپلود از خود اپ انجام میشود — عکس پروفایل، تصویر محصول، پیوست. ذخیرهسازی فایل بهصورت افزونه فعال میشود، پس اگر اپتان تصویر دارد، از همان ابتدا در برآورد هزینهتان بگذاریدش.
شبکهی ناپایدار: بخشی که در آموزشهای خارجی نیست
کاربر ایرانی روی موبایل، قطعی و کندی شبکه را حالت عادی تجربه میکند نه استثنا. اپی که این را در نظر نگرفته باشد، در نظر کاربر «خراب» است — حتی اگر بکاندش بیعیب باشد. پنج کاری که واقعاً تفاوت میسازد:
- تایماوت صریح بگذارید. بدون آن، درخواست میتواند تا دقیقهها معلق بماند و اپ قفل به نظر برسد.
- تلاش مجدد فقط برای درخواستهای امن.
GETرا با فاصلهی فزاینده تکرار کنید؛POSTرا نه — مگر اینکه بررسی تکراری بودن داشته باشید، وگرنه سفارش دوباره ثبت میشود. - آخرین پاسخ موفق را کش کنید. نمایش دادهی کمی قدیمی، بینهایت بهتر از صفحهی خالی است.
- ۴۲۹ را جدی بگیرید. عبور از سقف نرخ یعنی باید عقب بنشینید، نه اینکه سریعتر دوباره تلاش کنید.
- خطا را به زبان کاربر بگویید. «اتصال برقرار نشد، دوباره تلاش کنید» بههمراه دکمهی تلاش مجدد؛ نه کد خطای خام.
سمت سرور هم همین منطق دنبال میشود: ارقام فارسی ورودی خودکار نرمال میشوند، شمارهی موبایل در قالب E.164 ذخیره میشود، و تاریخها ISO 8601 و UTC هستند تا تبدیل به تقویم شمسی سمت نمایش انجام شود.
بهجای نوشتن مدلها، SDK تایپشده بگیرید
نوشتن دستی کلاسهای مدل و fromJson برای هر موجودیت، کار تکراریای است که با هر تغییر مدل داده دوباره تکرار میشود. هر پروژه کلاینت تایپشده از روی مدل دادهی همان لحظه تولید میکند — Flutter (Dart)، Kotlin و Swift — شامل مدلها، فهرست با فیلتر و صفحهبندی، و ورود و ثبتنام کاربران.
کالکشن Postman و Bruno هم آمادهاند، که برای هماهنگی با بقیهی تیم یا تحویل به توسعهدهندهی دیگر کافی است. (خروجیها و SDK)
چکلیست پیش از انتشار اپ
- کلید Production جایی داخل باندل اپ نمانده باشد
- توکن کاربر در حافظهی امن سیستمعامل ذخیره شود
- دسترسی «فقط رکوردهای خودم» روی موجودیتهای حساس فعال باشد
- هر درخواست تایماوت و مسیر خطای قابلفهم داشته باشد
- رفتار اپ در حالت آفلاین و در پاسخ ۴۲۹ آزمایش شده باشد
- برای رویدادهایی مثل ثبت سفارش، وبهوک بهجای پرسیدن دورهای استفاده شود
برای شروع، ثبتنام رایگان و بعد شروع سریع. اگر هنوز بین گزینهها مرددید، بکاند آماده برای اپلیکیشن معیارهای انتخاب را باز میکند.
سوالات متداول
- برای اپ فلاتر حتماً به بکاند نیاز دارم؟
- اگر اپتان فقط داده محلی دارد (مثل ماشینحساب یا یادداشت آفلاین) خیر. اما بهمحض اینکه داده باید بین دستگاهها مشترک باشد، حساب کاربری وجود داشته باشد، یا محتوایی از راه دور بهروز شود، به بکاند نیاز دارید.
- میشود یک بکاند را همزمان برای اپ و وبسایت استفاده کرد؟
- بله، و روش درست هم همین است. همان API از فلاتر، ریاکتنیتیو، وب و حتی اسکریپتهای داخلی مصرف میشود. کافی است برای هر مصرفکننده کلید مناسب و سطح دسترسی درست تعریف کنید.
- کلید API را کجای اپ موبایل بگذارم؟
- کلید Production را اصلاً داخل اپ نگذارید؛ باندل اپ قابل استخراج است. برای دسترسی کاربران از توکن JWT استفاده کنید که با ورود کاربر گرفته میشود و فقط به داده همان کاربر دسترسی دارد.
- برای اتصال به API باید مدلهای Dart را دستی بنویسم؟
- لازم نیست. کلاینت تایپشده Flutter از روی مدل داده پروژه تولید میشود و مدلها، فهرست با فیلتر و صفحهبندی، و ورود کاربران را دارد. بعد از هر تغییر مدل داده، نسخه تازه را میگیرید تا تایپها هماهنگ بمانند.
مطالب مرتبط
- ۴ دقیقه مطالعه
احراز هویت کاربران با JWT بدون کدنویسی؛ ثبتنام، ورود، دسترسی
JWT چطور کار میکند، چرا نوشتن دستی احراز هویت پرریسکترین کار پروژه است، و چطور ثبتنام و ورود و دسترسی سطح رکورد را بدون نوشتن کد بسازید.
- ۵ دقیقه مطالعه
وب سرویس چیست و چه فرقی با REST API دارد؟ راهنمای طراحی و ساخت
وب سرویس، API و REST سه چیز متفاوتاند که جای هم به کار میروند. تفاوتها، مقایسه با SOAP و GraphQL، اصول طراحی و ساخت بدون کد سمت سرور.