なりたさまかく語りき

結局好きなこと書くのがいちばんよい。

【Xamarin.Forms】MVVMっぽくBluetoothを実装する(その1:ペアリング済みデバイス一覧を取得する)

タイトル通りです。Xamarin.FormsでBluetoothします。

サマリ

  • Xamarin.Formsでペアリング済みデバイスの一覧を取得してListViewに表示した。
  • MVVM(っぽく)実装した。
  • Androidだけ(iOSは無し/UWPはそもそもやらない)。
  • Fody使った!
  • 勉強になった!

やること

Xamarin.FormsのAndroidBluetoothします。 といってもスマホ内のペアリング済みデバイス一覧を取得して情報表示するだけで、通信はしてないですが。

モチベーション

Xamarin.Forms勉強してるけどクラシックBluetoothの実装に関してXamarin.AndroidだったりMVVMっぽく実装してるものがあんまりないっぽかった(コードビハインドにベタ書きしてたり、かのペゾルド本にもVMにロジック書くことを許容するような記述がある)ので自分で作ってみた。 MVVMは知ったかすると宗教戦争の引き金になりそうなので、とりあえずUIとロジックを分離したとかそんな程度です。

ソースコードgithubにおいてます。 GitHub - johta/XF-BT

動作

ペアリング済みのデバイスとその情報を表示します。 f:id:naritasamadayo:20180909110642p:plain

構成

f:id:naritasamadayo:20180909105211p:plain MVVMって単なるフォルダ分けのことではないと思いますが一応Views、ViewModels、Models、Itemsに入れてます。

あと、Fody使うのでFodyWeavers.xmlを作成してプロジェクトに追加してます。 FodyはPropertyChangedにかかわる冗長な記述をシンプルにまとめられるNugetパッケージという認識です。 以下の記事を参考にしつつ、いろいろネットの記事を漁って使い方覚えました。 qiita.com

View

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Views.BTTest"
             x:Class="BTTest.MainPage">

    <StackLayout>
        <Label Text="{Binding lbl1}"/>
        <ListView x:Name="listView1" ItemsSource="{Binding ListDevices}" HasUnevenRows="False" RowHeight="80">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout>
                            <Label Text="{Binding Name,StringFormat='名前: {0:T}'}"/>
                            <Label Text="{Binding UUID,StringFormat='UUID: {0:T}'}"/>
                            <Label Text="{Binding Address,StringFormat='MACアドレス: {0:T}'}"/>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>

</ContentPage>

特に不思議なことは何もしてないけど、ListViewはデフォルトだとLabelを3行以上表示できない(高さが足りない)ので高さを調節するRowHeightを設定してます。

using BTTest.ViewModels;
using Xamarin.Forms;

namespace BTTest
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            BindingContext = new MainPageViewModel();
        }
    }
}

本当はコードビハインドには何も書かないほうがいいんだろうなって思いつつ、BindingContextの設定だけ記述してます。

ViewModel

using BTTest.Items;
using BTTest.Models;
using PropertyChanged;
using System.Collections.ObjectModel;
using Xamarin.Forms;


namespace BTTest.ViewModels
{
    [AddINotifyPropertyChangedInterface]
    class MainPageViewModel
    {
        public ObservableCollection<BTDevice> ListDevices { get; set; }
        public BTDevice Device { get; set; }
        public string lbl1 { get; set; }

        public MainPageViewModel()
        {
            lbl1 = "Bluetoothデバイス一覧";
            Initialize();
        }

        public void Initialize()
        {
            ListDevices = new ObservableCollection<BTDevice>();

            IBluetoothManager btMan = DependencyService.Get<IBluetoothManager>();
            var listBTDevices = btMan.GetBondedDevices();

            if(listBTDevices != null &&  listBTDevices.Count > 0 )
            {
                foreach (var device in listBTDevices)
                {
                    Device = new BTDevice
                    {
                        Name = device.Name,
                        UUID = device.UUID,
                        Address = device.Address
                    };
                    ListDevices.Add(Device);
                }
            }
            else
            {
                lbl1 = "Bluetoothデバイスがありません。";
            }
        }
    }
}

DependencyServiceしてAndroidの固有機能を呼び出して、ペアリング済みデバイスのリストを取得してる。 GetBondedDeviceについてはModelのところで解説。

あと一応、ペアリング済みデバイスがなかったら何もありませんでしたって表示するくらいのif文は実装してる。

Model

using BTTest.Items;
using System;
using System.Collections.Generic;
using System.Text;

namespace BTTest.Models
{
    public interface IBluetoothManager
    {
        List<BTDevice> GetBondedDevices();
    }
}

共通プロジェクト(PCLって言うのか?)にはデバイス固有機能の実装を呼び出すためのインターフェースを置く。GetBondedDevice()って書くだけ。

using Android.Bluetooth;
using BTTest.Items;
using BTTest.Models;
using System.Collections.Generic;
using System.Linq;
using Xamarin.Forms;

[assembly: Dependency(typeof(BTTest.Droid.BluetoothManager))]

namespace BTTest.Droid
{
    public class BluetoothManager:IBluetoothManager
    {
        private List<BTDevice> result = new List<BTDevice>();
        public List<BTDevice> GetBondedDevices()
        {
            BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter; //アダプター作成
            var listBondedDevices = adapter.BondedDevices;              //ペアリング済みデバイスの取得

            if(listBondedDevices != null && listBondedDevices.Count>0)
            {
                foreach (var device in listBondedDevices)
                {
                    var btDevice = new BTDevice();
                    btDevice.Name = device.Name;
                    btDevice.UUID = device.GetUuids().FirstOrDefault().ToString();
                    btDevice.Address = device.Address;
                    result.Add(btDevice);
                }
            }
            return result;
        }

    }
}

Androidプロジェクトの方ではBluetoothAPIでペアリング済みデバイスのリストを返すGetBondedDeviceを実装 アダプター作ってペアリング済みデバイスを取得して返してるだけ。

Item

using System;
using System.Collections.Generic;
using System.Text;

namespace BTTest.Items
{
    public class BTDevice
    {
        public string Name { get; set; }
        public string UUID { get; set; }
        public string Address { get; set; }
    }
}

特に言うことはないが、{ get; set; }の記述を忘れてVMでプロパティーが更新されず、1時間ぐらいwaste of timeしたのは初心者丸出しで恥ずかしすぎて反省。

マニフェストの設定

AndroidBluetoothするにはマニフェストの設定が必要です。BluetoothBluetooth_adminにチェックを入れる。

以上だ!

あとは動かせばわかるんじゃないかな。 ちなみにX2Tはイヤホン、8BitDoはゲームコントローラ

次回はこいつを拡張して実際に接続して値を取得したりしたいなと。