قابلیتهای Platform
اپلیکیشنهای موبایل به قابلیتهای دستگاه نیاز دارند: دوربین، اعلان، GPS، فایل سیستم. در این فصل با ابزارهای Flutter برای دسترسی به این قابلیتها آشنا میشویم.
۱۱.۱ مقدمه
اپلیکیشنهای موبایل به قابلیتهای دستگاه نیاز دارند: دوربین، اعلان، GPS، فایل سیستم. در این فصل با ابزارهای Flutter برای دسترسی به این قابلیتها آشنا میشویم.
۱۱.۲ Permissions
قبل از استفاده از قابلیتهای حساس، باید اجازه کاربر را بگیریم.
flutter pub add permission_handler
import "package:permission_handler/permission_handler.dart";
// بررسی و درخواست
Future requestCameraPermission() async {
final status = await Permission.camera.status;
if (status.isGranted) return true;
if (status.isPermanentlyDenied) {
// باید از Settings فعال کند
await openAppSettings();
return false;
}
final result = await Permission.camera.request();
return result.isGranted;
}
// چندین permission با هم
final statuses = await [
Permission.camera,
Permission.microphone,
Permission.location,
].request();
if (statuses[Permission.camera]!.isGranted &&
statuses[Permission.microphone]!.isGranted) {
// OK
}
تنظیمات Android
<!-- android/app/src/main/AndroidManifest.xml -->
<manifest ...>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application ...>...</application>
</manifest>
تنظیمات iOS
<!-- ios/Runner/Info.plist -->
<key>NSCameraUsageDescription</key>
<string>برای گرفتن عکس از محصولات</string>
<key>NSMicrophoneUsageDescription</key>
<string>برای ضبط صدا</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>برای پیدا کردن نزدیکترین فروشگاه</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>برای انتخاب عکس از گالری</string>
۱۱.۳ دوربین
flutter pub add camera
import "package:camera/camera.dart";
class CameraPage extends StatefulWidget {
@override
State createState() => _CameraPageState();
}
class _CameraPageState extends State {
CameraController? _controller;
List? _cameras;
@override
void initState() {
super.initState();
_initCamera();
}
Future _initCamera() async {
_cameras = await availableCameras();
if (_cameras!.isEmpty) return;
_controller = CameraController(
_cameras!.first, // دوربین پشت
ResolutionPreset.high,
enableAudio: false,
);
await _controller!.initialize();
if (mounted) setState(() {});
}
Future _takePicture() async {
if (_controller == null || !_controller!.value.isInitialized) return;
try {
final image = await _controller!.takePicture();
// image.path → ذخیره یا پردازش
print("ذخیره شد: ${image.path}");
} catch (e) {
print("خطا: $e");
}
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_controller == null || !_controller!.value.isInitialized) {
return const Center(child: CircularProgressIndicator());
}
return Stack(
children: [
CameraPreview(_controller!),
Positioned(
bottom: 32,
left: 0,
right: 0,
child: Center(
child: FloatingActionButton(
onPressed: _takePicture,
child: const Icon(Icons.camera),
),
),
),
],
);
}
}
نکته: برای موارد ساده (انتخاب از gallery یا گرفتن عکس)، image_picker از فصل ۹ کافی است. camera برای custom UI استفاده میشود.
۱۱.۴ Push Notification
Local Notification
flutter pub add flutter_local_notifications
import "package:flutter_local_notifications/flutter_local_notifications.dart";
class NotificationService {
final _plugin = FlutterLocalNotificationsPlugin();
Future init() async {
const android = AndroidInitializationSettings("@mipmap/ic_launcher");
const ios = DarwinInitializationSettings();
const settings = InitializationSettings(android: android, iOS: ios);
await _plugin.initialize(
settings,
onDidReceiveNotificationResponse: (response) {
// کلیک روی notification
print("Tapped: ${response.payload}");
},
);
}
Future showNotification({
required String title,
required String body,
String? payload,
}) async {
const android = AndroidNotificationDetails(
"main_channel",
"اعلانهای اصلی",
importance: Importance.max,
priority: Priority.high,
);
const ios = DarwinNotificationDetails();
await _plugin.show(
0,
title,
body,
const NotificationDetails(android: android, iOS: ios),
payload: payload,
);
}
Future scheduleNotification({
required DateTime scheduledTime,
required String title,
required String body,
}) async {
await _plugin.zonedSchedule(
0,
title,
body,
tz.TZDateTime.from(scheduledTime, tz.local),
const NotificationDetails(...),
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
);
}
}
Firebase Cloud Messaging (Push)
flutter pub add firebase_core firebase_messaging
import "package:firebase_messaging/firebase_messaging.dart";
class FCMService {
final _messaging = FirebaseMessaging.instance;
Future init() async {
// Permission
await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
// Get token
final token = await _messaging.getToken();
print("FCM token: $token");
// ارسال به backend برای ذخیره
// Foreground messages
FirebaseMessaging.onMessage.listen((message) {
print("Foreground: ${message.notification?.title}");
// نمایش با local notification
});
// Background tap
FirebaseMessaging.onMessageOpenedApp.listen((message) {
print("Tapped: ${message.data}");
// navigate to specific screen
});
// Background handler
FirebaseMessaging.onBackgroundMessage(_backgroundHandler);
}
static Future _backgroundHandler(RemoteMessage message) async {
print("Background: ${message.messageId}");
}
}
۱۱.۵ Location و GPS
flutter pub add geolocator
import "package:geolocator/geolocator.dart";
class LocationService {
Future getCurrentLocation() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return null;
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) return null;
}
if (permission == LocationPermission.deniedForever) {
return null;
}
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
}
Stream watchLocation() {
return Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10, // متر
),
);
}
// فاصله بین دو نقطه (متر)
double distance(double lat1, double lng1, double lat2, double lng2) {
return Geolocator.distanceBetween(lat1, lng1, lat2, lng2);
}
}
۱۱.۶ Map
flutter pub add google_maps_flutter
# یا برای متنباز:
flutter pub add flutter_map latlong2
// با Google Maps
import "package:google_maps_flutter/google_maps_flutter.dart";
class MapPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GoogleMap(
initialCameraPosition: const CameraPosition(
target: LatLng(33.9850, 51.4100), // کاشان
zoom: 14,
),
markers: {
const Marker(
markerId: MarkerId("1"),
position: LatLng(33.9850, 51.4100),
infoWindow: InfoWindow(title: "کاشان"),
),
},
onMapCreated: (controller) {
// کنترل برنامهریزی شده
},
);
}
}
// با OpenStreetMap (رایگان)
import "package:flutter_map/flutter_map.dart";
import "package:latlong2/latlong.dart";
FlutterMap(
options: const MapOptions(
initialCenter: LatLng(33.9850, 51.4100),
initialZoom: 14,
),
children: [
TileLayer(
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
),
const MarkerLayer(
markers: [
Marker(
point: LatLng(33.9850, 51.4100),
child: Icon(Icons.location_on, color: Colors.red),
),
],
),
],
)
۱۱.۷ احراز هویت بیومتریک
flutter pub add local_auth
import "package:local_auth/local_auth.dart";
class BiometricService {
final _auth = LocalAuthentication();
Future isAvailable() async {
return await _auth.canCheckBiometrics &&
await _auth.isDeviceSupported();
}
Future authenticate() async {
try {
return await _auth.authenticate(
localizedReason: "برای ورود به اپ خود را احراز هویت کنید",
options: const AuthenticationOptions(
biometricOnly: true,
stickyAuth: true,
),
);
} catch (e) {
return false;
}
}
}
۱۱.۸ Device Info و Connectivity
flutter pub add device_info_plus connectivity_plus
import "package:device_info_plus/device_info_plus.dart";
final deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
final android = await deviceInfo.androidInfo;
print("Model: ${android.model}");
print("Android: ${android.version.release}");
}
if (Platform.isIOS) {
final ios = await deviceInfo.iosInfo;
print("Model: ${ios.model}");
print("iOS: ${ios.systemVersion}");
}
// === Connectivity ===
import "package:connectivity_plus/connectivity_plus.dart";
final connectivity = Connectivity();
final result = await connectivity.checkConnectivity();
if (result.contains(ConnectivityResult.none)) {
print("بدون اینترنت");
}
// Listen
connectivity.onConnectivityChanged.listen((results) {
if (results.contains(ConnectivityResult.wifi)) {
print("WiFi متصل");
} else if (results.contains(ConnectivityResult.mobile)) {
print("Mobile data");
} else {
print("اتصال قطع شد");
}
});
۱۱.۹ Platform Channel — کد Native
گاهی نیاز به نوشتن کد Java/Kotlin (Android) یا Swift (iOS) داریم.
// Dart side
import "package:flutter/services.dart";
class BatteryService {
static const _channel = MethodChannel("samples.flutter.dev/battery");
Future getBatteryLevel() async {
try {
final level = await _channel.invokeMethod("getBatteryLevel");
return level ?? 0;
} on PlatformException catch (e) {
print("خطا: $e");
return 0;
}
}
}
// Android side: MainActivity.kt
class MainActivity: FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
result.success(batteryLevel)
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}
۱۱.۱۰ بهترین تجربیات
- Permissionهای حداقلی. فقط آنچه واقعاً نیاز دارید.
- توضیح دلیل permission. در iOS اجباری.
- Graceful fallback. اگر permission رد شد.
- FCM token را به backend بفرستید.
- Notification channels در Android 13+.
- Connectivity check قبل از network call.
- Plugin های پایدار. با star زیاد در pub.dev.
- Privacy Policy. برای Play Store/App Store.
۱۱.۱۱ خلاصه فصل
- permission_handler برای دسترسیها
- camera برای دوربین custom
- flutter_local_notifications و FCM
- geolocator برای GPS
- google_maps یا flutter_map
- local_auth برای biometric
- device_info و connectivity
- Platform Channel برای native code
در فصل بعد: Test — Unit، Widget، Integration.