アプリ開発日記 #30 ついに、Xamarin.Formsでテキストボックスのあるダイアログの表示に成功

今日の目標

Xamarin.Formsでテキスト入力ができるダイアログを表示する。

実際にやったこと

昨日の時点では、ダイアログを表示しようとした場合に、異常終了でアプリが落ちてしまっていましたが、今日、やっとその原因が判明し、ダイアログの表示に成功しました。

そして、ダイアログ表示時にアプリが異常終了していた原因は、ダイアログ表示の際に引数として渡していたContextが原因でした。

実際のロジック

実際に入力テキストボックス付きのダイアログを表示させたロジックです。

実装したロジックは、以下の3つのパートから作成されます。

1.AndroidiOSの共通プロジェクトへのInterfaceの実装
using System.Threading.Tasks;

namespace DoXXXXX.View.CustomDialog
{
    /// <summary>
    /// テキスト入力ができるダイアログを表示するためのインターフェース
    /// </summary>
    public interface IEntryAlertService
    {
        Task<DialogResult> Show(string title, string message, string entryText, string accepte, string cancel);
    }
}
namespace DoXXXXX.View.CustomDialog
{
 
    /// <summary>
    /// ダイアログの戻り値を格納するクラス
    /// </summary>
   public class DialogResult
    {
        public string PressedButtonTitle { get; set; }

        public string Text { get; set; }
    }
}
2.Xamarin.Androidプロジェクトへのクラスの追加

Android用のダイアログを表示するためのロジックです。
Xamarin.Androidプロジェクト側では、1で追加したInterfaceを実装したクラスを作成します。
そして、Dependencyサービスをアトリビュートすることで、共通処理からDependencyサービス経由でAndroidのダイアログを呼び出すことが可能です。

なお、[assembly: Dependency(typeof(EntryAlertService))]はnamespaceの外側に記述する必要があります。
そのため、Xamarin.Androidプロジェクトで作成したダイアログの名前空間をusingする必要があります。

[assembly: Dependency(typeof(EntryAlertService))]
namespace DoXXXXX.Droid
{
    public class EntryAlertService : IEntryAlertService
    {
        public Task<DialogResult> Show(string title, string message, string entryText, string accepte, string cancel)
        {
            var tcs = new TaskCompletionSource<DialogResult>();

            var edtSwotItem = new EditText(MainActivityContext.MainViewContext);

            edtSwotItem.InputType = global::Android.Text.InputTypes.ClassText;
            edtSwotItem.Text = entryText;
            edtSwotItem.SetWidth(200);

            // ダイアログの作成
            var dialogBuilder = new AlertDialog.Builder(MainActivityContext.MainViewContext);

            // ダイアログのタイトル、メッセージ、テキストボックスの設定
            dialogBuilder.SetTitle(title);
            dialogBuilder.SetMessage(message);
            dialogBuilder.SetView(edtSwotItem);

            // ダイアログボタンの設定
            dialogBuilder.SetNegativeButton(cancel,(obj,e) => tcs.SetResult(
                new DialogResult()
                {
                    PressedButtonTitle = cancel,
                    Text = edtSwotItem.Text
                }));

            dialogBuilder.SetPositiveButton(accepte, (obj, e) => tcs.SetResult(
                 new DialogResult()
                 {
                     PressedButtonTitle = accepte,
                     Text = edtSwotItem.Text
                 }));

            dialogBuilder.Show();

            return tcs.Task;
        }
    }
}

そして、今回ダイアログが表示できなかった原因は、ダイアログ作成時に渡していた引数(上記ソースコードではMainActivityContext.MainViewContextを渡している箇所)にAndroid.App.Application.Contextを渡していたためでした。

Androidのダイアログを表示するために必要となるコンテキストは、画面Activityのコンテキストである必要があります。
しかし、Android.App.Application.Contextは画面Activityのコンテキストではないようです。
そして、Forms.Contextは、新しいバージョンでは使用することができません。

そのため、多少強引ではありますが、staticクラスを作成し、Xamarin.AndroidプロジェクトのMainActivityのOnCreateの中で、MainActivityをstaticクラスのプロパティーに設定することで、Android用のダイアログを表示するくらすでもしようできるようにしています。
(ちなみに、MainActivityContext.MainViewContextがMainActivityを設定したstaticクラスのプロパティーです)

3.Xamarin.iOSプロジェクトへのクラスの追加

iOS用のダイアログを表示するためのロジックです。
iOS版も、基本はAndroid版と同じになります。

なお、iOS版でもAlertViewが非推奨となっておりましたので、UIAlertControllerを使用するようにしています。
iOS版は動作の確認を行っていません。

[assembly:Dependency(typeof(EntryAlertService))]
namespace DoXXXXX.iOS
{
    public class EntryAlertService : IEntryAlertService
    {
        public Task<DialogResult> Show(string title, string message, string entryText, string accepte, string cancel)
        {
            var tcs = new TaskCompletionSource<DialogResult>();

            UIApplication.SharedApplication.InvokeOnMainThread(
                () =>
                {
                    // ダイアログの作成
                    var dialog = new UIAlertController();

                    dialog.Title = title;
                    dialog.Message = message;

                    dialog.AddTextField(
                        (textField) =>
                        {
                            textField.Text = entryText;
                        });

                    // ダイアログボタンのイベントの登録
                    var cancelAction = UIAlertAction.Create(cancel, UIAlertActionStyle.Cancel, (action) => tcs.SetResult(
                        new DialogResult()
                        {
                            PressedButtonTitle = cancel,
                            Text = dialog.TextFields[0].Text
                        }));

                    dialog.AddAction(cancelAction);

                    var acceptAction = UIAlertAction.Create(accepte, UIAlertActionStyle.Cancel, (action) => tcs.SetResult(
                        new DialogResult()
                        {
                            PressedButtonTitle = accepte,
                            Text = dialog.TextFields[0].Text
                        }));

                    dialog.AddAction(acceptAction);

                    // ダイアログの表示
                    dialog.PresentViewController(dialog.PresentedViewController, true, null);
                });

            return tcs.Task;
        }
    }
}

明日への思い

ダイアログで入力された値で、データを更新できるようにする。
更新した情報で画面を再描画できるようにする。
入力エラーがあった場合は、入力ダイアログを再表示できるようにする。

過去に作成したアプリ

①概算家計簿
家計簿を毎日つけれれないあなたのための家計簿アプリです。
f:id:b-kimagure:20190722205238p:plain:w120
Google Play で手に入れよう

②ToDoボタン
忙しくてついつい放置してしまう家事
そんな家事の最後にやった日を記録するアプリです。
Google Play で手に入れよう

読みたい本

人月の神話【新装版】

人月の神話【新装版】

「ひとり情シス」虎の巻

「ひとり情シス」虎の巻

プログラムをもっときれいに書けるようにするために

オブジェクト指向における再利用のためのデザインパターン

オブジェクト指向における再利用のためのデザインパターン

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)