SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

Xamarni.Forms AzureモバイルサービスによるToDoアプリ

【 Xamarin 記事一覧 】


はじめに

f:id:furuya02:20141016052929p:plain:w250:left

Microsoft Azure のドキュメントにある「モバイル サービスの使用」では、XamarinによるToDoアプリのチュートリアルが公開されています。

同ページのチュートリアルを進めると、Xamarin.iOS 及び Xamarin.Android の雛形プロジェクトをダウンロードしてそのまま実行できる手順となっています。

今回は、このToDoアプリをXamarin.Formsで作成してみました。

チュートリアルではXamarinコンポーネントが使用されていますが、このコンポーネントは、iOS用とAndroid用しか無いため、本記事ではNuGet版のWindowsAzure.MobileServicesを使用しました。

コンポーネント版もNuGet版もインターフェースが全く同じようなので、雛形プロジェクトのコードは、そのまま利用可能です。


なお、本記事をまとめるにあたって色々検索してみたのですが、どこのコードもMobileServicesのインスタンスは、各ターゲットのプロジェクト側に置いてあり、コードの共通化は、Sharedクラスなどで行われていました。
何か制約があるのかなと思っていたのですが・・・結果的には、Xamarin.FormsのPCLでインスタンスを生成しても、まったく問題ないようです。
ただ、iOSAndroidでは、CurrentPlatform.Init()という初期化コードが必要なのですが、この1行だけをそれぞれのプロジェクト側に記述する必要がありました。

モバイルサービスの作成とテーブル生成

Microsoft Azure のドキュメント「モバイル サービスの使用」では、下記の手順で紹介されています。

(1)新しいモバイル サービスを作成する
(2)新しい Xamarin.iOS アプリケーションを作成する
(3)新しい Xamarin.iOS アプリケーションを実行する

(1)で「モバイルサービス」を作成して、(2)でToDoアプリ用の「テーブル」を生成していますが、本記事では、ここまでをそのまま流用して、同テーブルをそのまま利用しています。

チュートリアルで作成されるテーブルは下記のようになっています。

class ToDoItem {
    public string Id { get; set; }

    [JsonProperty(PropertyName = "text")]
    public string Text { get; set; }

    [JsonProperty(PropertyName = "complete")]
    public bool Complete { get; set; }
}

また、URL及びキーは、雛形プロジェクトのコード(QSTodoService.cs)や、Azureポータル(ダッシュボード)で確認できます。

f:id:furuya02:20141016053746p:plain:w400:left

public class QSTodoService : DelegatingHandler

    static QSTodoService instance = new QSTodoService ();
    const string applicationURL = @"https://XXXXXX.azure-mobile.net/";
    const string applicationKey = @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    MobileServiceClient client;
    IMobileServiceTable<ToDoItem> todoTable;

プロジェクトの作成(パッケージのインストールと初期化)

f:id:furuya02:20141016053927p:plain

プロジェクトは、「Mobile Apps」の「Blank App(Xamarin.Forms Portable)」で作成します。
全プロジェクトにMobileServicesをインストールします。

PM> Install-Package WindowsAzure.MobileServices [全プラットフォーム]

MobileServicesは、いくつかの依存関係に基づいてインストールされます。

f:id:furuya02:20141016053953p:plain

続いて、AndoroidとiOSのプロジェクトで、MobileServicesの初期化のため「CurrentPlatform.Init()」の1行を追加します。

Android【MainActivity.cs】

public class MainActivity : AndroidActivity {
    protected override void OnCreate(Bundle bundle) {
        base.OnCreate(bundle);

        Xamarin.Forms.Forms.Init(this, bundle);

        //初期化処理を追加
        Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();

        ....
    }
}

iOS【AppDelegate.cs】

public partial class AppDelegate : UIApplicationDelegate {

    public override bool FinishedLaunching(UIApplication app, NSDictionary options) {
        Forms.Init();
            
        //初期化処理を追加
        Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();

        ....
    }
}

PCLの実装

PCLに記述した全てのコードは下記のとおりです。
非常にシンプルで、MobileServicesの威力を実感します。

public class App {
    public static Page GetMainPage() {
        //インジケータ(IsBusy)を表示するためにNavigationPageを使用する
        return new NavigationPage(new MainPage());
    }

    class MainPage : ContentPage {
            
        //モバイルサービスへの接続のためのキー
        const string ApplicationUrl = "XXXXX";
        const string ApplicationKey = "xxxxx";
        readonly MobileServiceClient _client = new MobileServiceClient(ApplicationUrl, ApplicationKey);
        //リストボックスのデータソース
        readonly ObservableCollection<ToDoItem> _ar = new ObservableCollection<ToDoItem>();

        public MainPage() {

            Title = "TodoAzure (Xamarin.Forms)";

            //リストボックス
            var listView = new ListView {
                ItemsSource = _ar,
                ItemTemplate = new DataTemplate(typeof(TextCell))
            };
            //ToDoItemのTextプロパティを表示する
            listView.ItemTemplate.SetBinding(TextCell.TextProperty, "Text");

            listView.ItemTapped += async (s, a) => {
                //タップ時のイベント処理
                if (await DisplayAlert("確認", "削除して宜しいですか?", "OK", "Cancel")){
                    Del((ToDoItem) a.Item);
                };
            };
            //テキストボックス
            var entry = new Entry{
                HorizontalOptions = LayoutOptions.FillAndExpand,
            };
            //追加ボタン
            var buttonAdd = new Button { Text = "Add" };
            buttonAdd.Clicked += (s, a) =>{ //クリック時のイベント処理
                Add(entry.Text);
                entry.Text = "";
            };
            //更新ボタン
            var buttonRefresh = new Button { Text = "Refresh" };
            buttonRefresh.Clicked += (s, a) => Refresh(); //クリック時のイベント処理

                
            //画面構成
            Content = new StackLayout { 
                Children = {
                    //上下にStackLayoutとListViewを配置する
                    new StackLayout{ 
                        BackgroundColor = Color.FromRgb(169,206,152),
                        Padding = 5,
                        Spacing = 10,
                        Orientation = StackOrientation.Horizontal,
                        //左右にEdit・Button・Buttonを配置する
                        Children = {entry,buttonAdd,buttonRefresh}
                    },listView
                }
            };

            Refresh();//表示更新
        }

        //表示更新
        async void Refresh(){
            IsBusy = true;//インジケータのON
                
            //DB検索 (Complete==falseのデータだけ抽出する)
            var result = await _client.GetTable<ToDoItem>().Where(todoItem => todoItem.Complete == false).ToListAsync();
                
            //リストボックスの更新
            _ar.Clear();
            foreach (var a in result) {
                _ar.Insert(0,a);
            }

            IsBusy = false;//インジケータのOFF
        }

        //追加
        async void Add(string str){
            if (str != ""){
                IsBusy = true;//インジケータのON

                //DBへの追加
                var item = new ToDoItem { Complete = false, Text = str };
                await _client.GetTable<ToDoItem>().InsertAsync(item);
                    
                //リストボックスへの追加
                _ar.Insert(0, item);
                
                IsBusy = false;//インジケータのOFF
            }
        }
        //削除
        private async void Del(ToDoItem item){
            IsBusy = true;//インジケータのON

            //DB更新
            item.Complete = true;
            await _client.GetTable<ToDoItem>().UpdateAsync(item);
                
            //リストボックスからの削除
            _ar.Remove(item);

            IsBusy = false;//インジケータのOFF
        }
    }
    class ToDoItem {
        public string Id { get; set; }

        [JsonProperty(PropertyName = "text")]
        public string Text { get; set; }

        [JsonProperty(PropertyName = "complete")]
        public bool Complete { get; set; }
    }
}


Android
f:id:furuya02:20141016054315p:plain:w200:left f:id:furuya02:20141016054316p:plain:w200:left
iOS
f:id:furuya02:20141016054318p:plain:w200:left f:id:furuya02:20141016054311p:plain:w200:left
WindowsPhone
f:id:furuya02:20141016054312p:plain:w200:left f:id:furuya02:20141016054313p:plain:w200:left


[参考にさせて頂きました]
Azure Mobile Service は Nuget版とXamarin Components版があるので注意
http://www.moonmile.net/blog/archives/5773

モバイル サービスの使用
http://azure.microsoft.com/ja-jp/documentation/articles/partner-xamarin-mobile-services-ios-get-started/


【 Xamarin 記事一覧 】