How to connect my arduino nano ble 33 sense rev2 to my flutter app via BLE

Hello everyone, so I'm making a seizure detection device, i'll be using my arduino nano ble 33 sense rev2's accelerometer for this and so basically-- I'll be creating a phone app by flutter run in vscode that gets the data from the arduino if ever a seizure will be detected and sends an email with a google maps link to the input caretaker's email on the app screen using the phone's built in gps and sms. but i honestly have no idea how to make my app connect to the arduino via ble. should i change the andorid manifest xml?,
this is my current main.dart right now:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SeizureAlert BLE',
      debugShowCheckedModeBanner: false,
      // βœ… ABSOLUTE FINAL LICENSE FIX
      theme: ThemeData(
        useMaterial3: false, // Disable Material 3
        primarySwatch: Colors.red,
        appBarTheme: const AppBarTheme(
          backgroundColor: Color(0xFFB71C1C),
          foregroundColor: Colors.white,
        ),
      ),
      home: const BleMonitor(),
    );
  }
}

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

  @override
  State<BleMonitor> createState() => _BleMonitorState();
}

class _BleMonitorState extends State<BleMonitor> {
  BluetoothDevice? device;
  BluetoothCharacteristic? charac;
  String status = 'SCAN FOR NANO BLE 33';
  String email = '';
  bool isConnected = false;
  StreamSubscription<List<int>>? subscription;

  final TextEditingController _emailController = TextEditingController();

  @override
  void initState() {
    super.initState();
    loadEmail();
    _emailController.addListener(() {
      email = _emailController.text;
    });
  }

  Future<void> loadEmail() async {
    final prefs = await SharedPreferences.getInstance();
    if (mounted) {
      setState(() {
        email = prefs.getString('email') ?? '';
        _emailController.text = email;
      });
    }
  }

  Future<void> saveEmail(String e) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('email', e);
  }

  Future<void> scan() async {
    setState(() => status = 'Scanning...');

    if (!await FlutterBluePlus.isSupported) {
      setState(() => status = 'Bluetooth NOT supported');
      return;
    }

    await FlutterBluePlus.startScan(timeout: const Duration(seconds: 4));

    FlutterBluePlus.scanResults.listen((results) async {
      for (ScanResult r in results) {
        debugPrint('Found: ${r.device.platformName}');
        if (r.device.platformName.toLowerCase().contains('nano') ||
            r.device.platformName.toLowerCase().contains('arduino') ||
            r.device.platformName.toLowerCase().contains('ble')) {
          await FlutterBluePlus.stopScan();
          connect(r.device);
          break;
        }
      }
    });
  }

  Future<void> connect(BluetoothDevice d) async {
    setState(() => status = 'Connecting...');
    try {
      await d.connect(timeout: const Duration(seconds: 10));
      device = d;

      final services = await d.discoverServices();
      for (BluetoothService s in services) {
        for (BluetoothCharacteristic c in s.characteristics) {
          if (c.properties.notify) {
            charac = c;
            await c.setNotifyValue(true);
            subscription = c.lastValueStream.listen((value) {
              if (value.isNotEmpty && value[0] == 1) {
                sendAlert();
              }
            });
            if (mounted) {
              setState(() {
                isConnected = true;
                status = '🧠 Monitoring Seizures';
              });
            }
            return;
          }
        }
      }
      setState(() => status = 'No notify characteristic');
    } catch (e) {
      setState(() => status = 'Connection failed: $e');
    }
  }

  Future<void> disconnect() async {
    await device?.disconnect();
    await subscription?.cancel();
    if (mounted) {
      setState(() {
        isConnected = false;
        device = null;
        charac = null;
        status = 'Disconnected';
      });
    }
  }

  Future<void> sendAlert() async {
    if (email.isEmpty) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('⚠️ Enter caretaker email first!')),
        );
      }
      return;
    }

    const String lat = "8.5853";
    const String lon = "123.8425";
    const String maps = "https://maps.google.com/?q=$lat,$lon";

    final Uri uri = Uri(
      scheme: 'mailto',
      path: email,
      queryParameters: {
        'subject': '🚨 SEIZURE ALERT - EMERGENCY',
        'body':
            'Seizure detected!\n\nπŸ“ Location: $lat,$lon\nπŸ—ΊοΈ Maps: $maps\n\nEMERGENCY - CALL NOW!',
      },
    );

    await launchUrl(uri);

    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('🚨 Alert sent to $email'),
          backgroundColor: Colors.red,
          duration: const Duration(seconds: 3),
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      appBar: AppBar(
        title: const Text('🧠 SeizureAlert BLE'),
        backgroundColor: Colors.red[700],
        foregroundColor: Colors.white,
      ),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: double.infinity,
              padding: const EdgeInsets.all(25),
              decoration: BoxDecoration(
                color: isConnected ? Colors.green[100] : Colors.orange[100],
                borderRadius: BorderRadius.circular(20),
                border: Border.all(
                  color: isConnected ? Colors.green : Colors.orange,
                  width: 2,
                ),
              ),
              child: Column(
                children: [
                  Icon(
                    isConnected
                        ? Icons.monitor_heart
                        : Icons.bluetooth_searching,
                    size: 60,
                    color: isConnected ? Colors.green[700] : Colors.orange[700],
                  ),
                  const SizedBox(height: 15),
                  Text(
                    status,
                    style: const TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                    textAlign: TextAlign.center,
                  ),
                ],
              ),
            ),
            const SizedBox(height: 40),
            TextField(
              controller: _emailController,
              decoration: InputDecoration(
                labelText: 'Caretaker Email *',
                hintText: 'emergency@example.com',
                prefixIcon: const Icon(Icons.email),
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
              ),
              keyboardType: TextInputType.emailAddress,
              onEditingComplete: () => saveEmail(_emailController.text),
            ),
            const SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton.icon(
                  onPressed: scan,
                  icon: const Icon(Icons.search),
                  label: const Text('SCAN'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.blue[600],
                    foregroundColor: Colors.white,
                    padding: const EdgeInsets.symmetric(
                      horizontal: 30,
                      vertical: 15,
                    ),
                  ),
                ),
                ElevatedButton.icon(
                  onPressed: isConnected ? disconnect : null,
                  icon: const Icon(Icons.bluetooth_disabled),
                  label: const Text('DISCONNECT'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.red[600],
                    foregroundColor: Colors.white,
                    padding: const EdgeInsets.symmetric(
                      horizontal: 20,
                      vertical: 15,
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 20),
            const Text(
              'πŸ“ Ozamiz City | Arduino Nano BLE 33 Sense Rev2',
              style: TextStyle(fontSize: 12, color: Colors.grey),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _emailController.dispose();
    subscription?.cancel();
    device?.disconnect();
    super.dispose();
  }
}

and this here is my pubspec yaml:

name: flutter_application_1
description: Seizure Alert BLE App
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '>=3.2.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  flutter_blue_plus: ^1.35.5
  url_launcher: ^6.3.1
  shared_preferences: ^2.3.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0

flutter:
  uses-material-design: true

this is my android manifest xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
    <!-- BLE Permissions -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" 
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    
    <!-- Location (required for BLE scan) -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    
    <!-- Internet (for maps) -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:label="SeizureAlert"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme" />
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

this is my build.gradle.kts:

android {
    namespace = "com.example.seizure_alert"
    compileSdk = flutter.compileSdkVersion
    ndkVersion = flutter.ndkVersion

    defaultConfig {
        applicationId = "com.example.seizure_alert"
        minSdk = 21        // ← CHANGED FROM 26 TO 21
        targetSdk = flutter.targetSdkVersion
        versionCode = flutterVersionCode.toInteger()
        versionName = flutterVersionName
    }
    
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

and this is my widget test dart:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('SeizureAlert BLE test', (WidgetTester tester) async {
    await tester.pumpWidget(const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('SCAN FOR NANO BLE 33', style: TextStyle(fontSize: 18)),
              SizedBox(height: 20),
              Text('Caretaker Email *'),
            ],
          ),
        ),
      ),
    ));

    expect(find.text('SCAN FOR NANO BLE 33'), findsOneWidget);
    expect(find.text('Caretaker Email *'), findsOneWidget);
  });
}

help is very much appreciated because I always get this problem (

[{
	"resource": "/c:/Users/ADMIN/Documents/sentryultrafinalnewest/flutter_application_1/lib/main.dart",
	"owner": "_generated_diagnostic_collection_name_#0",
	"code": {
		"value": "missing_required_argument",
		"target": {
			"$mid": 1,
			"path": "/diagnostics/missing_required_argument",
			"scheme": "https",
			"authority": "dart.dev"
		}
	},
	"severity": 8,
	"message": "The named parameter 'license' is required, but there's no corresponding argument.\nTry adding the required argument.",
	"source": "dart",
	"startLineNumber": 99,
	"startColumn": 15,
	"endLineNumber": 99,
	"endColumn": 22,
	"modelVersionId": 57,
	"origin": "extHost1"
}] 

no matter how many times i try to 'fix' it, that one problem always occurs. so help is vv much appreciated

yup i just edited it sorry,..... i was rushing cause i was so close to crashing out over flutter XD

1 Like

Much easier to read. Thank you.