Debug School

rakesh kumar
rakesh kumar

Posted on • Edited on

End-to-End Push Notifications in Flutter with Laravel

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:

C:\myworkspace\motoshare-web\storage\fcm-service-account.json
Enter fullscreen mode Exit fullscreen mode

STEP 2 — Add in .env

FIREBASE_CREDENTIALS=storage/app/fcm-service-account.json
FIREBASE_PROJECT_ID=your-project-id
==============OR============================
FIREBASE_CREDENTIALS=fcm-service-account.json
FIREBASE_PROJECT_ID=motoshare-2f51c

Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode
<?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->string('fcm_token')->nullable();
        $table->timestamps();
    });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('device_tokens');
    }
};
Enter fullscreen mode Exit fullscreen mode

Run:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Model: DeviceToken

php artisan make:model DeviceToken
Enter fullscreen mode Exit fullscreen mode
app/Models/DeviceToken.php:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class DeviceToken extends Model
{
    protected $fillable = [
        'user_id',
        'fcm_token',
    ];
}
Enter fullscreen mode Exit fullscreen mode

Controller: Save FCM Token from Flutter

Route in routes/api.php:

use App\Http\Controllers\DeviceTokenController;

Route::post('/device-token', [DeviceTokenController::class, 'store']);
Enter fullscreen mode Exit fullscreen mode

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',
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

==============================OR=========================

 public function get_tokenbyemail(Request $request)
    {
        // 1) Validate input
          Log::info('get_tokenbyemail data:', $request->all());
        $data = $request->validate([
            'email' => 'required|email',
            'token' => 'required|string',
        ]);
        // 2) Find user by email
        $user = User::where('email', $data['email'])->first();

        if (!$user) {
            return response()->json([
                'success' => false,
                'message' => 'User not found for given email',
            ], 404);
        }

        // 3) Save / update FCM token in device_tokens table
        $deviceToken = DeviceToken::updateOrCreate(
            ['user_id' => $user->id],           // search by user_id
            ['fcm_token' => $data['token']]     // update this field
        );

        // 4) Return JSON response
        return response()->json([
            'success' => true,
            'message' => 'FCM token saved',
            'data'    => [
                'user_id'         => $user->id,
                'device_token_id' => $deviceToken->id,
                'fcm_token'       => $deviceToken->fcm_token,
            ],
        ]);
    }


public function get_tokenbyphone(Request $request)
{
    // 1) Validate input
      Log::info('get_tokenbyphone data:', $request->all());
    $data = $request->validate([
        'phone' => 'required|string',
        'token' => 'required|string',
    ]);

    // 2) Find user by phone number
    $user = User::where('number', $data['phone'])->first();

    if (!$user) {
        return response()->json([
            'success' => false,
            'message' => 'User not found for given phone number',
        ], 404);
    }

    // 3) Save or update FCM token for this user
    $deviceToken = DeviceToken::updateOrCreate(
        ['user_id' => $user->id],          // Search by user_id
        ['fcm_token' => $data['token']]    // Update fcm_token field
    );

    // 4) Return response
    return response()->json([
        'success' => true,
        'message' => 'FCM token saved successfully',
        'data' => [
            'user_id'         => $user->id,
            'device_token_id' => $deviceToken->id,
            'fcm_token'       => $deviceToken->fcm_token,
        ]
    ]);
}
Enter fullscreen mode Exit fullscreen mode

FCM Service: app/Services/FcmService.php

Create the file manually:

<?php

namespace App\Services;

use Google\Client;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;

class FcmService
{
    /**
     * Send push notification using FCM HTTP v1 API
     */
public function sendToToken(string $token, string $title, string $body, array $data = []): bool
{
    try {
        $credRelative = env('FIREBASE_CREDENTIALS', 'fcm-service-account.json');
        $credPath     = storage_path($credRelative);

        Log::info('[FCM V1] Using credentials path', ['path' => $credPath]);

        if (!is_file($credPath)) {
            Log::error('[FCM V1] Firebase credentials file not found or not a file', [
                'path' => $credPath,
            ]);
            return false;
        }

        $client = new Client();
        $client->setAuthConfig($credPath);
        $client->addScope('https://www.googleapis.com/auth/firebase.messaging');

        $googleToken = $client->fetchAccessTokenWithAssertion();
        $accessToken = $googleToken['access_token'] ?? null;

        if (!$accessToken) {
            Log::error("[FCM V1] Failed to fetch access token", $googleToken);
            return false;
        }

        // 🔹 Sanitize data: ALL values must be STRING for FCM
        $cleanData = [];
        foreach ($data as $key => $value) {
            if (is_bool($value)) {
                $cleanData[$key] = $value ? '1' : '0';
            } elseif (is_scalar($value)) { // int, float, string
                $cleanData[$key] = (string) $value;
            } else {
                // arrays/objects → JSON string
                $cleanData[$key] = json_encode($value);
            }
        }

        $payload = [
            'message' => [
                'token' => $token,
                'notification' => [
                    'title' => $title,
                    'body'  => $body,
                ],
                'data' => $cleanData,
            ],
        ];

        $projectId = env('FIREBASE_PROJECT_ID');

        $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,
                'payload'  => $payload,            // helpful for debugging
                '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;
    }
}
}

Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

=============================OR==============

class UtilityFunctionsController extends Controller
{

   protected $accessToken;
    protected $phoneNumberId;
    protected $version;
    protected $url;
protected FcmService $fcm;

  public function __construct(FcmService $fcm)
{
    // Inject FCM Service
    $this->fcm = $fcm;

    // Keep existing WhatsApp initialization
    $this->accessToken   = env('WHATSAPP_ACCESS_TOKEN');
    $this->phoneNumberId = env('WHATSAPP_PHONE_NUMBER_ID');
    $this->version       = 'v20.0';
    $this->url           = "https://graph.facebook.com/{$this->version}/{$this->phoneNumberId}/messages";
}
Enter fullscreen mode Exit fullscreen mode

Now your updated createBooking():

 public function createBooking($request, $getphone, $getvehicle)
    {
           $booking = null; // Default to null in case no booking gets created
        $endDate = new DateTime($request->end_date);
        $endDate->modify('-1 day');

        // Get shop details to set destination
        $shop_id = $getvehicle->shop_id;
        $getshop = Shop::where('id', $shop_id)->first();
        $destination = $getshop->partner_name;  // Corrected to use shop's partner name as destination
        $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;
        // Create booking in the database
      $isDuplicate = Booking::where('user_id', $getphone->id)
    ->where('vechicle_id', $vehicle_id)
    ->where('start_date', $request->start_date)
    ->where('end_date', $endDate->format('Y-m-d'))
    ->whereIn('status', [0, 1, 2, 3, 4])
    ->exists();    
        log::info("inside createbooking");
        log::info($isDuplicate);
        if (!$isDuplicate) {
                $booking = Booking::create([
                    'user_name' => $getphone->name,
                    'destination' => $destination, // Use destination from shop model
                    'vechicle_type' => $getvehicle->vechicle,
                    'brand' => $getvehicle->brand,
                    'model' => $getvehicle->model,
                    'price' => $this->calculateBookingPrice(
            $request->start_date,
            $request->end_date,
            $vehicleprice  // make sure this is per-day price from your DB/model
        ),
            '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',
        ]);

        // Send email notifications to user, vendor, and admin

     // Send WhatsApp notification to user



    $this->sendWhatsappMessage(
        $getphone, $getvehicle, $request->total_price, $getvehicle->vechicle,
        $getvehicle->brand, $getvehicle->model, $getshop->partner_name,
        $request->start_date, $endDate,  null, $template=1
    );
}
    $this->sendEmails(
    $getphone,            // User model (user details)
    $vendor,              // Vendor model (vendor details)
    $booking,             // Booking model (booking details)
    $getvehicle,          // Vehicle model (vehicle details)
    $shop,                // Shop model (shop details)
    $request,             // Request data (includes total_price, start_date, end_date)
    $payment_type         // Payment type (for status logic)
   );

 $this->removeDuplicateBookings();
  log::info($getphone->id);
$startDateStr = $request->start_date;
$endDateStr   = $endDate->format('Y-m-d');

$title = 'Booking Created Successfully';
$body = "Hi {$getphone->name}, your booking #{$booking->id} "
      . "for {$getvehicle->brand} {$getvehicle->model} "
      . "from {$startDateStr} to {$endDateStr} is created.";

$this->sendBookingNotification(
    $getphone,     // user
    $booking,      // booking model
    $getvehicle,   // vehicle
    $getshop,      // shop
    $title,        // title
    $body,         // body
    'booking_created' // type
);       // Return success response


$bodys = "Hi {$vendor->name}, your booking #{$booking->id} "
      . "for {$getvehicle->brand} {$getvehicle->model} "
      . "from {$startDateStr} to {$endDateStr} is created.";
$this->sendBookingNotification(
    $vendor,     // user
    $booking,      // booking model
    $getvehicle,   // vehicle
    $getshop,      // shop
    $title,        // title
    $bodys,         // body
   'booking_created' // type
);  


       return $booking;

    }
==================================================================

private function sendBookingNotification($getphone, $booking, $getvehicle, $getshop, $title, $body, $type)
{
    Log::info("[FCM] Notification triggered for user: {$getphone->id}");

    $tokens = DeviceToken::where('user_id', $getphone->id)->pluck('fcm_token')->all();

    if (empty($tokens)) {
        Log::warning('[FCM] No device tokens for user', ['user_id' => $getphone->id]);
        return;
    }

    foreach ($tokens as $token) {
        $this->fcm->sendToToken($token, $title, $body, [
            'type'        => $type,
            'booking_id'  => $booking->id,
            'price'       => $booking->price,
            'state'       => $booking->state,
            'vehicle'     => $getvehicle->vechicle,
            'brand'       => $getvehicle->brand,
            'model'       => $getvehicle->model,
            'shop_name'   => $getshop->partner_name,
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Flutter Setup

Add dependencies (pubspec.yaml)


step1
android/build.gradle

buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.gms:google-services:4.4.2'
    }
}
Enter fullscreen mode Exit fullscreen mode

STEP 2 — Add plugin inside app/build.gradle
android/app/build.gradle

dependencies:
  flutter:
    sdk: flutter

  app_settings: ^6.1.1
  firebase_core: ^4.2.1
  firebase_messaging: ^16.0.4
  flutter_local_notifications: ^19.5.0
  http: ^1.2.2

Enter fullscreen mode Exit fullscreen mode

AndroidManifest.xml

R:\firebase\firebase\android\app\src\main\AndroidManifest.xml
Enter fullscreen mode Exit fullscreen mode
       <meta-data
        android:name="com.google.firebase.messaging.default_notification_channel_id"
        android:value="high_importance_channel" />
Enter fullscreen mode Exit fullscreen mode

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'
}
Enter fullscreen mode Exit fullscreen mode

Run:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

Local Notification Service (for foreground notifications)


Create file: lib/local_notification_service.dart

import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:app_settings/app_settings.dart';
import 'package:motoshare/main.dart';
import 'package:motoshare/screens/drawer_widget/messagescreen.dart';
import 'package:motoshare/screens/drawer_widget/all_notifications_page.dart';
import 'package:motoshare/screens/login.dart';
import 'package:motoshare/screens/login_screen.dart';
import 'package:motoshare/view_modal/Login_view_model.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

class NotificationServices {
  final FirebaseMessaging _messaging = FirebaseMessaging.instance;

  final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
   FlutterLocalNotificationsPlugin();
  // -----------------------------
  // 1) Request permission
  // -----------------------------
  Future<void> requestNotificationPermission() async {
    NotificationSettings settings = await _messaging.requestPermission(
      alert: true,
      announcement: true,
      badge: true,
      carPlay: false,
      criticalAlert: true,
      provisional: true,
      sound: true,
    );

    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      print("User granted permission");
    } else if (settings.authorizationStatus ==
        AuthorizationStatus.provisional) {
      print("User granted provisional permission");
    } else {
      print("User denied notification permission");
      AppSettings.openAppSettings();
    }
  }

  // -----------------------------
  // 2) Token + refresh
  // -----------------------------
  Future<String?> getDeviceToken() async {
    String? token = await _messaging.getToken();
    print("FCM my Token: $token");
    return token;
  }


void monitorTokenRefresh(Function(String) onNewToken) {
  FirebaseMessaging.instance.onTokenRefresh.listen((newToken) async {
    print("🔄 FCM AUTO-REFRESHED TOKEN");
    print("New Token: $newToken");

    // Callback to save + send to backend
    onNewToken(newToken);
  });
}
  // -----------------------------
  // 3) Init local notifications
  //    (CALL ONLY ON MAIN ISOLATE)
  // -----------------------------
  Future<void> initLocalNotifications() async {
    const AndroidInitializationSettings androidInitializationSettings =
        AndroidInitializationSettings('@mipmap/ic_launcher');

    const DarwinInitializationSettings iosInitializationSettings =
        DarwinInitializationSettings();

    const InitializationSettings initializationSettings =
        InitializationSettings(
      android: androidInitializationSettings,
      iOS: iosInitializationSettings,
    );

  await _flutterLocalNotificationsPlugin.initialize(
  initializationSettings,
  onDidReceiveNotificationResponse: (response) {
    String? bookingId = response.payload;
    print("📲 LOCAL notification tapped with payload: $bookingId");

    if (bookingId != null) {
      _navigateFromTopBar(bookingId);
    }
  },
);

    // NOTE: channel id must match showNotification()
    const AndroidNotificationChannel channel = AndroidNotificationChannel(
      'high_importance_channel',
      'High Importance Notifications',
      description: 'Channel for important notifications',
      importance: Importance.max,
    );

    await _flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);
  }

  // -----------------------------
  // 4) Foreground messages
  // -----------------------------
 void firebaseInit() {
  FirebaseMessaging.onMessage.listen((message) {
    print("FG data: ${message.data}");
    showNotification(message);


    // if (message.data['type'] == 'booking_created') {
    //   final bookingId = message.data['booking_id'];
    //   final ctx = navigatorKey.currentContext;

    //   if (ctx == null) return;

    //   showDialog(
    //     context: ctx,
    //     builder: (_) => AlertDialog(
    //       title: Text("Booking Created"),
    //       content: Text("Tap to open booking details."),
    //       actions: [
    //         TextButton(
    //           child: Text("Open"),
    //           onPressed: () {
    //             Navigator.pop(ctx);
    //             Navigator.push(
    //               ctx,
    //               MaterialPageRoute(
    //                 builder: (_) => AllVehicle(),
    //               ),
    //             );
    //           },
    //         )
    //       ],
    //     ),
    //   );
    // }


  });
}


void _navigateFromTopBar(String bookingId) {
  final ctx = navigatorKey.currentContext;
  if (ctx == null) {
    print("❌ No context for navigation");
    return;
  }
  Navigator.push(
    ctx,
    MaterialPageRoute(
      builder: (_) => AllNotificationsPage(),
    ),
  );
}



void handleMessage(BuildContext context, RemoteMessage message) {
  if (!context.mounted) return;

  if (message.data['type'] == 'booking_created') {
    final bookingId = message.data['booking_id']; // FIXED KEY

    if (bookingId == null) {
      print("❌ booking_id is missing in message.data");
      return;
    }

    print("Navigating to booking screen with ID: $bookingId");

    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (_) => MessageScreen(id: bookingId),
      ),
    );
  }
}

  // -----------------------------
  // 5) Local notification display
  // -----------------------------
  Future<void> showNotification(RemoteMessage message) async {
    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
      'high_importance_channel', // ✅ SAME as in channel
      'High Importance Notifications',
      channelDescription: 'Channel for important notifications',
      importance: Importance.high,
      priority: Priority.high,
      icon: '@mipmap/ic_launcher',
    );

    const DarwinNotificationDetails darwinNotificationDetails =
        DarwinNotificationDetails(
      presentAlert: true,
      presentBadge: true,
      presentSound: true,
    );

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
      iOS: darwinNotificationDetails,
    );

    await _flutterLocalNotificationsPlugin.show(
      0,
  message.notification?.title ?? 'Notification',
  message.notification?.body ?? '',
  notificationDetails,
  payload: message.data['booking_id'],
    );
  }

  // -----------------------------
  // 6) Background / resumed click
  // -----------------------------
void handleNotificationClick() {
  // App opened from background (notification tap)
  FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
    print("📲 Notification clicked (Background)");
    _handleNavigation(message);
  });
}

Future<void> handleInitialMessage() async {
  // App opened from terminated state by tapping notification
  RemoteMessage? message = await FirebaseMessaging.instance.getInitialMessage();

  if (message != null) {
    print("🚀 App opened from terminated via notification");
    _handleNavigation(message);
  }
}

void _handleNavigation(RemoteMessage message) async {
  final bookingId = message.data['booking_id'];

  if (bookingId == null) {
    print("❌ booking_id missing in notification click!");
    return;
  }

  final ctx = navigatorKey.currentContext;
  if (ctx == null) {
    print("❌ navigatorKey context is null");
    return;
  }

  // -------------------------
  // CHECK LOGIN STATUS
  // -------------------------
  final prefs = await SharedPreferences.getInstance();
  final email = prefs.getString('email');

  bool isLoggedIn = email != null && email.trim().isNotEmpty;

  if (isLoggedIn) {
    // USER LOGGED IN → Redirect to Notifications
    Navigator.push(
      ctx,
      MaterialPageRoute(builder: (_) => const AllNotificationsPage()),
    );
  } else {
   Navigator.push(
  ctx,
  MaterialPageRoute(
    builder: (context) {
      final loginViewModel = Provider.of<LoginViewModel>(context, listen: false);
      return LoginScreen(loginViewModel: loginViewModel);
    },
  ),
);
  }
}
}

class NotificationClickScreen extends StatelessWidget {
  final String title;
  final String body;

  const NotificationClickScreen({
    super.key,
    required this.title,
    required this.body,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Notification Details")),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              "Title: $title",
              style: const TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 10),
            Text(
              "Body: $body",
              style: const TextStyle(fontSize: 18),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Firebase API Wrapper (get token, send to Laravel, handle foreground + background)

In motoshare view model

Future<List<DeviceToken>> gettokenbyemail(String email,String token) async {

  var url = "$baseUrl/api/get_tokenbyemail";  
  // Create the request body including the additional parameters
  var body = jsonEncode({
    "email": email, // Email of the user 
     "token": token, // Email of the user    
  });
  print("Request fetchProjects: $url");
  print("Request fetchProjects: $body");
  // Send the POST request to the API
  final response = await http.post(
    Uri.parse(url),
    headers: {
      "Content-Type": "application/json", // Set content type as JSON
    },
    body: body,
  );
  print("Response Status getshopname: ${response.statusCode}");
  print("Response Body getshopname: ${response.body}");
  // Check if the response status is OK (200)
  if (response.statusCode == 200) {
    // Parse the response body to a Map<String, dynamic>
    Map<String, dynamic> responseData = jsonDecode(response.body);
    // Extract the 'data' field
    var vehicleData = responseData['data'];
     return vehicleData.map((data) => DeviceToken.fromJson(data)).toList();
  } else {
    throw Exception("Failed to fetch projects data");
  }
}

Future<List<DeviceToken>> gettokenbyphone(String phone,String token) async {

  var url = "$baseUrl/api/get_tokenbyphone";  
  // Create the request body including the additional parameters
  var body = jsonEncode({
    "phone": phone, // Email of the user 
     "token": token, // Email of the user    
  });
  print("Request fetchProjects: $url");
  print("Request fetchProjects: $body");
  // Send the POST request to the API
  final response = await http.post(
    Uri.parse(url),
    headers: {
      "Content-Type": "application/json", // Set content type as JSON
    },
    body: body,
  );
  print("Response Status getshopname: ${response.statusCode}");
  print("Response Body getshopname: ${response.body}");
  // Check if the response status is OK (200)
  if (response.statusCode == 200) {
    // Parse the response body to a Map<String, dynamic>
    Map<String, dynamic> responseData = jsonDecode(response.body);
    // Extract the 'data' field
    var vehicleData = responseData['data'];
     return vehicleData.map((data) => DeviceToken.fromJson(data)).toList();
  } else {
    throw Exception("Failed to fetch projects data");
  }
}
Enter fullscreen mode Exit fullscreen mode

main.dart – Initialize Firebase, background handler, and start FCM

Create / update lib/main.dart:

import 'package:flutter/material.dart';
import 'package:motoshare/screens/drawer_widget/decider.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:motoshare/screens/countryselectionpage.dart';
import 'package:motoshare/services/motoshare_service.dart';
import 'package:motoshare/view_modal/country_view_model.dart';
import 'package:motoshare/view_modal/Login_view_model.dart';
import 'package:motoshare/view_modal/motoshare_view_model.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:app_settings/app_settings.dart';
import 'package:motoshare/services/notification_services.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();


void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // 2) Background handler
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
   await dotenv.load(fileName: ".env");
  final prefs = await SharedPreferences.getInstance();

  // Fetch the saved baseUrl from SharedPreferences
  String initialBaseUrl = prefs.getString('baseUrl') ?? 'https://motoshare.in';  // Default URL

  runApp(DynamicAppWrapper(initialBaseUrl: initialBaseUrl));
}
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print(message.notification!.title.toString());
}
Enter fullscreen mode Exit fullscreen mode

====================OR===========================
In decider.dart file

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:motoshare/screens/countryselectionpage.dart';
import 'package:motoshare/screens/drawer_widget/allvehicle.dart';
import 'package:provider/provider.dart';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:motoshare/services/notification_services.dart';
import 'package:motoshare/view_modal/motoshare_view_model.dart';

class DeciderPage extends StatefulWidget {
  const DeciderPage({super.key});

  @override
  State<DeciderPage> createState() => _DeciderPageState();
}

class _DeciderPageState extends State<DeciderPage> {
  final NotificationServices _notificationServices = NotificationServices();
 @override
  void initState() {
    super.initState();
     _initNotificationsAndSendToken();
  }
Future<void> _initNotificationsAndSendToken() async {
    // 1) Token Refresh Listener
    _notificationServices.monitorTokenRefresh((newToken) async {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString('fcm_token', newToken);

      final savedPhone = prefs.getString('phone');
      final savedEmail = prefs.getString('email');

      if (savedPhone != null && savedPhone.isNotEmpty) {
        await _sendTokenToBackendByPhone(savedPhone, newToken);
      } else if (savedEmail != null && savedEmail.isNotEmpty) {
        await _sendTokenToBackendByEmail(savedEmail, newToken);
      }
    });

    // 2) Ask permission
    await _notificationServices.requestNotificationPermission();

    // 3) Get and save token
    final token = await _notificationServices.getDeviceToken();
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('fcm_token', token ?? "");

    // 4) Send token to backend
    final savedPhone = prefs.getString('phone');
    final savedEmail = prefs.getString('email');

    if (savedPhone != null && savedPhone.isNotEmpty) {
      await _sendTokenToBackendByPhone(savedPhone, token.toString());
    } else if (savedEmail != null && savedEmail.isNotEmpty) {
      await _sendTokenToBackendByEmail(savedEmail, token.toString());
    }

    // 5) Initialize FCM handlers
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      await _notificationServices.initLocalNotifications();
      _notificationServices.firebaseInit();                 // Foreground
      _notificationServices.handleNotificationClick();      // Background click
      _notificationServices.handleInitialMessage();         // Terminated click
    });

    // 6) Navigate
    _navigate();
  }



Future<void> _sendTokenToBackendByEmail(String email, String token) async {
  // exactly like your getmyprofile pattern
  final profileViewModel =
      Provider.of<MotoshareViewModel>(context, listen: false);

  try {
    await profileViewModel.sendFcmTokenByEmail(email,token);
    print("✅ FCM token sent to backend using email");
  } catch (e) {
    print("❌ Error sending FCM token by email: $e");
  }
}

Future<void> _sendTokenToBackendByPhone(String phone, String token) async {
  final profileViewModel =
      Provider.of<MotoshareViewModel>(context, listen: false);

  try {
    await profileViewModel.sendFcmTokenByPhone(phone,token);
    print("✅ FCM token sent to backend using phone");
  } catch (e) {
    print("❌ Error sending FCM token by phone: $e");
  }
}
Enter fullscreen mode Exit fullscreen mode

=============== OR in motoshare.partner=========================

In C:\Project\motoshare\lib\screens\drawer_widget\allvehicle.dart

  Future<void> _fetchData() async {
    setState(() {
      _isLoading = true;
    });
    try {
      final motoshareViewModel =Provider.of<MotoshareViewModel>(context, listen: false);
      await motoshareViewModel.getVehicleData();
      final prefs = await SharedPreferences.getInstance();
  String baseUrls = prefs.getString('baseUrl') ?? 'https://motoshare.in';  // Default URL 

  String currencySymbols = prefs.getString('currency_symbol')?? 'Rs';  // Default URL
 String countrynames = prefs.getString('countryname')?? 'Rs';  // Default URL
 print("mycountryname is there");
 print(countrynames);
// Load baseUrl (fallback to motosshare.in)
// Read stored email
final email = prefs.getString('email');

// Check email not null & not empty
if (email != null && email.trim().isNotEmpty) {

  // Read FCM token
  final fcmToken = prefs.getString('fcm_token');

  print("Stored FCM Token: $fcmToken");

  if (fcmToken != null && fcmToken.trim().isNotEmpty) {
    // Call backend API
    await _sendTokenToBackendByEmail(email, fcmToken);

    print("Token sent to backend successfully");
  } else {
    print("⚠ No FCM token found in SharedPreferences");
  }

} else {
  print("⚠ Email is NULL or EMPTY — cannot send token");
}


  ===================================================================

Enter fullscreen mode Exit fullscreen mode

Future _sendTokenToBackendByEmail(String email, String token) async {
// exactly like your getmyprofile pattern
final profileViewModel =
Provider.of(context, listen: false);

try {
await profileViewModel.sendFcmTokenByEmail(email,token);
print("✅ FCM token sent to backend using email");
} catch (e) {
print("❌ Error sending FCM token by email: $e");
}
}

C:\Project\motoshare\lib\screens\login.dart

Enter fullscreen mode Exit fullscreen mode


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)
Enter fullscreen mode Exit fullscreen mode

firebase_notifications

Troubleshooting steps

minSdk = 23
Enter fullscreen mode Exit fullscreen mode

C:\Project\motoshare\android\settings.gradle

Look for something like:

plugins {
    id "dev.flutter.flutter-plugin-loader" version "1.0.0" apply false
    id "com.android.application" version "8.1.0" apply false
    id "org.jetbrains.kotlin.android" version "1.8.0" apply false   // 👈 here
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)