Flutter ile Bluetooth Cihazları Tarama ve Bağlantı Kurup/Kaldırma Uygulaması – 3 (Izınler ve Kontroller)

Flutter ile Bluetooth Cihazları Tarama ve Bağlantı Kurup/Kaldırma Uygulaması – 1 başlığı altında verilen kodda, import kısmında flutter_blue kutuphanesine prefix verilerek tanımlanmasının sebebi flutter_reactive_ble kutuphanesinin de BluetoothDevice sınıfına sahip olmasıdır. Hangi kutuphanenin BluetoothDevice sınıfını kullanacağımızı belirtmek için kütüphanelerin birine prefix vermemiz gerekti.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_blue/flutter_blue.dart' as flutter_blue;
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:open_settings/open_settings.dart';
import 'package:permission_handler/permission_handler.dart';

flutter_blue kutuphanesine ait bir sınıf, metod veya özellik kullanıldığı zaman artık bunu belirtmemiz gerekmektedir. flutter_blue.FlutterBlue.instance veya flutter_blue.BluetoothDevice gibi kullanımlarının sebebi budur.

Uygulamanın amacı çevredeki bağlanabilecek cihazları taramak ve bu cihazlara bağlanıp, bağlı cihazlarla da bağlantıyı koparmak olduğundan iki tane boş liste tanımladık.

Bulunan cihazları listelemek için:

List<DiscoveredDevice> _discoveredDeviceList = [];

Bağlı olduğumuz cihazları listelemek için:

List<DiscoveredDevice> _connectedBluetoothDeviceList = [];

Konum ve bluetooth izinlerini uygulama başlatıldığında almak için _getBluetoothPermission(), _getLocationPermission(), _getPermissions() methodları initState içerisinde çağrıldılar.

@override
void initState() {
super.initState();

// setting devices orientation on landscape
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
]);

_getBluetoothPermission();
_getLocationPermission();
_getPermissions();
}

Uygulamaya ilk defa giriş yapıldığında konum ve bluetooth izinleri verilmemiş olacağından if bloğuna ilk olarak bluetoothPermission.isDenied ve locationPermission.isDenied koşullarıyla başladık böylece izin verildikten sonra bu koşullar false döndüreceğinden if bloğu tekrar koşmayacaktır.

İzinler verildikten sonra if bloğu bluetoothPermission.isDenied koşulunu sağlamayıp false’a düştüğü için sonraki koşmalarda else bloğu içerisindeki print komutu koşacak ve konsola Not Granted yazılacak, bu print komutunu Granted olarak değiştirmeniz daha doğru olur.

Future<bool> _getBluetoothPermission() async {
var bluetoothPermission = await Permission.bluetoothScan.status;

if (bluetoothPermission.isDenied) {
if (await Permission.bluetoothScan.request().isGranted) {
if (await Permission.bluetoothConnect.request().isGranted) {
print("Blueooth Permission: Granted");
return true; // Permission granted
}
}
} else {
print("Blueooth Permission: Granted");
}
return false;
}

Future<bool> _getLocationPermission() async {
var locationPermission = await Permission.location.status;

if (locationPermission.isDenied) {
if (await Permission.location.request().isGranted) {
if (await Permission.accessMediaLocation.request().isGranted) {
print("Location Permission: Granted");
return true; // Permission granted
}
}
} else {
print("Location Permission: Granted");
}
return false;
}

Alttaki _getPermissions metodunun tanımlanmasının sebebi ise yukarıdaki gibi izinleri teker teker çağırmak yerine tüm izinleri tek seferde çağırmayı denemek içindir. if bloğu içerisindeki koşulda tüm izinler izin verilmediği sürece true döndürmesi istenmiştir ama şuanki durumda izinlerden sadece birine bile izin verildiği taktirde await Permission.location.status.isDenied && await Permission.bluetoothScan.status.isDenied ifadesi false döndürür.

Bunun için && (AND) ifadesi yerine || (OR) ifadesi kullanmamız gerekmektedir veya her iki ifadenin de başına ! işareti koyabilirsiniz. Böylece sadece tüm izinler verildiği takdirde koşulumuz false döndürür ve if bloğu koşmaz.

Future<void> _getPermissions() async {
// Requesting multiple permissions at once.
if (!await Permission.location.status.isDenied &&
!await Permission.bluetoothScan.status.isDenied) {
Map<Permission, PermissionStatus> statuses = await [
Permission.location,
Permission.bluetoothScan,
].request();
print(statuses[Permission.location]);
print(“\n”);
print(statuses[Permission.bluetoothScan]);
}
}

Çevredeki cihazları taramamız için gereken şartlar sadece izinler değil, aynı zamanda taramaya çalıştığımız zamanda hem bluetooth hem de konum özelliklerinin o anda açık olması gerekmektedir. Bunu kontrol etmek için de _chechkIfBluetoothIsOn ve _chechkIfLocationIsOn methodlarını tanımladık.

Future<bool> _chechkIfBluetoothIsOn() async {
// Check bluetooth service status
var isBluetoothOn = await Permission.bluetooth.serviceStatus.isEnabled;

if (!isBluetoothOn) {
// propmpt user to enable the bluetooth if the bluetooth is off
showDialog(
context: context,
builder: (_) => AlertDialog(
title:
Text("Bluetooth Özelliği ${isBluetoothOn ? "Açık" : "Kapalı"}"),
content: const Text(
"Bu özelliği kullanmak için bluetooth özelliğinin açık olması gerekir."),
actions: [
ElevatedButton(
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop(false),
child: Text(isBluetoothOn ? "Kapat" : "Vazgeç")),
ElevatedButton(
// make the button not intractable when the location is on
onPressed: () async => isBluetoothOn
? null
: {
Navigator.of(context, rootNavigator: true).pop(),
await OpenSettings.openBluetoothSetting(),
},
child: Text(isBluetoothOn ? "" : "Aç")),
],
elevation: 12.0,
backgroundColor: Colors.white,
),
barrierDismissible: true,
);
} else {
//dismiss dialog box
}
return isBluetoothOn;
}

Burada bluetooth servisi açık değilse kullanıcıyı AlertDialog ile uyarıyoruz ve bluetooth servisini açması için yönlendiriyoruz.

await OpenSettings.openBluetoothSetting() satırının amacı Aç tuşuna tıklandığında kullanıcıyı bluetooth ayarlarına yönlendirmek, bu methodu ‘open_settings‘ kutuphanesini import ederek kullanabiliyoruz.


Navigator.of(context, rootNavigator: true).pop(), satırı ise kullanıcıyı yönlendirdiğimizde AlertDialog’u kapatmak içindir, böylece kullanıcı izin verip uygulamaya döndüğünde AlertBox ile karşılaşmayacak.

İlk başlıkta verilen kodda Scan Devices ve List Connected Devices tuşlarında hem if koşulunda hem de ek olarak onPressed metodu altında bluetooth ve konum izinleri istendiğinden iki tane AlertDialog üst üste oluşturulmaktadır. Bu durumu çözmek için _chechkIfBluetoothIsOn ve _checkIfLocationIsOn satırları “Scan Devices” ve “List Connected Devices” tuşlarının onPressed metodundan silinebilir.

TextButton(
onPressed: () async {
_getPermissions();
/* _chechkIfBluetoothIsOn();
_checkIfLocationIsOn(); */

if (await _chechkIfBluetoothIsOn() &&
await _checkIfLocationIsOn()) {
_scanForAvaileableBluetoothDevices();
} else {
print(
"Blueooth veya Location özellikleri açık olmadığından tarama yapılamadı.");
}
},
child: const Text('Scan Devices'),

Yukarıda gösterildiği gibi bu iki satırı yorum satırı da yapabilirsiniz, aynı durum “List Connected Devices” tuşu için de geçerlidir ve aynı işlemi o tuş için de uygulayabilirsiniz.

Flutter ile Bluetooth Cihazları Tarama ve Bağlantı Kurup/Kaldırma Uygulaması – 2

Flutter reactive BLE ve FlutterBlue kütüphaneleri bluetooth cihazlarını bulmak, bağlanmak ve bu cihazlar üzerinden bilgi çekmek için kullanılanılan kütüphanelerdir.

Bu uygulamada Flutter reactive Ble kütüphanesini çevredeki bluetooth cihazlarını bulmak, listelemek ve bağlanmak için, FlutterBlue kütüphanesini de bağlı olduğumuz cihazları listelemek için kullanıyoruz.

Kodların işlevlerini açıklamadan önce kısaca bluetooth cihazlarının çalışma mantığına göz atalım. Uygulamalarımız bluetooth donanımlarıyla iletişim kurarken alttaki katmanlı yapıda Generic Access Profile (GAP) ve Generic Attribute Profile (GATT) katmanları üzerinden bluetooth cihaza bağlanma ve bağlandıktan sonraki iletişim için bu katmanları kullanırlar.

GAP: Cihazların keşfedilmesi ve birbirleri arasındaki bağlantı ile ilgili prosedürlerin çoğunu GAP katmanı üstlenir.

GATT: Uygulamalarımız, GATT katmanını bağlı olan iki cihaz arasındaki veri iletimini sağlamak için kullanır.

GATT iki cihaz arasında bağlantı sağlandıktan sonra devreye girer yani ilk önce GAP ile cihazlar birbirine bağlanıyor sonra GATT ile birbirleri arasında veri alışverişini gerçekleştiriyorlar.

ATT (Attribute Proctocol): Service ve Characteristic ve ilişkili verilerin, herbiri benzersiz 16-bitlik ID’lerle basit tablolar şeklinde tutulması için kullanılır.

GATT server: GATT Client tarafından yazılan veya okunan “characterictic veritabanını” bulunduran cihazlardır.

GATT client: GATT server’dan verileri okuyan veya server’a yazan cihazlardır.

Peripheral: bağlanacağımız bluetooth cihazlar (saat, kulaklık, fare, klavye, sensörler gibi)

Center: bağlantıyı yapacak olduğumuz cihazlar (telefon, tablet, bilgisayar gibi)

Bu durumda her iki cihazda GATT server ve GATT client olabilir, her iki cihazda birbiri üzerinde okuma ve yazma işlemi gerçekleştiriyor.

Peripheral ile center arasındaki ilişki tekli şekildedir yani peripheral bir center ile bağlantı halindeyken, diğer cihazlar bu peripheral’ı göremezler veya bu peripheral’a bağlanamazlar. Bu durum peripheral ve center arasındaki bağlantı sonlanıncaya kadar böyle devam eder.

Center ile peripheral arasındaki ilişki ise çoklu olabilir yani bir center birden fazla peripheral ile aynı anda bağlı olabilir.

Profile: Service kümesi, (Fitness profili; kalp atış hızı servisi, kan basıncı servisi gibi servisleri içerir)

Service: Characteristic kümesi, (Kalp atış hızı servisi; kalp atış hızı değeri, kalp atış hızlarının zamanı gibi karakteristikleri içerir)

Characteristic: Değer ve Başlıktan (Value ve Label) oluşan veri (kalp atış hızı değeri: 10 gibi)

Profile: Neyin sunulduğunu,

Service: Nasıl sunulduğunu,

Characteristic ise Ne sunulduğunu tanımlar.

Veriler cihazların belleğinde “characteristic” şeklinde tutulurlar.

Peripheralların birbiri arasında veri alışverişi yapmaları gerektiği durumlarda bu işlem center üzerinden gerçekleştirilir.

Bizim yaptığımız uygulamanın çalışma şeklini ve yaptığımız listelemeler ve listeler arasındaki ilişkilerin anlam kazanması için buraya kadar olan kısım yeterlidir.

Daha fazla bilgiye sahip olmak isterseniz alttaki linklere göz atabilirsiniz:
https://www.bluetooth.com/bluetooth-resources/bluetooth-5-go-faster-go-further/

https://docs.silabs.com/bluetooth/6.1.0/bluetooth-gatt

https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gatt

https://software-dl.ti.com/lprf/simplelink_cc2640r2_latest/docs/blestack/ble_user_guide/html/ble-stack-3.x/gatt.html

Flutter ile Bluetooth Cihazları Tarama ve Bağlantı Kurup/Kaldırma Uygulaması – 1

Uygulamada hem taranan cihazlar listeleneceği hem de telefonumuzun bağlantıda olduğu cihazların listeleneceği bir arayüz kullanılacak. Iki liste yan yana sergileneceği için uygulamanın yönü landscapeRight ve landscapeLeft olarak ayarlanmıştır.

Ekranı yan döndürme işlemi alttaki kod bloğu ile gerçekleştirebilir.

@override
void initState() {
super.initState();

// setting devices orientation on landscape
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
]);
}

Aynı zamanda DeviceOrientation.portraitDown ve DeviceOrientation.portraitUp satırları ile de uygulamanın sadece dikey eksenlerde çalışması sağlanabilir.

Uygulamanın şu anki görüntüsü aşağıdaki gibidir.

Uygulamamızın çalışma mantığı alttaki akış şemasında verilmiştir.

Çevredeki bağlanılabilecek cihazları bulup listelemek için Appbar’da yer alan “Scan Devices” TextButton’a tıklıyoruz. “List Connected Devices” TextButton’a tıklandığında ise bize o anlık bağlı olunan cihazlar listelenecek.

Ekranın sol kısmında bağlanmaya hazır cihazlar, sağ kısmında ise bağlı olduğumuz cihazlar listelenmektedir. Soldaki listeden seçilen bir cihaza bağlanılması durumunda listeler güncellenecektir, yani seçtiğimiz cihaza başarılı bir şekilde bağlanabilirsek artık cihaz sağda listelenecektir. Ayni mantikla sağ listede yer alan bir cihaz ile olan bağlantının kalkması durumunda bu cihaz da listeler güncellendiğinde sol listede yer alacaktır.

Uygulamanın kodu altta verilmiştir, main.dart dosyası:

import 'dart:async';
import 'dart:core';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_blue/flutter_blue.dart' as flutter_blue;
import 'package:flutter_blue/gen/flutterblue.pb.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:open_settings/open_settings.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
runApp(const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyApp(),
));
}

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

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Android Studio',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});

final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
final _reactive_Ble = FlutterReactiveBle();
final _flutter_Blue = flutter_blue.FlutterBlue.instance;
// Discovered Device List
List<DiscoveredDevice> _discoveredDeviceList = [];
// To get Device Connection States
Stream<ConnectionStateUpdate> get state => _deviceConnectionController.stream;
// Connection Update Control Stream
final _deviceConnectionController = StreamController<ConnectionStateUpdate>();
//Bluetooth Low Energy Connection State Update
late StreamSubscription<ConnectionStateUpdate> _ble_Connection;
String connectionMessage = "";
// Connected Bluetooth Device List
List<flutter_blue.BluetoothDevice> _connectedBluetoothDeviceList = [];

// defined to be used for defining UI behavior
var _isConnecting = false;

@override
void initState() {
super.initState();

// setting devices orientation on landscape
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
]);

_getBluetoothPermission();
_getLocationPermission();
_getPermissions();
}

// Listen to stateStream for updates
/*FlutterBlue.instance.state.listen((BluetoothState state) {});*/

Future<bool> _getBluetoothPermission() async {
var bluetoothPermission = await Permission.bluetoothScan.status;

if (bluetoothPermission.isDenied) {
if (await Permission.bluetoothScan.request().isGranted) {
if (await Permission.bluetoothConnect.request().isGranted) {
print("Blueooth Permission: Granted");
return true; // Permission granted
}
}
} else {
print("Blueooth Permission: Not Granted");
}
return false;
}

Future<bool> _getLocationPermission() async {
var locationPermission = await Permission.location.status;

if (locationPermission.isDenied) {
if (await Permission.location.request().isGranted) {
if (await Permission.accessMediaLocation.request().isGranted) {
print("Location Permission: Granted");
return true; // Permission granted
}
}
} else {
print("Location Permission: Not Granted");
}
return false;
}

Future<void> _getPermissions() async {
// Requesting multiple permissions at once.
if (await Permission.location.status.isDenied &&
await Permission.bluetoothScan.status.isDenied) {
Map<Permission, PermissionStatus> statuses = await [
Permission.location,
Permission.bluetoothScan,
].request();
print(statuses[Permission.location]);
print("\n");
print(statuses[Permission.bluetoothScan]);
}
}

Future<bool> _chechkIfBluetoothIsOn() async {
// Check bluetooth service status
var isBluetoothOn = await Permission.bluetooth.serviceStatus.isEnabled;

if (!isBluetoothOn) {
// propmpt user to enable the bluetooth if the bluetooth is off
showDialog(
context: context,
builder: (_) => AlertDialog(
title:
Text("Bluetooth Özelliği ${isBluetoothOn ? "Açık" : "Kapalı"}"),
content: const Text(
"Bu özelliği kullanmak için bluetooth özelliğinin açık olması gerekir."),
actions: [
ElevatedButton(
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop(false),
child: Text(isBluetoothOn ? "Kapat" : "Vazgeç")),
ElevatedButton(
// make the button not intractable when the location is on
onPressed: () async => isBluetoothOn
? null
: {
Navigator.of(context, rootNavigator: true).pop(),
await OpenSettings.openBluetoothSetting(),
},
child: Text(isBluetoothOn ? "" : "Aç")),
],
elevation: 12.0,
backgroundColor: Colors.white,
),
barrierDismissible: true,
);
} else {
//dismiss dialog box
}
return isBluetoothOn;
}

Future<bool> _checkIfLocationIsOn() async {
// Check location service status
var isLocationOn =
await Permission.locationWhenInUse.serviceStatus.isEnabled;

if (!isLocationOn) {
// Request location permission
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text("Konum Özelliği ${isLocationOn ? "Açık" : "Kapalı"}"),
content: const Text(
"Bu özelliği kullanmak için konum özelliğinin açık olması gerekir."),
actions: [
ElevatedButton(
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop(false),
child: Text(isLocationOn ? "Kapat" : "Vazgeç")),
ElevatedButton(
// make the button not intractable when th location is on
onPressed: () async => isLocationOn
? null
: {
Navigator.of(context, rootNavigator: true).pop(),
await OpenSettings.openLocationSourceSetting(),
},
child: Text(isLocationOn ? "" : "Aç")),
],
elevation: 12.0,
backgroundColor: Colors.white,
),
barrierDismissible: true,
);
} else {}
return isLocationOn;
}

void _scanForAvaileableBluetoothDevices() {
setState(() {
_discoveredDeviceList = [];
});

StreamSubscription<DiscoveredDevice> scanSubscription =
_reactive_Ble.scanForDevices(
withServices: [],
scanMode: ScanMode.lowLatency,
).listen((device) {
setState(() {
// if device has a name and not on the list, add device to the list
if (!_discoveredDeviceList
.any((element) => element.id == device.id) &&
device.name != "") {
_discoveredDeviceList.add(device);
}
});
}, onError: (error) {
print("Error occured while scanning: $error");
});

Future.delayed(const Duration(seconds: 10)).then((_) {
scanSubscription.cancel();
print("Scan completed");
});

print("Available Bluetooth Devices is Dsplayed:");
}

Future<void> _listConnectedBluetoothDevices() async {
var connectedDevices = await _flutter_Blue.connectedDevices;

setState(() => _connectedBluetoothDeviceList = connectedDevices );
print("_connectedBluetoothDeviceList.lenght: ${_connectedBluetoothDeviceList.length}");
print("connectedDevices count: ${connectedDevices.length}");
print("Connected Device List is Displayed:");
}

Future<void> _connectToTheSelectedDevice(
BuildContext context,
DiscoveredDevice discoveredDevice
) async {

setState(() {
_isConnecting = true;
});

_ble_Connection = _reactive_Ble.connectToDevice(
id: discoveredDevice.id,
connectionTimeout: const Duration (seconds: 30)).listen((update) async {
// connection update handler (async)
print('ConnectionState for device : ${update.connectionState}');

setState(() {
if (update.connectionState == DeviceConnectionState.connecting) {
connectionMessage = "Connecting...";
}

if (update.connectionState == DeviceConnectionState.connected) {
connectionMessage =
"Connected";
}

if (update.connectionState == DeviceConnectionState.disconnected) {
connectionMessage = "Disconnected";
}
});
_deviceConnectionController.add(update);

if (update.connectionState == DeviceConnectionState.connecting) {}
if (update.connectionState == DeviceConnectionState.connected) {
_isConnecting = false;
print("Recalling the updated lists after connecting a device");
_scanForAvaileableBluetoothDevices();
_listConnectedBluetoothDevices();
}
if (update.connectionState == DeviceConnectionState.disconnected) {
_isConnecting = false;
print("DeviceConnectionState.disconnected");
print("Recalling the updated lists after disconnecting a device");
_scanForAvaileableBluetoothDevices();
_listConnectedBluetoothDevices();
}
},
onError: (Object error) => print("Connecting to device resulted in error $error"),
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xfff5f5f5),
appBar: AppBar(
elevation: 0,
centerTitle: false,
automaticallyImplyLeading: false,
backgroundColor: const Color(0xffffffff),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
title: const Text(
"AppBar",
style: TextStyle(
fontWeight: FontWeight.w400,
fontStyle: FontStyle.normal,
fontSize: 14,
color: Color(0xff000000),
),
),
actions: [
TextButton(
onPressed: () async {
_getPermissions();
_chechkIfBluetoothIsOn();
_checkIfLocationIsOn();

if (await _chechkIfBluetoothIsOn() &&
await _checkIfLocationIsOn()) {
_scanForAvaileableBluetoothDevices();
} else {
print(
"Blueooth ve Location özellikleri açık olmadığından tarama yapılamadı.");
}
},
child: const Text('Scan Devices'),
),
TextButton(
onPressed: () async {
_getPermissions();
_chechkIfBluetoothIsOn();
_checkIfLocationIsOn();

print("Connected Device Count: ${_connectedBluetoothDeviceList.length}");

if (await _chechkIfBluetoothIsOn() &&
await _checkIfLocationIsOn()) {
_listConnectedBluetoothDevices();
} else {
print(
"Blueooth ve Location özellikleri açık olmadığından liste yenilenemedi.");
}
},
child: const Text('List Connected Devices'),
)
],
),
body: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: ListView.builder(
itemCount: _discoveredDeviceList.length,
itemBuilder: (BuildContext context, int index) {
final scanned_Device = _discoveredDeviceList[index];
return Column(
children: [
ListTile(
title: Text(scanned_Device.name),
subtitle: Text(scanned_Device.id),
trailing: ElevatedButton(
onPressed: _isConnecting
? null
: () {
_connectToTheSelectedDevice(context, scanned_Device);
print(_connectedBluetoothDeviceList.length);
},
child: const Text("Bağlan"),
style: ElevatedButton.styleFrom(
backgroundColor: _isConnecting
? Colors.red
: Colors.green,
foregroundColor: Colors.white,
),
),
tileColor: Colors.lightBlue,
textColor: Colors.white,
),
],
);
},
),
),
Expanded(
child: ListView.builder(
itemCount: _connectedBluetoothDeviceList.length,
itemBuilder: (BuildContext context, int index) {
final connected_Device = _connectedBluetoothDeviceList[index];
return Column(
children: [
ListTile(
title: Text(connected_Device.name),
subtitle: Text("${connected_Device.id}"),
trailing: ElevatedButton (
onPressed: () async {
_ble_Connection.cancel();
//connected_Device.disconnect();
print("connected_Device.disconnect() Ran");
await Future.delayed(const Duration(milliseconds: 100));
_listConnectedBluetoothDevices();
print("_listConnectedBluetoothDevices() Ran");
},
child: const Text("Unut"),
),
),
],
);
},
),
)
],
),
);
}

@override
void dispose() {
super.dispose();
}
}

pubspec.yaml dosyası:

name: ...
description: "..."

publish_to: 'none' # Remove this line if you wish to publish to pub.dev

version: 1.0.0+1

environment:
sdk: '>=3.3.0 <4.0.0'

dependencies:
flutter:
sdk: flutter

open_settings:

cupertino_icons: ^1.0.6
flutter_svg: ^2.0.10+1
flutter_reactive_ble: ^5.3.1
permission_handler: ^11.2.0
flutter_joystick: ^0.0.4
flutter_blue: ^0.8.0
get:

dev_dependencies:
flutter_test:
sdk: flutter

flutter_lints: ^3.0.0

flutter:

uses-material-design: true

android/app/src/main/AndroidManifest.xml dosyasında <manifest> ve <application> başlıkları arasına altta verilen şekilde <usus-permission …> satırlarını ekleyin:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
tools:remove="android:usesPermissionFlags"/>

<application ...>

android/app/src/build.gradle dosyasında android { … defaultConfig { … bloğunun içinde minSdkVersion’u 21 olarak değiştirin:

android {
namespace "com.beyhude.flutterandroidstudio"
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.beyhude.flutterandroidstudio"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}

Bir sonraki yazıda Bluetooth cihazlarının çalışma mantığına, kod anlatımlarına ve rastlanılan hatalara değinilecektir, eğer yukarıdaki kodlar direkt olarak çalışmazsa:
https://pub.dev/packages/flutter_blue,
https://pub.dev/packages/flutter_reactive_ble,
https://pub.dev/packages/permission_handler/install ve
https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration
sayfalarından yararlanabilirsiniz. İyi günler.

Flutter Kolay Arayüz Tasarımları

Uygulamamız için arayüz tasarımlarını hızlı ve kolay bir şekilde yapabileceğimiz FlutterViz ve FlutterFlow siteleri üzerinde ayrıca bu siteler yeni uygulamalar yaparken hazır, örnek uygulamalarda bulundurmaktadır.

FlutterViz yeni uygulama oluşturma arayüzü
FlutterFlow yeni uygulama oluşturma arayüzü

FlutterFlow üzerinden yapılan bir uygulamada ekranın sol tarafında bulunan “Build” başlığı altında “Widget Palette” sekmesinde kullanılabilecek widget’lar,

FlutterFlow Widget Palette

“Widget Tree” sekmesi altında sayfalar ve bu sayfalardaki widget hiyerarşisi,

FlutterFlow Widget Tree

“Storyboard” sekmesi altında da sayfalar arasındaki Navigation ilişkileri sergilenir.

FlutterFlow Storyboard

Bu sitede hazırladığınız projeyi indirebilmek için üyelik planını yükseltmeniz gerekmektedir. Ücretli üyelik yapılmak istenmediği durumda bu sitenin sadece “Build” başlığından yararlanılabilir. Uygulamanın arayüzü ve widget hiyerarşisi bu sistede tasarlanabilir ve sonrasında kendi uygulamanızda kullanabilirsiniz.

FlutterViz ise daha kısıtlı fonksiyonellik sunmaktadır ama tasarladığınız projeyi ücretsiz bir şekilde indirebilirsiniz veya isterseniz sadece arayüzleri direk olarak projenizde kullanabilirsiniz.

Uygulama Arayüzünden Debug Yazısını Kaldırmak

Yeni bir uygulama oluşturulduğunda veya varolan uygulamanızda sağ üst köşede yer alan DEBUG yazısını kaldırmak için page’in arayüzünün MaterialApp widget’ının içine

void main() async {
// redis connection
// final redis_Connection = await RedisConnection().connect('localhost', 6379);

runApp(
MaterialApp(
//debug yazısını kaldırmak için
debugShowCheckedModeBanner: false,
home: MyApp(), // app's root widget
),
);
}

ve uygulamanın main fonksiyonunun içerisinde bulunan MaterialApp methodunun içerisine debugShowChechedModeBanner = false; satırı eklenir.

@override
Widget build(BuildContext context) {

return MaterialApp(
debugShowCheckedModeBanner: false,

title: 'Flutter Android Studio',
theme: ThemeData(

colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}

Uygulama koşulurken ekran görüntüsü almak için terminalden flutter screenshot komutu koşulur, alınan ekran görüntüsü cwd(current working directory) yani projenin koştuğu dosya dizini altına default olarak flutter_01, flutter_02 şeklinde kaydedilir.