Xamarin.Forms入門中~Androidホームスクリーンウィジェットの実装~

Posted by 技術ブログ by Strawhat.net on Saturday, December 24, 2016

TOC

これは、[学生さん・初心者さん大歓迎!]Xamarin Advent Calendar 2016の24日目の記事です。

先日は Xamarin.Forms上でHello World - SDD(Sleep-Driven Development)… でした。

Windows 10 UWPアプリのライブタイルをXamarin.FormsのAndroidアプリ側で実装する案をまとめてみます。これまでに自力で調べられた範囲での案なので、これ以外にもっとよい方法があるかもしれませんし、既知の方法かもしれませんがその点はご了承ください。

お詫び

記事を書いてる途中で、Visual Studio 2015でサンプルコードのソリューションを開けなくなり、さらに新しいソリューションも作成が終わらないトラブルに見舞われています。そのため、サンプルコードとその実行結果をおみせできない状況です。トラブルが直ったら、サンプルコードをGitHubに上げて、実行結果のスクリーンショットを掲載したいと思います。

また、記事中のコードの断片もビルドが通ってないので、誤りがあるかもしれません。これも後日直したいと思います。

Xamarin.Formsを窓から放り投げたい気分ですが、気を取り直して記事を進めます。

背景

これまで、KumalicaなどWindows 10 MobileのUWPアプリを作ってきましたが、Android版も同時にリリースしてもっと多くの人に使ってほしいと思い、Xamarin.Formsへの移行を進めています。そこで、Xamarin.Formsの機能、特にUWPアプリで使った機能を置き換えるための方法を調べて、記事にまとめています。

前回の記事、Xamarin.Forms入門中~UWPアプリのライブタイルの置き換え①~では、Xamarin.Androidでのウィジェットのサンプルコードを調べた結果を書いてます。

Xamarin.Forms入門中~UWPアプリのライブタイルの置き換え①~

この時点では、Xamarin.Formsの場合の構成方法が見えてなかったのと、UWPアプリのライブタイルのようにアプリ側からウィジェットを更新する方針が見えてなかったので、さらに調べてみました。

Xamarin.FormsでのAndroidホームスクリーンウィジェットの実装方法

Xamarin.FormsアプリのAndroidプロジェクトで、ホームスクリーンのウィジェットを実装する方法ですが、VSソリューションの構成を見せられないので、概要だけ書くと、

  1. Xamarin.FormsのXamarin.AndroidプロジェクトにResources/xml/widget.xmlファイルを作成して、appwidget-provider要素でウィジェットを定義する。レイアウトの定義方法は、Androidでの一般的な方法なので公式資料かXamarin.AndroidのSimpleWidgetサンプルを参考にしてください。

  2. AppWidgetProviderを実装したクラスを作成して、BroadcastReceiver・IntentFilter・MetaDataの属性を設定する。ここもXamarin.AndroidのSimpleWidgetサンプルと同じです。

[BroadcastReceiver (Label = "@string/widget_name")]
[IntentFilter(new [] { AppWidgetManager.ActionAppwidgetUpdate, })]
[MetaData("android.appwidget.provider", Resource = "@xml/widget")]
public class WordWidget : AppWidgetProvider {
(略)

と、Xamarin.AndroidのSimpleWidgetサンプルで実装されていたコードを、Xamarin.FormsのDroidプロジェクトに持ってきただけです。

(後日、ソリューションのファイル構成を載せます)

このDroidプロジェクトをビルドして配置すると、アプリ本体がインストールされるとともに、ウィジェットもインストールされて画面に配置できます。

(後日、スクリーンショットを載せます)

アプリからホームスクリーンウィジェットの更新

アプリからホームスクリーンウィジェットを更新する方針として、以下の2案を考えていました。

  1. アプリからインテントをブロードキャストして、BroadcastReceiverを実装したクラスがそれを受信してウィジェットを更新する。

  2. ローカルデータベースか何かに更新情報を記録して、ウィジェットから参照する(更新を知る手段は未検討)

まず案1で実装したところ、思ったように動作しているので、これで進めようと思います。実装内容の概要は以下の通りです。

  • DroidプロジェクトにBroadcastReceiverを実装したクラスMyBroadcastReceiver を作成する。カスタムインテントに対して、ウィジェットの更新メソッドを呼び出す。
    [BroadcastReceiver]
    [IntentFilter(new[] { MyBroadcastReceiver.CUSTOM_INTENT })]
    public class MyBroadcastReceiver : BroadcastReceiver
    {
        public const string CUSTOM_INTENT = "homescreenwidgetsample1.droid.widget.update";

        public override void OnReceive(Context context, Intent intent)
        {
            if (intent.Action == CUSTOM_INTENT)
            {
                string text = intent.GetStringExtra("text");
                SampleWidget.Update(context, text);
            }
        }
    }
  • MyBroadcastReceiverから呼び出されるウィジェット更新のメソッドを作成する。このメソッドはウィジェットのOnUpdateメソッドからも呼ばれます。
        public static void Update(Context context, string text)
        {
            RemoteViews updateViews = new RemoteViews(context.PackageName, Resource.Layout.widget_word);
            updateViews.SetTextViewText(Resource.Id.blog_title, text);

            ComponentName thisWidget = new ComponentName(
                context.PackageName, Java.Lang.Class.FromType(typeof(SampleWidget)).Name);
            AppWidgetManager manager = AppWidgetManager.GetInstance(context);
            manager.UpdateAppWidget(thisWidget, updateViews);
        }
  • ウィジェット更新のインターフェースILiveTileServiceをPCLプロジェクトに作成する。
    public interface ILiveTileService
    {
        void Update(string text);
    }
  • ILiveTileServiceの実装クラスLiveTileServiceをDroidプロジェクトに作成する。更新の実装では、更新データを載せたExtraを設定したカスタムインテントをブロードキャストします。
    public class LiveTileService : ILiveTileService
    {
        public void Update(string text)
        {
            Intent updateIntent = new Intent();
            updateIntent.SetAction(Widgets.MyBroadcastReceiver.CUSTOM_INTENT);
            updateIntent.PutExtra("text", text);
            Xamarin.Forms.Forms.Context.SendBroadcast(updateIntent);
            return;
        }
    }
  • IPlatformInitializerのAndroidの実装クラス(MainActivity.csファイル)で同インターフェースに対する同実装クラスのインジェクションを登録する。
    public class AndroidInitializer : IPlatformInitializer
    {
        public void RegisterTypes(IUnityContainer container)
        {
            container.RegisterType(typeof(ILiveTileService), typeof(LiveTileService));
        }
    }
  • PCLプロジェクトのViewModelのコンストラクタで、ILiveTileServiceを実装したインスタンスを受け取り、ウィジェットの更新時にそのメソッドを呼び出す。
public class MainPageViewModel : BindableBase, INavigationAware
    {
        private ILiveTileService liveTileService;

        public DelegateCommand UpdateButtonClickedCommand { get; private set; }

        public MainPageViewModel(ILiveTileService liveTileService)
        {
            this.liveTileService = liveTileService;
            this.UpdateButtonClickedCommand = new DelegateCommand(() => UpdateButtonClicked());
        }

        private void UpdateButtonClicked()
        {
            this.liveTileService.Update(DateTime.Now.ToString());
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
        }
    }

(後日、ソリューションのファイル構成を載せます)

ソリューションのビルド・配置の後、ウィジェットをホームスクリーンに置いてからアプリを起動してボタンを押すと、ウィジェットが更新されています。

(後日、スクリーンショットを載せます)

まとめ

Windows 10 UWPアプリのライブタイルを、Xamarin.FormsのDroidプロジェクトでもホームスクリーンウィジェットを使って同様の機能を実装できることが分かりました。

なお、Xamarin.FormsのUWPアプリ側でも、Android側と同様にIPlatformInitializerの実装クラスを作成して、 TileUpdateManagerを使ってライブタイルを更新できます。今後は、この方法によるUWP版とAndroid版でライブタイルの共通化を実装していきたいと思います。

明日はfumiya-kumeさんです。よろしくお願いします。