Xamarin.Android と Azure Functions で全てがC#製のスマホアプリを作った話

この記事は [C# Advent Calendar 2019](https://qiita.com/advent-calendar/2019/c-sharp) の19日目です。
前回は [wasimaru](https://qiita.com/wasimaru)さんによる[ASP.NET Core / ASP.NET Web API 2 Owin で Web API の自動テスト環境を整える](https://qiita.com/wasimaru/items/8b87e8c5c9c4a7112335)でした。

# 忙しい方のための概略
Xamarin.Androidを用いてスマホアプリを制作し、バックエンドに Azure Functions を採用することで**全てをC#で完結させました**。
今回書き留めるのは、[はこだてローカル勉強会"ゆるはこ"](https://yurui-hakodate.connpass.com/)での登壇内容＋αです。
[登壇の際に用いたスライドはこちら(All C#なモバイル開発. / All C # Mobile Development.)](https://speakerdeck.com/atria/all-c-number-mobile-development)


構成はこんな感じ(スライドより)
![AllCSharpなモバイル開発.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86472/df2b4c86-cc11-89a8-84c0-755ecd307589.jpeg)


### 環境

 - Visual Studio 2019
 - Windows 10

# 作ったもの
地元にあるハンバーガーショップ「[ラッキーピエロ](http://luckypierrot.jp/)」の特売情報をストレージから自動で出してくれるアプリ「**ラピナビ**」を制作しました。
個人開発で遊んでみたかっただけなので、一般公開はしていません。UIも実用性重視で、凝ったものではありません。

## 画面
<img height="500" alt="Screenshot_20190923-125632.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86472/00eafcd0-37b1-ad38-ade9-4ba49c3d8866.png">

# 経緯
僕の住んでいる地域、北海道函館市には「ラッキーピエロ」という有名なハンバーガーショップがあります。テレビなどの媒体でよく紹介されているようなので、もしかするとご存じの方もいらっしゃるかもしれません。

このお店はよく割引キャンペーンなどを行うのですが、その情報をしっかりと捉えるのが結構大変なんです。
具体的には実施店舗が違ったり、対象商品が違ったり、実施期間が限られているなどなど…

キャンペーンに乗っかって最高の立ち回りをするためには覚えることが多いです。

そこで！**情報をまとめたスマホアプリを作ってやろう**という、自分による自分のためのアプリ開発を始めることにしました。
# ALL_C#開発

自分はC#が大好きなので、どうせならすべての処理にC#を採用してやろうと考えました。

今回のスマホアプリ化に必要な機能は以下の通り

 - 日時を取得する
 - 定期割引日かどうかを判断する
 - 特別割引日かどうかをサーバーから情報を受け取り判断する

うん、これくらいなら簡単な実装でなんとかなりそうだ。

いろいろ考えた結果、特別割引日の情報を格納するストレージと、特別割引日の情報を送信するサーバが必要だと考えました。

「そういえば Azure に簡単にWebAPIを立てられるサービスがあったな」と考え、Azure Functionsにたどり着きます。

# Azure Functions とは
<img width="300" alt="1_XlE7tOMn_UzYKE0FolVnBA.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86472/8e86863b-9372-f013-7d26-ad8ccf90aaef.png">
Azure Functions は Microsoftが提供するクラウドサービスです。
最近流行りのサーバーレスアーキテクチャを採用しています。

従量課金制を採用しており、僕のような個人の開発者でも気軽に使用することができます。
（そして結構安かったりする）

またクラウドサービスであるため、オンプレミスほどサーバー管理を気にすることがなく気持ちよく開発を進めることができます。

使用言語はC#,Java,JavaScript,Pythonなどから選ぶことが可能です。
もちろん今回の開発ではC#を採用しました。
# Azure Table Storageを使う
> 特別割引日かどうかをサーバーから情報を受け取り判断する

この機能を実現するには割引日の情報をサーバーに保存しておく必要があります。
そこで、DBのような働きをしてくれるのがこの [Azure Table Storage](https://azure.microsoft.com/ja-jp/services/storage/tables/) です。

Azure Table Storage の作成とアクセスにはWindowsの場合 **Microsoft Azure Storage Explorer**が便利です。
Microsoft Azure Storage Explorerをインストールし、Table Storageを作成して、必要な特別割引情報を次の画像のようにぶち込みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86472/b50bdf03-0c4e-6ec3-56cf-c8f71f03ecdd.png)
これでデータ側の準備はできました。次はこのデータを吐き出すAPIの部分です。

# Functionの作成
今回データの受け渡しにはJSONを用いますので、Azure FunctionsでJSONを吐き出すAPIを作成します。
プロジェクトの作成はとっても簡単。VisualStudioで「新しいプロジェクトの作成」で作成できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86472/75280cca-dad6-65c3-18d0-786491a59ff3.png)
プロジェクトを作成したら、Azure Table Storageを使用するために**"Microsoft.Azure.Webjobs.Extensions.Storage"** を,JSON形式のデータを扱うために**"Json.NET(Newtonsoft.Json)"**を**NuGet**で導入します。
<img width="600" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86472/4a689432-ff7e-3c36-d712-0c04fb09d74f.png">
<img width="500" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86472/e28e658d-519a-8a04-02f9-34263fa8895a.png">

そして、Azure Functions特有の記法でプログラムを記述。

内容は**"POSTリクエストが送信されたときに、DBに入っている全ての要素をJSON化して送信する"**というものです。

また、**Azure FunctionsとAzure Table Storageのバインドはちょっとコツがあります**。以下の文献を読むことをおすすめします。

[Azure Functions における Azure Table Storage のバインド - GitHub - MicrosoftDocs/azure-docs.ja-jp](https://github.com/MicrosoftDocs/azure-docs.ja-jp/blob/master/articles/azure-functions/functions-bindings-storage-table.md)


```csharp
using System.Text;
using System.Net;
using System.Net.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.WindowsAzure.Storage.Table;
using System.Collections.Generic;

namespace LpNaviFunc
{
    public static class GetInfo
    {
        [FunctionName("GetInfo")]
        public static HttpResponseMessage Run(
            //[Table(“LPInfo”)]などでデータとバインディングしている。
            //詳しい記法は Azure Functions の公式ドキュメントを読んでください。
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            [Table("LPInfo")] CloudTable cloudTable,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            //Azure Table Strageにアクセスし、データをリストに格納する。
            var querySegment = cloudTable.ExecuteQuerySegmentedAsync(new TableQuery<LPINFO>(), null);
            StringContent responseContent = null;
            var ls = new List<LPINFO>();

            foreach (LPINFO item in querySegment.Result)
            {
                ls.Add(item);
            }
            //すべての格納が終了した後に、Jsonファイルを生成している。
            responseContent = new StringContent(JsonConvert.SerializeObject(ls,Formatting.Indented),Encoding.UTF8,"application/json");
            //最後に生成したJsonファイルを返す。
            return new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = responseContent
            };
        }
    }

    //レコードを取得する用のクラス。
    //これを作ることによってListでの管理が可能になる。
    public class LPINFO : TableEntity
    {
        public string Shops { get; set; }
        public string Summary { get; set; }
    }

}
```
### V2版を用いる際のちょっとした注意点
コードを記述していくに当たり多くの日本語記事で"Iqueryable"を使用するものが見受けられますが、Azure Functions V2からこの記法はできなくなっていました。
#### 参考url
[Functions v2: Can't bind Table to type System.Linq.IQueryable - GitHub](https://github.com/Azure/azure-webjobs-sdk/issues/1566)

この情報は[公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-storage-table)にも記述されていますので、公式ドキュメントを読む方ならそこまで詰まることはないと思います。(自分は精読せずに時間を捨ててしまった(公式ドキュメントはちゃんと読もう))

実際に起動してみるとAzure Functionsのマークが出てきて、おおっ！となります。
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Hello Azure Functions. <a href="https://t.co/bK6ZUcpBUk">pic.twitter.com/bK6ZUcpBUk</a></p>&mdash; Atria (@AtriaSoft) <a href="https://twitter.com/AtriaSoft/status/1183291421383524352?ref_src=twsrc%5Etfw">October 13, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# Xamarin.Androidを用いたモバイル側の実装

### 画面
バックエンド側はオッケーです。

フロント側はXamarin.Androidを用いてモバイル側を実装します。
そのために、「新しいプロジェクト」から「Androidアプリ(Xamarin)」を選択してプロジェクトを立てます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86472/cdf02bab-05c6-629d-e844-2989b5fe32b3.png)


画面はデザイナーやXamlファイルをよしなに編集してやって作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86472/9bdb94f5-595a-e536-1c8d-898c8456235c.png)
ListViewを設置して、Listに格納したデータから情報を表示させる算段です。

### ロジック
JSON形式のデータを扱うために**"Json.NET(Newtonsoft.Json)"**を**NuGet**で導入します。

ここではボタンをクリックされたときに更新処理を行うようにしてあげたいので、お得意の**"FindCiewById"**をしてあげた後に**イベントハンドラを与えます**。

```csharp
//本物の実装ではFragmentを用いているのでちょっと形式が違うと思います…
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    var view = inflater.Inflate(Resource.Layout.Suggest, container, false);

    var button = view.FindViewById<Button>(Resource.Id.updateButton/* ボタンのId */);
    button.Click += Clicked;
    return view;
}

private void Clicked(object sender, EventArgs e)
//ここにクリック時の処理を記述...
}
```

#### 用いるデータ形式
本当はC＃でよく用いられる Dictionary型を用いたかったのですが、この型だとうまくいきませんでした(JavaList < IDictionary< string, object>>()へのキャストでExceptionを吐いてしまいます)。
そこで、データ構造にはJavaListを採用することにしました。

```csharp
//本当はDictionaryを用いたかったが、JavaList<IDictionary<string, object>>()へのキャストでExceptionを吐いてしまうためJavaDictionaryを用いた,
private JavaDictionary<string, string> dic = new JavaDictionary<string, string>();
```

# Httpリクエストを送信してJSONを受け取る
Httpリクエストを送ってAzure Functions からJSONデータを受け取ります

JSON.NETを用いているので、デシリアライズも簡単で快適です。

```c#

//臨時キャンペーンの追加
//Azure functionsを利用している
//アクセスするURL
var url = "hogehoge";
using (var client = new HttpClient())
{
    try
    {
        //JSONを受け取って独自クラスにコンバートする
        var result = await client.GetStringAsync(url);
        var ls = JsonConvert.DeserializeObject<List<LPinfo>>(result);
        //データ格納処理
        for (int i = 0; i < ls.Count; i++)
        {
            dic[ls[i].Shops] = ls[i].Summary;
        }
    }
    catch (Exception)
    {
        //ここにエラー時処理
    }
}
```

そんなこんなでデータを受け取り、JavaListに格納することに成功しました。

あとはJavaListの内容をListViewに表示させるだけです。

```csharp
//アダプター作成とセット
var lsView = View.FindViewById<ListView>(Resource.Id.outputView);
var arrayAdapter =
    new SimpleAdapter(this.Context,
    JavaListName,
    Android.Resource.Layout.SimpleListItem2,
    new string[] { "main", "sub" },
    new int[] { Android.Resource.Id.Text2, Android.Resource.Id.Text1 }
    );

lsView.Adapter = arrayAdapter;
```

こうして全てがC#製の自分用モバイルアプリの作成に成功しました。
# 得られた知見
 - Azure Functionsを用いると簡単にAPIを作成できる
  - そしてそこまでお金がかからない(かかっても10円くらい)
 - 全てをC#製にすると統一性があって開発がしやすい
 - データ構造の面で少し工夫をする必要があったりする
 - **全部C#でもしっかりとしたスマホアプリが作れる！！！！**

今回紹介した全てがC#な開発事例、
そしてこれらの知見がどこかの誰かの役に立てば幸いです。

# 次回
次回のアドベントカレンダーは [mei_9961](https://qiita.com/mei_9961) さんです！