.NET MAUIを使ってAndroidで動く天気予報アプリをつくる

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

最近、家でPCを触るときは、いつもウェザーニューズ社の天気予報youtubeのライブ動画をずっと見ている。

動画の中で、きれいなお姉さんがお天気を解説してくれているのだが、その動画が、テレビのニュースなどよりも崩れた感じで話すので、なんとなく深夜ラジオを聴いている感じがして聞きながら作業するのにちょうどいい。あと目の保養。

その中でも、檜山沙耶さんというお天気キャスターの方が推しで、その方のまとめ動画をつい見てしまう。 (人気お天気キャスターランキングの2位らしい)

www.youtube.com

家で趣味でプログラム作成される方は、ラジオ代わりに見てみて頂きたい。

で、その天気予報を見ていたら、どうしても天気予報アプリを作りたくなった。
最近よく触るWinUI3で作ろうかと思ったが、朝起きて仕事に行く前にWindowsPCで天気予報を見るのはつらいので、スマホで見たい。(最初から天気予報アプリあるじゃないかというのは置いといて)

スマホで見るなら、昔使ってたAndroidがあるのと、最近.NET MAUIがVS2022 Previewで使えるというのを見たので、一度それでアプリを作ってみたい。

やり方

.NET MAUIを使って、WindowsでもAndroidでも動くアプリを作る。

MAUIは22年6月時点でPreview段階のようで、プログラム作成するにはVisual Studio 2022 Previewが必要。

また、天気予報の取得には、気象庁の天気予報APIというのがあるらしいので、それを使って取得する。

やったこと

環境

下図のような環境で、実験を行った。

VisualStudio2022 Previewのインストール

こちらのページから、VisualStudio2022 Preview をダウンロードし、インストールする。

https://visualstudio.microsoft.com/ja/vs/preview/

今回は、インストール時に下図の項目にチェックを入れた。
(もしかしたらMAUIに必要最低限なもの以外のチェックも入っているかも)

VSプロジェクトの作成

新規ソリューションの作成で、「.NET MAUI アプリ」のテンプレートを選択する。

そのテンプレでプロジェクトを作成すると、下記のような構成になる。

(WPFやWinUI3では)見慣れない「Platforms」というフォルダがあり、その中に、AndroidiOSMacWindowsのフォルダがある。
まだ詳しく調べてないが、ここにプラットフォームごとに、起動時の処理を書いたりするのだろうと思う。(今回は割愛)

手持ちのAndroidスマホのためのVisualStudioの設定

手持ちのAndroidは、HUAWEIのHW503という機種で、

というのを使っている様子。まずそのSDKをインストールする。

まず、下図のボタンを押して、「Android SDK マネージャー」を開く。(管理者権限を要求される)

その中の、Android 5.0 - Lollipopを開き、Android SDK Platform 21にチェックを入れ、「変更の適用」を押す。 これで、SDKのインストールは完了。

※VisualStudioには「Androidエミュレーター」の機能が備わっており、本当はまずそれを使って動作を見たかったのだが、 私のPCでは下図のようなエラーがでて、どうしてもAndroidエミュレータを起動することができなかった。

実機でデバッグならできたので、エミュレータはあきらめて、最初からUSBケーブルで接続したHW503実機でデバッグすることにした。

※ここは実機準備のとこにかいたほうがいいかも?↓

USBで実機をつなぐと、こんな感じでデバッグ対象に「Android Local Device」というのが出てきて、その中にHW503が出てきた。

(最初、USB接続したときに、充電しかできない、通信できない安物USBケーブルを使っていたために、このLocalDeviceが出てきてくれず、地味にハマった。要注意。)

Androidスマホの準備

開発者モードに入れる

AndroidスマホをUSBでPCに接続して開発(プログラムの配置やデバッグ)をするには、スマホを「開発者モード」に入れる必要がある。 そのやり方は下記の通り。

  • HW503の場合
    • 「設定ボタン」を押して、設定画面を開く
    • 「端末情報」を押し、端末情報画面を開く
    • 「ビルド番号」を5回連続で押して開発者モードにする
    • 設定画面に戻ると「開発者向けオプション」の項目が出ているので、それを押す
    • 開発者向けオプション画面で、
      • 「開発者向けオプション」をONにする
      • 「USBデバッグ」をONにする

これで、USB接続してプログラムの配置やデバッグができるようになった。

※今回は、「スリープモードにしない」の設定もONにする(ずっと画面ONのままにしたかったので)

WiFiの設定

天気予報をwebから取得したいので、WiFiの設定をして、自宅のWiFiルーターと接続できるようにしておく。 一般的なWiFi接続手順でOKなので、今回は手順は割愛。

実機を接続して、デフォルト状態のMAUIコードをAndroidスマホで動かしてみる

コードを自分でいろいろ書く前に、MAUIのプロジェクトを作成したそのままの状態でビルドして、プログラムを配置、デバッグ実行してみる。

プロジェクト作りたてのときのVSの上部は下図のようになっている。

この部分の下矢印を押すと、プログラムを実行する環境を選択できる。

この状態ではまだAndroidの実機をUSBで接続していないので、Androidの実機の選択項目が出ていない。 USBでAndroid実機(今回はHW503使用)を接続すると、下図のようになる。

HUAWEI HW503を押すと、実機へのプログラムの配置が始まる。
(開発PCやAndroid端末の性能にもよるのかもしれないが、私の場合、ビルドには20秒ほど、配置が終わるまでには1分以上かかった)

配置が終わると、実機上でアプリが動き出す。

まずスプラッシュスクリーンがでたあとに、

初期状態のサンプルアプリが表示された。

ここまで出来たら準備はOKなので、次はコードを書いていく。

お天気取得プログラム

つくるプログラムのイメージ

まず、ゴールは下記のようにしようと思う。

下記のものを表示する。

  • お天気アイコン
  • 最高予想気温
  • 最低予想気温
  • お天気概要
  • 更新時間

プログラムがやること

下記のことをやる。

お天気情報について

詳しくは、下の参照の項目に挙げたページが詳しいが、下記のようにして情報の出所を決めている。

  • 天気予報の情報は、https://www.jma.go.jp/bosai/forecast/data/forecast/XXXXXX.jsonからとれる。
  • XXXXXXの部分は地域コードで、大阪ならそこが270000になる。→地域コードはこちら
  • 270000.jsonからとってきた情報の中で、今回欲しいのはこちら。

→大阪のお天気概要:https://www.jma.go.jp/bosai/forecast/data/forecast/270000.json

→お天気アイコン(晴れ):https://www.jma.go.jp/bosai/forecast/img/100.png

欲しい情報
今日の天気アイコン|https://www.jma.go.jp/bosai/forecast/img/天気コード.pngからとれる
今日の天気コード|270000.jsonの1個目のデータのtimeSeriesのareasのweatherCodesの一番目
今日の天気概要|270000.jsonの1個目のデータのtimeSeriesのareasのweathersの一番目
今日の最高気温|270000.jsonの2個目のデータのtempAverageのareasのmax
今日の最低気温|270000.jsonの2個目のデータのtempAverageのareasのmin

文字にするとわけわからないが、jsonの中の下記の部分。

コードを見た方が早いかもしれない。

実際のプログラム

変更を行った部分のみ挙げる。

AppShell.xaml
タイトル表示のみいじった。

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="MauiApp3.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:MauiApp3"
    Shell.FlyoutBehavior="Disabled">

    <ShellContent
        Title="お父ちゃん天気予報 (1時間に一回更新)"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" />
</Shell>

メインの画面のxamlコード。 表示したい内容をここに書いている。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiApp3.MainPage"
             Loaded="ContentPage_Loaded">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="2*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1.5*"/>
        </Grid.ColumnDefinitions>

        <Grid Grid.Row="0" Grid.Column="0" >
            <Image x:Name="WeatherIcon" />
        </Grid>

        <Grid Grid.Row="0" Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>

            <Label x:Name="MaxTemp" Grid.Row="0" Text="Max" FontSize="50" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
            <Label x:Name="MinTemp" Grid.Row="1" Text="Min" FontSize="50" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
        </Grid>

        <Grid Grid.Row="1" Grid.ColumnSpan="2">
            <Label x:Name="Overall" Text="GaiyouSetsumei" FontSize="25" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
            <Label x:Name="UpdateTime" HorizontalOptions="End" VerticalTextAlignment="End"/>
        </Grid>
    </Grid>
</ContentPage>

少しだけ長いが、メイン画面でやっているお天気情報取得&表示のコード。 class Rootobjectから下の、json保存用クラスは、VisualStudioのjson→クラス変換機能で自動で作った。 (やり方はこちら)

using Microsoft.Maui.Dispatching;
using System.Text.Json;

namespace MauiApp3;

public partial class MainPage : ContentPage
{
    static readonly HttpClient client = new HttpClient();
    IDispatcherTimer timer;

    public MainPage()
    {
        InitializeComponent();
    }

    private async Task Update()
    {
        var jsonObj = await Task.Run<List<Class1>>(async () =>
        {
            HttpResponseMessage response = await client.GetAsync("https://www.jma.go.jp/bosai/forecast/data/forecast/270000.json");
            response.EnsureSuccessStatusCode();
            string responseBody = await response.Content.ReadAsStringAsync();

            var jsonObj = JsonSerializer.Deserialize<List<Class1>>(responseBody);

            return jsonObj;
        });

        var todayData = jsonObj[0].timeSeries[0].areas[0];

        //WeatherIcon.Text = todayData.weatherCodes[0];
        MaxTemp.Text = "最高 " + jsonObj[1].tempAverage.areas[0].max + " ℃";
        MinTemp.Text = "最低 " + jsonObj[1].tempAverage.areas[0].min + " ℃";
        Overall.Text = todayData.weathers[0];
        UpdateTime.Text = "最終更新:" + DateTime.Now.ToString();
        WeatherIcon.Source = @"https://www.jma.go.jp/bosai/forecast/img/" + todayData.weatherCodes[0] + ".png";
    }

    private async void ContentPage_Loaded(object sender, EventArgs e)
    {
        await Update();

        timer = Dispatcher.CreateTimer();
        timer.Interval = TimeSpan.FromHours(1);
        timer.Tick += async (s, e) => await Update();
        timer.Start();
    }

    private async void Button_Clicked(object sender, EventArgs e)
    {
        HttpResponseMessage response = await client.GetAsync("http://f.st-hatena.com/images/fotolife/s/shirakamisauto/20160120/20160120232256.png");
        response.EnsureSuccessStatusCode();
        var _ss = await response.Content.ReadAsStreamAsync();

        await Update();
        WeatherIcon.Source = @"https://www.jma.go.jp/bosai/forecast/img/100.png";
    }
}


public class Rootobject
{
    public Class1[] Property1 { get; set; }
}

public class Class1
{
    public string publishingOffice { get; set; }
    public DateTime reportDatetime { get; set; }
    public Timesery[] timeSeries { get; set; }
    public Tempaverage tempAverage { get; set; }
    public Precipaverage precipAverage { get; set; }
}

public class Tempaverage
{
    public Area[] areas { get; set; }
}

public class Area
{
    public Area1 area { get; set; }
    public string min { get; set; }
    public string max { get; set; }
}

public class Area1
{
    public string name { get; set; }
    public string code { get; set; }
}

public class Precipaverage
{
    public Area2[] areas { get; set; }
}

public class Area2
{
    public Area3 area { get; set; }
    public string min { get; set; }
    public string max { get; set; }
}

public class Area3
{
    public string name { get; set; }
    public string code { get; set; }
}

public class Timesery
{
    public DateTime[] timeDefines { get; set; }
    public Area4[] areas { get; set; }
}

public class Area4
{
    public Area5 area { get; set; }
    public string[] weatherCodes { get; set; }
    public string[] weathers { get; set; }
    public string[] winds { get; set; }
    public string[] waves { get; set; }
    public string[] pops { get; set; }
    public string[] temps { get; set; }
    public string[] reliabilities { get; set; }
    public string[] tempsMin { get; set; }
    public string[] tempsMinUpper { get; set; }
    public string[] tempsMinLower { get; set; }
    public string[] tempsMax { get; set; }
    public string[] tempsMaxUpper { get; set; }
    public string[] tempsMaxLower { get; set; }
}

public class Area5
{
    public string name { get; set; }
    public string code { get; set; }
}

出来上がり

これを、Android Local Deviceを選んで実行をしてやると、Androidスマホ上に天気予報アプリが表示された。

↓再掲

WindowsPC以外であまりアプリを作ったことがないので、結構感動した。

以前、ラズパイでLEDチカチカとか、サーモグラフィ作ってみたこともあったが、一応Windows(Windows IoT core)だったので、Windowsではないところで画面付きアプリを作ったのは初めてかもしれない。

あと、今使っているiphoneでも今回作った天気予報アプリを動かしてみたかったが、iOSアプリをMAUIで作るにはマックがいるということなので断念した。 もしマックが手に入るようなことがあれば、試してみたい。

所感

Android用に作ったこのアプリを、Windows上で動かすと、普通に動いた。

Windows Machineを選んで実行すると、PC上で動く。

Windowsをダークモードにしているので結構イメージ違う

今回、簡単なアプリだったのもあり、AndroidWindowsの違いを全く意識することなく、それぞれのプラットフォームを意識したコードを書くことなく、同じアプリを作ることができた。

たぶん、もっとちゃんと各プラットフォームに対応しようとするといろいろな苦労がでてくると予想するが、とりあえず趣味で作る分には、なんの苦労もせずに、WindowsAndroid両対応アプリができそうなことは分かった。

Androidでちょっとしたゲームとか作ってやれば、こどものヒーローになれるかもしれない。

参考

ウェザーニュースのサイト。ここの天気予報の動画をいつもみてる。

https://weathernews.jp/

つい見てしまう推しお天気キャスター 檜山沙耶さんの動画。 とにかくオモロイ。

https://www.youtube.com/watch?v=ftftFSfwFZk

.NET MAUIのページ

https://docs.microsoft.com/ja-jp/dotnet/maui/what-is-maui

気象庁の天気予報jsonの存在を知ったページ。
APIのアドレスと、地域コードなどが書いてある。

https://anko.education/webapi/jma

気象庁の天気予報jsonを使って工作されてる方の記事。
色々詳しく調べておられる様子。

https://www.mgo-tec.com/blog-entry-jp-weather01.html