If you read my previous blog post how to create Bluetooth GATT server on top of UWP, then you should be familiar with basics of Bluetooth. In this post I will extend on other side of the “wire” of the Bluetooth communication. I will do this with Xamarin.Forms.
In this post I will create simple (just plain vanilla C# code, no MVVM, plain “code-behind” C# code) mobile app which will communicate with GATT server over Bluetooth LE. Plain code-behind approach is used because I just wanted to focus on the code not the architecture, nor good practices and patterns.
Xamarin.Forms GATT client.
First, I created empty Xamarin.Forms project. Next, I put some NuGet dependencies on all three project: shared library, Android and iOS project. I put these dependencies:
Project and dependencies.
- Plugin.BLE:
- Xamarin.Forms Bluetooth plugin.
- Plugin.Permissions:
- Permission library – handles “Permission” on mobile apps.
- Xamarin.Essentials:
- Essential tools/extensions for Xamarin.Forms.
- Xamarin.Forms.Visual.Material:
- Material design for Xamarin.Forms – handles look&feel.
Application flow, pages and corresponding code.
The application flow is simple, straightforward: user opens the app, he or she is presented with scan button, which scans for nearby Bluetooth devices. When clicked on found device, it will be connected to and navigated to next page where user can exchange data with the device.
This Page’s XAML is very simple::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Visual="Material" Title="Search for BLE devices" x:Class="Jenx.Bluetooth.Xamarin.Client.MainPage"> <StackLayout Orientation="Vertical" > <Button x:Name="ScanButton" Text="Scan" Clicked="ScanButton_Clicked" Margin="10" /> <ListView x:Name="listView" ItemSelected="ListView_ItemSelected" /> </StackLayout> </ContentPage> |
Code-behind C# code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
[DesignTimeVisible(false)] public partial class MainPage : ContentPage { private readonly IAdapter _adapter; private List<IDevice> _gattDevices = new List<IDevice>(); public MainPage() { InitializeComponent(); _adapter = CrossBluetoothLE.Current.Adapter; _adapter.DeviceDiscovered += (s, a) => { _gattDevices.Add(a.Device); }; } private async void ScanButton_Clicked(object sender, EventArgs e) { ScanButton.IsEnabled = false; var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Location); if (status != PermissionStatus.Granted) { if (await CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Permission.Location)) { await DisplayAlert("Need location", "App needs location permission", "OK"); } var status1 = await CrossPermissions.Current.RequestPermissionsAsync(new[] { Permission.Location }); var loca = status1.FirstOrDefault(x => x.Key == Permission.Location); if (loca.Value != null) if (loca.Value == PermissionStatus.Granted) status = PermissionStatus.Granted; } if (status != PermissionStatus.Granted) { await DisplayAlert("Need location", "App need location permission", "OK"); return; } _gattDevices.Clear(); await _adapter.StartScanningForDevicesAsync(); listView.ItemsSource = _gattDevices.ToArray(); ScanButton.IsEnabled = true; } private async void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e) { IDevice selectedItem = e.SelectedItem as IDevice; if (selectedItem.State == Plugin.BLE.Abstractions.DeviceState.Connected) await Navigation.PushAsync(new BluetoothDataPage(selectedItem)); else { try { await _adapter.ConnectToDeviceAsync(selectedItem); await Navigation.PushAsync(new BluetoothDataPage(selectedItem)); } catch (DeviceConnectionException ex) { // ... could not connect to device } } } } |
The page for exchanging data with GATT device is presented in the next section.
XAML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="Talking to device via BLE" Visual="Material" x:Class="Jenx.Bluetooth.Xamarin.Client.BluetoothDataPage"> <ContentPage.Content> <StackLayout Orientation="Vertical" > <Button Text="Get manufacturer data from device" Margin="10" Clicked="GetManufacturerDataButton_Clicked" /> <Label x:Name="output" /> <Button Text="Send message to BLE GATT server" Margin="10" Clicked="WriteDataButton_Clicked" /> <Entry x:Name="writedata" /> </StackLayout> </ContentPage.Content> </ContentPage> |
C# code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
[XamlCompilation(XamlCompilationOptions.Compile)] public partial class BluetoothDataPage : ContentPage { private readonly IDevice _connectedDevice; public BluetoothDataPage(IDevice connectedDevice) { InitializeComponent(); _connectedDevice = connectedDevice; } private async void GetManufacturerDataButton_Clicked(object sender, EventArgs e) { try { var service = await _connectedDevice.GetServiceAsync(GattCharacteristicIdentifiers.ServiceId); if (service != null) { var characteristic = await service.GetCharacteristicAsync(GattCharacteristicIdentifiers.ManufacturerName); if (characteristic != null) { var bytes = await characteristic.ReadAsync(); var str = Encoding.UTF8.GetString(bytes); output.Text = str; } } } catch { } } private async void WriteDataButton_Clicked(object sender, EventArgs e) { try { var service = await _connectedDevice.GetServiceAsync(GattCharacteristicIdentifiers.ServiceId); if (service != null) { var characteristic = await service.GetCharacteristicAsync(GattCharacteristicIdentifiers.DataExchange); if (characteristic != null) { byte[] senddata = Encoding.UTF8.GetBytes(string.IsNullOrEmpty(writedata.Text) ? "jenx.si was here" : writedata.Text); var bytes = await characteristic.WriteAsync(senddata); } } } catch { } } } |
GATT server/device:
Next picture represent GATT server response when data is exchanged via Bluetooth.
Conclusion.
In this blog post you can find all details how to create GATT client with Xamarin.Forms. The example is presented in minimalistic way. In production, this code should be wrapped inside some services and (D)injected into ViewModels and data bonded to Views.
And yes, you can download code for this experiment here: https://github.com/josipx/Jenx.Bluetooth.GattServer
8 thoughts on “Bluetooth GATT Xamarin.Forms client.”
This project is not opened in github
@SriShalu, It should work now.
J
hello, do you have project posted in github, because shows 404, can you upload again please 🙂 best regards..
Hi, repository is now public. Forgot to hit Public button 🙁
Sorry about that.
Cheers
Joze
var service = await _connectedDevice.GetServiceAsync(GattCharacteristicIdentifiers.ServiceId);
Above line is returning null. So it is getting stuck. What to do .?Can you please help me..
Shailesh, did you manually check if GATT service is really running on that ServiceId and you are connected to the device?
Please, how to connect to a ble device that uses a whitelist? It is not possible via the bluetooth address, it is changing.
GetCharacteristicsAsync() always returns byte[0]. Please let me know how to read the characteristics of BLE device