trapemiyaの日記

hatenablogが新しくなったんで新規一転また2019年1月からちょこちょこ書いてます。C#中心のプログラミングに関するお話です。

音声ファイルを再生する。

Xamarinによるandroidのアプリで音声ファイルを鳴らすのは非常に簡単です。以下を読んでいただければ説明するまでもないのですが、

Play Audio
http://developer.xamarin.com/recipes/android/media/audio/play_audio/

一応説明しておくと、

1.Resourcesの中にrawという名前のフォルダを作成する。
2.そのrawフォルダの中に音声ファイルを入れる。
3.以下のメソッドを実行する。簡単に試したい場合は、このメソッドをViewのOnCreateイベントハンドラで実行して下さい。MvvmCrossを導入済みですと、ViewsフォルダのFirstView.csのOnCreateになります。

MediaPlayer.Create(this, Resource.Raw.c9).Start();

#MediaPlayerは、Android.Media名前空間にあります。

といたって簡単です。
実は今回は音声ファイルを再生することをご紹介することがメインではなくて、このメソッドをViewModelからどのように実行するのかをご紹介することの前振りになります。
音声ファイルの再生はandroidiOSなどのプラットフォーム毎に違いますから、ViewModelではなく、各プラットフォームに依存したViewに書きたいわけです。
というわけで、次回は、Viewに書かれたメソッドをViewModelから実行する方法を考察します。

Xamarin + MvvmCrossをVisual Studio 2013で開発する最初の手順

表記の件について、トータルでまとまっている記事を見かけなかったので、自分用のメモも兼ねてまとめておきます。
とりあえず、ターゲットはandroidのみとして話を進めます。


1.Visual Studio 2013(以下、VS2013)のインストール(当たり前!)


2.Xamarinのインストール(当たり前!)


3.PCLプロジェクトの作成

いよいよVisual Studio 2013でプロジェクトと作っていきます。
最初はPCLからにしましょう。
プロジェクトのテンプレートは「クラス ライブラリ(ポータブル)」を選択します。PCLは慣習でプロジェクト名の最後に「.Core」を付けるようですので、そのようにします。
また、気を付けたいのは、このようにするとVS2013は自動的にソリューション名をプロジェクト名と同じにしますので、ソリューション名まで「.Core」が付いてしまいます。
それが嫌なので、ソリューション名は手で編集します。ここはうっかりしますので、注意ポイントです。

#ちなみに、テンプレートで「クラス ライブラリ(ユニバーサル アプリ用ポータブル)」を選択しても良いですが、その場合はプロジェクトのプロパティを開き、ターゲットに、Xamarin.Android を追加する必要があります。

PCLのプロジェクトを作成途中で、以下の画面のようにターゲットを選択する画面が表示されますが、Xamarin.Androidにデフォルトでチェックが入っていますので、そのままOKボタンを押して閉じます。

#ちなみに、テンプレートで「クラス ライブラリ(ユニバーサル アプリ用ポータブル)」を選択した場合、上記のターゲットを選択する画面は表示されません。表示されませんが、上記にも書いた通り、プロジェクトのプロパティから変更できます。


4.PCLプロジェクトにMvvmCrossのインストール

ソリューションエクスプローラーで、「参照設定」を右クリックし、「NuGetパッケージの管理」をクリックします。


「NuGetパッケージの管理」の画面が開いたら、右上の検索ボックスに「MvvmCross」と入力して検索し、検索結果から「MvvmCross - Hot Tuna Starter Pack」を選択してインストールします。


インストール後、ViewModel等のフォルダが作成されます。


5.androidプロジェクトの作成

続いて、androidのプロジェクトを作成します。
プロジェクトのテンプレートは、「Visual C#」にある「Android」の中の「Blank App (Android)」を選択します。
プロジェクト名はandroidとわかりやすいように、XamarinMvvmCross.androidとしています。


6.androidプロジェクトにMvvmCrossのインストール

PCLのプロジェクトと全く同様にして、MvvmCrossをインストールします。


7.androidプロジェクトにPCLプロジェクトの参照を追加

ソリューションエクスプローラーの「参照の設定」からPCLプロジェクトの参照設定をします。


8.androidプロジェクトをスタートアッププロジェクトに設定

当然ですが、PCLプロジェクトの起動はできませんので、androidプロジェクトを起動するようにします。


9.不必要なアクティビティを削除

Xamarinのテンプレート(androidプロジェクトを作成したテンプレート)から生成された初期画面のアクティビティと、MvvmCrossをインストールした際に追加された初期画面のアクティビティの2つがあり、このままコンパイルして実行すると、android端末のランチャにこれら2つのアイコンが登録されてしまいます。そして、表示されるのはXamarinの初期画面になります。これはたまたま私の環境がそうであって、違う環境であればMvvmCrossの初期画面が出るのかもしれませんが、この辺りは調査していません。(すみません)
初期画面のアクティビティを指定するのは、MainLauncher = trueというアトリビュートで行います。今回の場合、Xamarinのデモアプリの初期画面であるMainActivity.csと、MvvmCrossのデモアプリの初期画面であるSplashScreen.csの両方に指定があります。よって、MainActivity.csのMainLauncher属性をfalseにしてしまえば良いのですが、MainActivity.cs、およびそれに対応するViewであるMain.axml(Rsources/layoutに存在)はもう必要ないので、削除してしまいましょう。といっても、物理的に削除すると何かの参考の時に見れなくなりますので、以下のようにプロジェクトからこれら2つのファイルを除外してしまいます。


10.MvvmCrossのデモアプリが動作することを確認

では、VS2013からコンパイルして、無事に以下の画面が表示されれば完了です。バインドの動作をデモするサンプルですから、上のEditBoxに入力した文字列が、下のTextViewにリアルタイムで表示されることを確認して下さい。

#上記のエミュレーターは、Xamarin Android Playerです。

以上です。おつかれさまでした。

【続報】Visual Studio 2013でAndroidのプロジェクトを作成し続けMvvmCrossを導入した場合

今日、新たなプロジェクトを作成してわかったのですが、私が以前経験したのは、上記のパターンで初めてアプリを作った際に、エラーにはなりませんが、アプリが動作しないということでした。
具体的には、バインドが動作していない感じでした。
そこで、以下のような対処方法を教えていただきながら書いたのですが、MvvmCrossを導入した直後のデモアプリも、Xamarinのデモアプリも正常に動作自体はすることに気が付きました。
私は上で書いたようにバインド動作がうまく動かなくなると思っていたのですが、勘違いでした。うまく動かなかったのは、きっとどこかを弄ってしまったのでしょう。

Xamarin + MvvmCrossで要らないファイルを削除しよう!
http://d.hatena.ne.jp/trapemiya/20150309/1425886429

ただ、伊勢さんが書かれているように、ランチャーにはそれぞれのアイコンが登録され、それぞれからはMvvmCrossのデモアプリ、Xamarinのデモアプリが起動しました。
動作としてはMainLauncherが2つあるために、MvvmCrossを導入したのにXamarinのデモアプリの方が自動的に起動してしまう状態ですが、MvvmCrossのデモアプリを知らなければ、一見、普通に動いているように見えます。
(つまり、MvvmCrossのデモアプリが動いていないことに気が付かない)
もちろん、AndroidシミュレーターであるXamarin Android Playerでランチャーの画面を出せば、アイコンが2つあることに気が付くのですが、その画面を出さなければ、一見、正常に動作しているように見えてしまいます。

とうわけで、注意しましょうということと、自分の勘違いを正した、自分へのメモ的要素が強い今回のブログの内容でした。

Xamarin + MvvmCrossで要らないファイルを削除しよう!

以下の記事に今頃気づきました。また、大変参考になる記事を上げていただき、ありがとうございます。

Xamarin + MvvmCrossでViewとViewModelの対応付けを変更する必要なんてある?
http://iseebi.hatenablog.com/entry/2015/03/01/172123

Xamarin、MvvmCrossと続けてインストールすると、Xamarinのみをインストールした際に作成されたActivityやコードのうち、要らなくなってしまうものが出てくることは何となく気が付いていました。
そこで、今はそれらを削除しています。
Visual Studioで開発していますので、正確にはプロジェクトから除外しており、物理的に削除していません。

削除したファイルは、上記のブログで伊勢さんが紹介されたままで、以下の通りです。

  • Resources/layout/Main.axml
  • MainActivity.cs

#MvvmCrossのインストール時に作成される、

  • Resources/layout/FirstView.axml
  • Views/FirstView.cs

も、私は要らないので削除しています。当然ですが、PCLのApp.csにあるRegisterAppStartメソッドの引数も、これに合わせて修正しなければなりません。

さて、今、ToDo-MvvmCross/_ Droid UI.txtを読むと、以下のように書かれています。

2. Remove any old `MainLauncher` activities - eg Activity1

これが、上記2つのファイルを消せという指示だったのですね。すみません、気が付いていませんでした。WPF + MVVMでの開発ばかりやっていると、ActivityのMainLauncherという考え方に慣れておらず、うっかりスルーしてしまったようです。というか、上の文章の意味が全く理解できていませんでした。

そういうわけで、以下のコードレシピに書いたように、

Xamarin + MvvmCrossでViewとViewModelの対応付けを変更する
https://code.msdn.microsoft.com/Xamarin-MvvmCrossViewViewMo-b3c9b197

MainLauncher = false にする必要はなくて、単に上記の2つのファイルを削除すれば解決だったわけですね。あとでコードレシピに修正を入れておきます。

ところで、

「Xamarin + MvvmCrossでViewとViewModelの対応付けを変更する必要なんてある?」

についてですが、通常はあまり無いと思いますが、1つのViewModelに複数のViewを対応させる、もしくはその逆のことを想定してのことです。したがって、画面遷移等のことは考えていなかったのですが、伊勢さんが指摘されていることはしっかりと書き留めておこうと思います。ありがとうございます。
また、コードレシピに上げた方法の他にも、実は対応付けの変更ができるようで、そのコードも見つけていましたが、それはViewとViewModelを一つずつリストのような形で対応付けて渡すというものでした。時間があればこちらも検証してみようと思ったのですが、こちらはViewとViewModelはあくまで1対1のようでしたので、今のところあまり真剣に考えていません。
いずれにしても、XamarinやPCL等で共通化を目指しているわけですから、共通で使えるViewModelも想定しておいた方が良いのではないだろうか?という発想でした。

オプションメニュー

よちよち歩きで開発を進めているわけですが、おかげさまでネット上にはいろいろ情報が転がっていて助かっています。
ただ、Xamarin + MvvmCross の組み合わせの情報を探し出しても、そこで解説されているコードをどこに書いてよいのかわからず、MvvmCrossを導入していなくてXamarinのみで開発しているコードを参考にすると、うまく動かなかったりします。しかもエラーにならなくて動作しないだけだから質(タチ)が悪いんですね。まぁ、正確にはそのコードを書いても、その部分は全く通らないからエラーにならないだけなんですけどね。

なんで、ど素人がXamarin + MvvmCrossでアプリケーションを開発していく上での必要な情報をしばらく発信していこうかと思います。自分用のメモも兼ねていますので、内容はかなりベーシックになると思いますが、暖かい目でどうぞご覧下さい。

さて、早速ですが、オプションメニューの出し方を書きます。あ〜、ちなみにVisual Studioを使っています。基本的に慣れているのがその理由です。でも、C# + Visual Studioで開発できるなんでXamarinさまさまですね。ありがたい限りです。

オプションメニューを出すのは簡単とよく書いてあります。そう、

public override bool OnCreateOptionsMenu(IMenu menu)
{
    MenuInflater.Inflate(Resource.Menu.OptionMenu, menu);
    return true;
}

を追加すれば良いだけと。書く場所は、Activityクラスを継承したクラスだそうです。
しかし、これはMvvmCrossを導入していない場合なんですね。導入していると、MvxActivityクラスを継承したクラスになります。
このクラスは、通常、ViewフォルダーにViewの名前と同じ名前でC#のコードのファイルがあります。例えば、Resources\drawable\layout\MainView.axmlですと、Views\MainView.csになります。このファイルで、MvxActivityクラスを継承したMainViewというクラスが定義されています。

しかし、ど素人はまだはまります。そう、基本的にまだXamarin + MvvmCrossのお作法がわかっていないからです(苦笑)
MenuInflater.Inflate(Resource.Menu.OptionMenu, menu);
で、Resource.Menu のMenuなんてどこにも無いよというエラーになります。
そこで想像するに、ResourcesフォルダーにMenuフォルダーを作ればいいんじゃないかと思いました。
早速作ってみましたが、エラーは取れません。
仕方なのでもう少し調査してみると、Resourcesフォルダーに、Resource.Designer.csというファイルがあるのを見つけました。
この中身をみると、ここでリソースを定義していることがわかりました。おそらく、ここにMenuフォルダーの定義も必要だと思ったのですが、ありません。
手動で追加することも考えたのですが、注意書きとして、自動生成されるのでいじるなよ的なことが思いっきり書かれていたので断念しました。
じゃあ、どうすれば自動生成されるの?と思い、Menuフォルダーに何か突っ込めばいいんじゃないかと思いました。

そこで歩を進めることにし、Menuフォルダーに入れなければならないメニューを定義したXMLファイルを入れてみました。ちなみに、以下のファイルです。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/OptionMenu1"
        android:title="Menu1" />
  <item android:id="@+id/OptionMenu2"
        android:title="Menu2" />
  <item android:id="@+id/OptionMenu3"
        android:title="Menu3" />
</menu>

これがビンゴでした! Resource.Designer.csにも自動でMenuフォルダー等、メニューの定義が追加されました。
というわけで、今回はオプションメニューが無事に表示されるところまででした。最後に、Views\MainView.csにあるMainViewクラスと、動作中の画面を載せておきます。

[Activity(Label = "View for MainViewModel")]
public class MainView : MvxActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.MainView);
    }

    public override bool OnCreateOptionsMenu(IMenu menu)
    {
        MenuInflater.Inflate(Resource.Menu.OptionMenu, menu);
        return true;
    }
}



以上、超ベーシックでした! でも、なんか楽しいね。新しいことにチャレンジするってのは。

PCLにおけるタイマーを実装する。

直接的にXamarinやMvvmCrossと関係ないのですが、PCLにおいてタイマーを使う方法がわからなかったので、ネットで調べつつ実装してみました。
一応、以下が完成品になります。突っ込みどころがあれば教えていただけると幸いです。
しばらく様子を見て落ち着いたら、コードレシピにこのタイマーを使ったサンプルを上げる予定です。

/// <summary>
/// 定期的に処理を実行するタイマークラス
/// </summary>
public class Timer : CancellationTokenSource, IDisposable
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="action">実行するアクション</param>
    /// <param name="state">継続アクションによって使用されるデータを表すオブジェクト</param>
    /// <param name="dueTime">最初の実行までに遅延する時間(ミリ秒)</param>
    /// <param name="period">アクションを実行する間隔(ミリ秒)</param>
    /// <param name="ts">CancellationTokenSourceオブジェクト</param>
    public Timer(Action<Object> action, object state, int dueTime, int period, CancellationTokenSource cts)
    {
        Task.Delay(dueTime, cts.Token).ContinueWith(
        async (t, s) =>
        {
            var tuple = (Tuple<Action<Object>, object>)s;

            while (! cts.IsCancellationRequested)
            {
                await Task.Run(() => tuple.Item1(tuple.Item2), cts.Token);
                await Task.Delay(period);
            }
        },
        Tuple.Create(action, state),
        CancellationToken.None,
        TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
        TaskScheduler.Default);
    }

    public new void Dispose() { base.Cancel(); }
}

◆参考にさせていただいたサイト

Timer in portable class library
http://stackoverflow.com/questions/20445041/timer-in-portable-class-library

#まだいろいろわからないことはたくさんたくさんあるけれど、着実に開発を進めていこうと思います。