What Are Push Notifications?
Role of Firebase Cloud Messaging (FCM)
Complete Implementation: Flutter + Laravel + FCM + MySQL
What Are Push Notifications?
Push notifications are messages sent from a server to a user’s device even when the app is not actively open. They are commonly used for:
Booking confirmations
Order status updates
Payment / recharge success messages
Reminders and alerts
Key characteristics:
Server-initiated: The server decides when to send.
Delivered via a push service: Google, Apple, etc.
Can wake the app or show messages in the notification tray.
For your use case (Flutter app + Laravel backend + MySQL bookings), push notifications are ideal for:
“Booking created successfully”
“Booking approved / vehicle ready”
“Booking cancelled”
Role of Firebase Cloud Messaging (FCM)
Firebase Cloud Messaging (FCM) is Google’s push notification service.
FCM’s responsibilities:
Generate device tokens
Each device/app installation gets a unique fcm_token.
You send this token to your backend (Laravel).
Deliver messages from your server to devices
Laravel sends a POST request to FCM with:
Target token(s)
Notification title + body
Optional data (e.g., booking_id)
Handle different app states
App in foreground: You can show a custom in-app notification.
Background / killed: System tray notification appears.
In your architecture:
Flutter app:
Gets FCM token and sends it to Laravel API.
Laravel backend (with MySQL):
Stores token in device_tokens table.
When createBooking() is called and booking is saved, it:
Looks up the user’s token(s).
Sends a request to FCM.
FCM delivers the push notification to the device.
Complete Implementation: Flutter + Laravel + FCM + MySQL
Laravel Setup
STEP 1 — Prerequisite (Service Account JSON)
Place your downloaded JSON here:
storage/app/fcm-service-account.json
✅ STEP 2 — Add in .env
FIREBASE_CREDENTIALS=storage/app/fcm-service-account.json
FIREBASE_PROJECT_ID=your-project-id
Find your project ID in Firebase console.
Migration: device_tokens table
php artisan make:migration create_device_tokens_table
database/migrations/xxxx_xx_xx_create_device_tokens_table.php:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('device_tokens', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->text('fcm_token');
$table->timestamps();
$table->foreign('user_id')
->references('id')->on('users')
->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('device_tokens');
}
};
Run:
php artisan migrate
Model: DeviceToken
php artisan make:model DeviceToken
app/Models/DeviceToken.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DeviceToken extends Model
{
protected $fillable = [
'user_id',
'fcm_token',
];
}
Controller: Save FCM Token from Flutter
Route in routes/api.php:
use App\Http\Controllers\DeviceTokenController;
Route::post('/device-token', [DeviceTokenController::class, 'store']);
Controller app/Http/Controllers/DeviceTokenController.php:
<?php
namespace App\Http\Controllers;
use App\Models\DeviceToken;
use Illuminate\Http\Request;
class DeviceTokenController extends Controller
{
public function store(Request $request)
{
$data = $request->validate([
'user_id' => 'required|integer',
'fcm_token' => 'required|string',
]);
DeviceToken::updateOrCreate(
['user_id' => $data['user_id']],
['fcm_token' => $data['fcm_token']]
);
return response()->json([
'success' => true,
'message' => 'FCM token saved',
]);
}
}
FCM Service: app/Services/FcmService.php
Create the file manually:
use Google\Client;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;
public function sendToToken(string $token, string $title, string $body, array $data = []): bool
{
try {
// STEP 1: Load Service Account Credentials
$client = new Client();
$client->setAuthConfig(storage_path(env('FIREBASE_CREDENTIALS')));
$client->addScope('https://www.googleapis.com/auth/firebase.messaging');
// STEP 2: Fetch OAuth Access Token
$accessToken = $client->fetchAccessTokenWithAssertion()['access_token'];
// STEP 3: Build FCM v1 Message Payload
$payload = [
'message' => [
'token' => $token,
'notification' => [
'title' => $title,
'body' => $body,
],
'data' => $data,
]
];
$projectId = env('FIREBASE_PROJECT_ID');
// STEP 4: Send HTTP POST Request
$response = Http::withToken($accessToken)
->post("https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send", $payload);
if ($response->failed()) {
Log::error('[FCM V1] Send Failed', [
'token' => $token,
'response' => $response->body(),
]);
return false;
}
Log::info('[FCM V1] Push Sent Successfully', [
'token' => $token,
'response' => $response->body(),
]);
return true;
} catch (\Throwable $e) {
Log::error('[FCM V1] Exception', [
'error' => $e->getMessage()
]);
return false;
}
}
Integrate FCM into your existing createBooking() method
You shared this method earlier. We’ll just add FCM sending at the end.
At top of the file where createBooking lives:
use App\Models\DeviceToken;
use App\Services\FcmService;
use Illuminate\Support\Facades\Log;
Inject FcmService into the class:
protected FcmService $fcm;
public function __construct(FcmService $fcm)
{
$this->fcm = $fcm;
}
Now your updated createBooking():
public function createBooking($request, $getphone, $getvehicle)
{
Log::info("inside create booking");
Log::info($request);
$endDate = new \DateTime($request->end_date);
$endDate->modify('-1 day');
$shop_id = $getvehicle->shop_id;
$getshop = Shop::where('id', $shop_id)->first();
$destination = $getshop->partner_name;
$vehicleImage = $getvehicle->vechicle_image;
$img = json_decode($vehicleImage, true);
$payment_type = $getvehicle->payment_type;
$vehicleprice = $getvehicle->price;
$vendor = User::where('id', $getvehicle->vender_ID)->first();
$shop = Shop::where('id', $getvehicle->shop_id)->first();
$vehicle_id = $request->has('vehicle_id') ? $request->vehicle_id : $request->vechicle_id;
$booking = Booking::create([
'user_name' => $getphone->name,
'destination' => $destination,
'vechicle_type' => $getvehicle->vechicle,
'brand' => $getvehicle->brand,
'model' => $getvehicle->model,
'price' => $this->calculateBookingPrice(
$request->start_date,
$request->end_date,
$vehicleprice
),
'start_date' => $request->start_date,
'end_date' => $endDate->format('Y-m-d'),
'number' => $getphone->number,
'myvechical_id' => $getvehicle->vehical_id,
'email' => $getphone->email,
'user_id' => $getphone->id,
'vender_id' => $getvehicle->vender_ID,
'vechicle_id' => $vehicle_id,
'vechicle_image' => json_encode($img),
'shop_id' => $getvehicle->shop_id,
'status' => ($payment_type == 1) ? '0' : '3',
'state' => ($payment_type == 1) ? 'Pending' : 'Vehicle Ready',
]);
// your existing email & WhatsApp notifications...
$this->sendEmails(
$getphone,
$vendor,
$booking,
$getvehicle,
$shop,
$request,
$payment_type
);
$this->sendWhatsappMessage(
$getphone, $getvehicle, $request->total_price, $getvehicle->vechicle,
$getvehicle->brand, $getvehicle->model, $getshop->partner_name,
$request->start_date, $endDate, null, $template = 1
);
$this->removeDuplicateBookings();
// 🔔 NEW: Send FCM notification to this user
$tokens = DeviceToken::where('user_id', $getphone->id)->pluck('fcm_token')->all();
if (!empty($tokens)) {
$title = 'Booking Created Successfully';
$body = "Hi {$getphone->name}, your booking #{$booking->id} "
. "for {$getvehicle->brand} {$getvehicle->model} "
. "from {$request->start_date} to {$endDate->format('Y-m-d')} is created.";
foreach ($tokens as $token) {
$this->fcm->sendToToken($token, $title, $body, [
'type' => 'booking_created',
'booking_id' => $booking->id,
'price' => $booking->price,
'state' => $booking->state,
'vehicle' => $getvehicle->vechicle,
'brand' => $getvehicle->brand,
'model' => $getvehicle->model,
'shop_name' => $getshop->partner_name,
]);
}
} else {
Log::warning('[FCM] No device tokens for user', ['user_id' => $getphone->id]);
}
return $booking;
}
Flutter Setup
Add dependencies (pubspec.yaml)
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.google.gms:google-services:4.4.2'
}
}
STEP 2 — Add plugin inside app/build.gradle
android/app/build.gradle
dependencies:
flutter:
sdk: flutter
firebase_core: ^3.6.0
firebase_messaging: ^15.1.0
flutter_local_notifications: ^17.2.2
http: ^1.2.2
STEP 3 — Add Firebase BoM + Firebase Messaging
Inside your dependencies block:
dependencies {
implementation platform('com.google.firebase:firebase-bom:34.5.0')
implementation 'com.google.firebase:firebase-messaging'
implementation 'com.google.firebase:firebase-analytics'
// Your existing dependencies
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation 'com.google.errorprone:error_prone_annotations:2.15.0'
implementation 'com.google.crypto.tink:tink-android:1.9.0'
implementation 'androidx.multidex:multidex:2.0.1'
}
Run:
flutter pub get
Local Notification Service (for foreground notifications)
Create file: lib/local_notification_service.dart
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
class LocalNotificationService {
static final FlutterLocalNotificationsPlugin notificationsPlugin =
FlutterLocalNotificationsPlugin();
static Future<void> initialize() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings("@mipmap/ic_launcher");
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
);
await notificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
// Handle notification tap while app is in foreground/background
// You can navigate to a specific screen here using a navigator key
},
);
}
static Future<void> showNotification(RemoteMessage message) async {
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'high_importance_channel', // channel ID
'High Importance Notifications', // channel name
channelDescription: 'Used for important notifications like bookings.',
importance: Importance.max,
priority: Priority.high,
playSound: true,
);
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await notificationsPlugin.show(
DateTime.now().millisecondsSinceEpoch ~/ 1000,
message.notification?.title ?? "New Notification",
message.notification?.body ?? "",
platformChannelSpecifics,
payload: message.data.toString(),
);
}
}
Firebase API Wrapper (get token, send to Laravel, handle foreground + background)
Create file: lib/firebase_api.dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:http/http.dart' as http;
import 'local_notification_service.dart';
class FirebaseApi {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
Future<void> initNotifications(String userId) async {
// Request permission (especially required on iOS / Android 13+)
await _firebaseMessaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
// Get the token for this device
final token = await _firebaseMessaging.getToken();
print('FCM Token: $token');
if (token != null) {
await _sendTokenToBackend(userId, token);
}
// Listen to token refresh (in case FCM token changes)
_firebaseMessaging.onTokenRefresh.listen((newToken) {
print('FCM Token refreshed: $newToken');
_sendTokenToBackend(userId, newToken);
});
// FOREGROUND NOTIFICATIONS
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print(
'Foreground message received: ${message.notification?.title} - ${message.notification?.body}');
// Show using local notifications
LocalNotificationService.showNotification(message);
});
// APP OPENED FROM BACKGROUND (user taps notification)
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print(
'Notification clicked (background): ${message.notification?.title}');
// Here you can navigate to booking details screen using message.data['booking_id'] etc.
});
}
Future<void> _sendTokenToBackend(String userId, String token) async {
// Replace with your Laravel domain/IP
const baseUrl = 'http://YOUR-LARAVEL-DOMAIN'; // e.g. http://192.168.0.100
await http.post(
Uri.parse('$baseUrl/api/device-token'),
body: {
'user_id': userId,
'fcm_token': token,
},
);
}
}
main.dart
└─ main()
└─ LocalNotificationService.initialize()
local_notification_service.dart
└─ initialize()
Your Login/Home Screen
└─ FirebaseApi.initNotifications()
firebase_api.dart
└─ initNotifications()
└─ onMessage.listen()
└─ LocalNotificationService.showNotification()
local_notification_service.dart
└─ showNotification()
└─ notificationsPlugin.show() → (Notification appears)
Top comments (0)