rksoftware

Visual Studio とか C# とかが好きです

MAUI で DevDays ハンズオンをしてみたかった話

ようやく MAUI がリリースされました。Xamarin のサポート終了も予告され憂いなく MAUI に移行できるようになりました。
そこでこれからはコミュニティのイベントも MAUI を焦点に行っていくことになります。当然、Xamarin 時代に行っていたことも MAUI にアップデートしていかなければなりません。

■ 一番多くやってきたもの

正直一番多くやってきたのは Phoneword ハンズオンな気がします。
github.com

このコンテンツで入門者向けのイベントをよくやっていました。時期が経って新しいハンズオン素材が作られたのでその後は次のコンテンツをやり続けてきました。

github.com github.com

二つ貼っていますが、英語のコンテンツとそれを日本で行うために手順書を日本語にした公式のコンテンツです。そのほかにもハンズオン素材はいくつも作ってきましたが、一つを挙げるならばこれでしょう。

今日はこれを MAUI でやっていこうと思います。

■ 開発環境をアップデートする

MAUI はこれまでも何度か触ってきましたが、リリースに当たって開発環境もアップデートされています。まずは開発環境をアップデートしなければリリースされたバージョンで作れません。
開発環境としては正直、Visual Studio をアップデートすれば全部うまくいくはずです。それで事足りるのですが敢えて今回は maui-check というツールを使ってみます。これのツールは MAUI の開発環境が整っているかを確認し、整っていない部分を整えてくれるツールです。今の時代の開発環境はこういったツールで整えるのが一般的です。

正直、実はこのツールを使っても Visual Studio はアップデートすることになるのであまり意味はないのですが、将来 Visual Studio でなく Visual Studio Code で作れるようになる日が来ると思います。その時のために今から応援したいので愛用しています。

■ maui-check のアップデート

maui-check も MAUI とともにアップデートされているので、まずは maui-check をアップデートします。手順はアップデートが存在するかの確認から。maui-check は dotnet tool なのですが、dotnet tool は今のところ一括でインストール済みのツールのアップデートの存在確認を行ってくれる機能がないようです。そこでインストールされている dotnet tool のアップデートの存在確認を一括で行ってくれる dotnet tool の紹介です。

rksoftware.hatenablog.com

このツールで確認すると、アップデートがありました。インストールされているのが 0.10.0 で、現行バージョンは 1.0.0 と出ています。

> dotnettoolupdatecheck

Package ID                                      Current         Latest          Command
------------------------------------------------------------
redth.net.maui.check                            0.10.0          1.0.0           maui-check
rksoftware.dotnettoolupdatecheckerconsole       0.1.3           0.1.3           DotNetToolUpdateCheck
rksoftware.imageresizeconsole                   0.2.0           0.2.0           ImageResize
uno.check                                       1.4.2           1.5.4           uno-check

アップデートが見つかったのでアップデートします。方法はこちらに詳しいです。
rksoftware.hatenablog.com

> dotnet tool update -g redth.net.maui.check
ツール 'redth.net.maui.check' がバージョン '0.10.0' からバージョン '1.0.0' に正常に更新されました。

■ maui-check の実行

アップデートできたので maui-check していきます。

maui-check

Windows が立ち上がって環境を調べて結果を表示し、整えてくれます。

Visual Studio が古いと言われました。残念ながら Visual Studio の更新はツールでやってくれないので後で自分でアップデートします。


OpenJDK、AndroisSDK、Android エミュレーター、.NET のアップデートもしてくれます。


Workload がエラーになります。これはいつもエラーになりますね。



整えて、とお願いしても失敗しました。原因はわかりませんが私の環境ではいつもこうです。しかしいつも問題ないのでそのままにしてしまっています。

■ Visual Studio のアップデート

先ほど maui-check が整っていないと教えてくれた Visual Studio をアップデートしていきます。

Windows のスタートメニューで `visual studio installer をタイプしてインストーラーを起動。

私の環境では製品版とプレビュー版が入っています。これまで MAUI はプレビュー版でしたが、リリースされた今は製品版です。製品版をアップデートします。

今回は 5.6 GB ダウンロードするようです。私は個人の活動は優雅にカフェで作業しています。テザリングで。

デザリングで 5.6 GB というと少し抵抗のある方もあるかもしれません。しかし私には無限のギガがあります。どんな大容量ダウンロードも「まあ私には無限ギガあるしな」で OK です。都会の 5G 環境下では田舎の固定回線より早いかもしれません。それがこちらのプランです。よかったらぜひご検討してみてください。おすすめです。
www.docomo.ne.jp

5G の力で、ちょっと Twitter している体感すぐにアップデート完了。

■ もう一度 maui-check

もう一度 maui-check します。


Visual Studio、OK です。


5.0 が入っていて 5.0 でないからエラー? ひとまず y で整えてもらって......。

整った! けどワークロードはダメ。

maui-check では整わないようなので、Visual Studio の方でワークロードやコンポーネントを見てみましょう。インストーラーの [ 変更 ] で。


.NET マルチプラットフォーム アプリの UI 開発 を ON を → OFF → ON。


個別のコンポーネントも MAUI 関係を全部 ON を確認して [ 変更 ] 。

やっぱりだめ

しかし実はだめでも大丈夫なので、このまま進めていきます。

■ 小ネタ F# の場合

Xamarin でアプリを作る言語と言ったら何でしょう? そう F# です!

F# を指定して Visual Studio のプロジェクト テンプレートを検索すると......。
F# を選んでるのに C# しか出てきません。

Xamarin だと元気に F# もあります。

無念ですが C# で行くしかないようです。

■ プロジェクトの新規作成

というわけで普通に C# でプロジェクトを作成します。




つまるところなく出来上がりました!

■ Android エミュレーターの実行

普通に実行します。



実行できました!
Visual Studio のデバッグ実行をするだけでもエミュレーターの起動 → デバッグ実行の開始、と動作しますが結構時間がかかります。時間がかかるタスクは分解していた方が、ちゃんと動いてるかな? と不安になりにくいのでお勧めです。

■ デバッグ実行

まずはそのまま。素材の味を楽しむために一切の編集無しにデバッグ実行してみます。



動きました! テンプレートで新規作成してそのままデバッグ実行できるのはすばらしいですね! すばらしいですね!

■ 余談 Xamarin の場合

Xamarin の場合どんな感じか見てみましょう。
プロジェクト テンプレートから選択すればよいのですが、実は Xamarin が入っていない Visual Studio でもプロジェクトテンプレートに出てきたりします。

Xamarin を追加します。

プロジェクト テンプレートから選択して進めてみます。ここで、Xamarin.Forms ではなく Xamarin が選択できていることに注目してください!
MAUI は Xamarin.Forms の進化です。今のところ Xamarin に相当するもの ( Xamarin.Forms を使わない純粋な Xamarin ) は MAUI 世代では 作れないのかもしれません。




できました!

そのまま実行も OK です。すばらしいですね。

ここで、MAUI のデフォルトと比較してみましょう。だいぶリッチになっていますね。

■ DavDays ハンズオンの今

ようやく本題に入って DavDays ハンズオンの話が始められます。

DevDays ハンズオンは半完成品からの作業という特徴があります。その特徴が後々厳しいことになるのですが実はそんなことは大したことではありません。
まずは日本語コンテンツを見てみましょう。 github.com

半完成コードをダウンロードして始めることが書いてあります。

次に半完成コードをダウンロードする本家英語コンテンツがこちらです。
github.com

この二つのコンテンツがあることが大きな問題になります。そのコンテンツの最終変更日を比較します。

おわかりいただけただろうか?

コンテンツ 更新日
日本語 2018/09/12
本家英語 2019/12/15

1 年と 3 か月も日本向けに用意されていたコンテンツが整備されていないことが分かりました。この日本語コンテンツのメンテナンスが行われていない間に本家英語のコンテンツが大きくつくりかえられており、日本語のテキストは全く実体と違ってしまっています。

結論その 1

何度もお世話になってきた日本語コンテンツですが、日本語テキストは古いので英語の本家でやっていきましょう。偉い人などに MAUI を推す際には英語本家のリポジトリを伝えるのが良いと思います。大丈夫。今のご時世なら「公式情報は英語が基本です」で納得いただけるでしょう。

■ 本家英語コンテンツでやっていく

まずはソースをダウンロードします。

3 年前のコンテンツだが、開いてビルド実行できます。素晴らしい。 ( ※ Xamarin の開発環境が必要です。 )

■ フォルダ構成

このダウンロードしたソースは Xamarin のプロジェクトなので、クラスファイルなどを MAUI で新規作成したプロジェクトに順次移さなければなりません。その際、Xamarin と MAUI でフォルダー構成が異なっている点をしなければなりません。
( ※ この辺りで入門用としては使えない事実から逃れられません。もしこのコンテンツで MAUI を学習できる世界が欲しければ、我々で MAUI 用に移植をする必要があるでしょう )

Xamarin で作れている半完成コードのフォルダ構成

MAUI で新規作成したプロジェクトのフォルダ構成

■ Speaker クラス

ハンズオン手順で最初に出てくるのが Model クラスの Common/DevDaysSpeakers.Shared/Models/Speaker.cs です。

これは MAUI では クラスライブラリプロジェクト Shared を作って ../Shared/Models/Speaker.cs とするが良いと思います。

ここで、なぜ日本でハンズオンが行われていた時にはなかった共有プロジェクトがあるかというと、Azure Functions を使うように変更されているからです。Azure Functions とモデルを共有できることを示すためでしょう。

その他同じように読み替えるクラス

Xamarin MAUI
Common/DevDaysSpeakers.Shared/Models/Speaker.cs ../Shared/Models/Speaker.cs
Mobile/DevDaysSpeakers/ViewModels/SpeakersViewModel.cs ViewModels/SpeakersViewModel.cs
Mobile/DevDaysSpeakers/Views/SpeakersPage.cs Views/SpeakersPage.cs
Mobile/DevDaysSpeakers/Views/SpeakersCell.cs Views/SpeakersCell.cs
Mobile/DevDaysSpeakers/Views/DetailsPage.cs Views/DetailsPage.cs
Mobile > DevDaysSpeakers > Services > AzureService.cs Services/AzureService.cs

ただソースコードファイルを持ってくるだけだと、ファイルのフォルダパスと namespace が対応しなくなりますが大丈夫です。そのままで行ける。コピーしてきたコードと新しく作った別のクラスなどで整合性が取れていなくなることもありますが、そこはアドリブできる力があれば雑にやって大丈夫です。概ね Visual Studio の「 ctrl + .」での修正機能を使えばうまくできるでしょう。

■ Azure Functions 関連

コンテンツの手順では Azure Functions も作り、Xamarin からそれを呼び出すというものになっていました。Xamarin と Azure の連携を体験して欲しかったのだと思います。
しかし今回は Xamarin → MAUI が目的なので Azure Functions は作らない方向で行きたいと思います。作らなくても大丈夫なので。

なぜ作らなくて大丈夫かというと、デモ用の Azure Functions が未だに活きているようだからです。実は半完成コードをダウンロードした際にフォルダが分かれて完成コードも .zip に含まれています。 Finish というフォルダの中です。この中に書かれている URL がデモ用なようでいまだに動作しているようです。Finish の中から URL を取り出して使いましょう。

■ namespace や using

もともと半完成コードに namespace や using が既に書かれているため、手順のテキストでは namespace や using に触れられていないクラスの編集手順があります。それらは半完成コードからアドリブでコピーして持ってくる必要があります。
その際、その時点で手順に出てこないファイル ( もとでは半完成のため存在するが MAUI では自分で作らなければならない。まだ作っていない ) の namespace が using されていることがあるので一時的にコメントにしておく等も必要になります。

■ NuGet パッケージ

もともとのコードでは Json.NET ( Newtonsoft.Json ) が使われています。以前はほぼ基本ライブラリ扱いのパッケージでどんなアプリでも使っていたライブラリです。しかし現在の .NET ではその地位を別のものに譲っていて新規作成した MAUI プロジェクトではインストールされていません。MAUI プロジェクトでは手動で nuget から追加が必要になります。

■ Xamarin.Forms の参照

もともとのコードでは当然ですが Xamarin.Forms を使っているので、何か所かで using されています。この using は消す必要があります。

using  Xamarin.Forms;

もコメントにします。

ここで、この using を消すだけで良いというところが大きなポイントです。Xamarin.Forms と MAUI のコントロールは namespace の違いだけでクラス名が同じものが大半だという点です。これは移行が捗る要因です。

■ nullable の警告

nullable の警告が出る場所があります。これは .NET の世代が上がったことでプロジェクトのデフォルト設定が変わったためです。警告なので無視可能です。今回は無視します。

■ ページ

ページも自分で作らなければなりません。ここで嬉しいのが、コードで画面を作るようになっている部分です。xaml を使っていないということです。つまりコードを半完成コードから MAUI に持ってくるのが楽です。MAUI でもコードで UI を作ることは Xamarin.Forms のころと変わらず可能です。
ページをプロジェクトに追加するのも簡単。

■ クラスのフィールド

クラスのフィールドがすでにか書かれていて手順に出てこないことがあります。これも半完成コードからコピペして持ってくる案件です。

■ クラスの出てくる順番

手順で出てくるたびにクラスを作っていくと途中でコンパイルが通らない場面が出てきます。後方の手順で出てくるクラスがコードに出てくることがあるためです。
半完成コードだとすべてのクラスが最初からコード上に存在していますが、MAUI で新規に作ったプロジェクトには自分で作らなければ存在しません。手順を先回りしてクラスを作らないとコンパイルが通らないタイミングが出てきます。
うまく把握して半完成コードから都度コピペで持ってくる案件です。

■ global using

前述の using Xamarin.Forms; を消すだけで MAUI のコントロールが使える理由ですが、これは MAUI のプロジェクトでは global using という機能が使われるためです。この global using とは、一か所で using を書くだけですべての C# ソースコードファイルで using したものと同じ状態になるという素敵機能です。

Xamarin.Forms.ImageCell というコントロールクラスを使う場面で using Xamarin.Forms; がされていてコンパイルがエラーとなる場面が出てきます。MAUI では Microsoft.Maui.Controls.ImageCell なので本当は using Microsoft.Maui.Controls; が必要なはずですが MAUI プロジェクトでは不要です。
MAUI プロジェクトでは次の global using のコードが自動生成され有効になります。

global using global::Microsoft.Extensions.DependencyInjection;
global using global::Microsoft.Maui;
global using global::Microsoft.Maui.Accessibility;
global using global::Microsoft.Maui.ApplicationModel;
global using global::Microsoft.Maui.ApplicationModel.Communication;
global using global::Microsoft.Maui.ApplicationModel.DataTransfer;
global using global::Microsoft.Maui.Authentication;
global using global::Microsoft.Maui.Controls;
global using global::Microsoft.Maui.Controls.Hosting;
global using global::Microsoft.Maui.Controls.Xaml;
global using global::Microsoft.Maui.Devices;
global using global::Microsoft.Maui.Devices.Sensors;
global using global::Microsoft.Maui.Dispatching;
global using global::Microsoft.Maui.Graphics;
global using global::Microsoft.Maui.Hosting;
global using global::Microsoft.Maui.Media;
global using global::Microsoft.Maui.Networking;
global using global::Microsoft.Maui.Storage;
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;

■ namespace だけではない変更 Color クラス

ハンズオンのコードに Xamarin.Forms.Color 構造体が出てきます。これは、Microsoft.Maui.Graphics.Color クラスと Microsoft.Maui.Graphics.Colors クラスになりました。
これは納得の変更で、MAUI への移行に際ししっかりと検討され適切な変更がされているとこが分かります。色を表すクラスと、決まった色のインスタンスをフィールドに持つクラスに整理されています。

その結果ハンズオンのコードで出てくる Color.TransparentColors.Transparent と書き換える必要があります。少し分かりにくいので縦に並べておきます。
Color.Transparent
Colors.Transparent

■ スタートアップページを変更

これはかなり難易度の高いアドリブです。MAUI プロジェクトテンプレートからのプロジェクトの新規作成でスタートアップのページが作られていますが、半完成コードのページに変更する必要があります。
プロジェクトの新規作成時に作られたページに半完成コードをマージしても良いでしょうが、それはそれできちんと理解してのアドリブが必要です。 どうせアドリブするならきれいにコピペできるスタートアップページの変更で対処するのが良いと思います。MAUI でのスタートアップページ変更も学べますし。

AppShell.xaml ファイルを編集します。

編集前

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

    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" />

</Shell>

編集後

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

    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:SpeakersPage}"
        Route="MainPage" />

</Shell>

編集個所を分かりやすいように縦に並べておきます。
ContentTemplate="{DataTemplate local:MainPage}"
ContentTemplate="{DataTemplate local:SpeakersPage}"

■ avatar のリンク切れ

これはコードの問題ではないので、対処がないです。ハンズオンで作るリスト画面で各要素ごとにアバター画像が表示されるはずの部分があります。
このアバター画像がリンク切れになっています。デモデータの問題です。正直賞味期限切れのコンテンツなのでリストのデータが生きていただけでもありがたいところです。ここはこれまで学習させてくれたことに感謝をしつつ受け入れるしかありません。


■ しゃべってくれない

これは解決できませんでした。Xamarin のコードでもしゃべってくれませんでした。

こんなエラー

ここの 「 Speak 」 でエラーになります。

System.ArgumentException: 'Failed to initialize Text to Speech engine.'

このエラー、ネットの情報で AndroidManifest.xml に記述の追加が必要というものが多く見つかりますが、解決できませんでした。

デフォルトの AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>

追加する記述

    <queries>
        <intent>
            <action android:name="android.intent.action.TTS_SERVICE" />
        </intent>
    </queries>

記述追加後の AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
  <queries>
    <intent>
      <action android:name="android.intent.action.TTS_SERVICE" />
    </intent>
  </queries>
</manifest>

結論その 2

Microsoft Learn を使え。

docs が learn にドメイン変更になって公式情報として docs の URL を記載した情報の更新に皆様頭を悩ませている昨今ですが、以前からの Learn のコンテンツのことです。ここに MAUI の学習ラーニング パスがあります。
Learn は非常に公式で推されているコンテンツですので、MAUI の学習コンテンツもここが注力の場であることは想像に難くありません。実際、MAUI のコンテンツがちゃんとあります。

docs.microsoft.com

ここで、この画面、Microsoft による Xamarin 買収前後以前に何やら見覚えがある気がします。その見覚えは何でしょう? そう、このハンズオンです。
github.com

歴史と伝統のコンテンツです。DevDays ハンズオンでなくこちらが Learn の題材になっています。これは見ようによっては MAUI 採用時に偉い人に説明しやすい状況です。

「Microsoft が Xamrin を始めたころの入門コンテンツが未だに入門コンテンツとして生きている安定性」

とアッピールするといいと思います。偉い人は Write once, run forever. が大好きです。

簡単ですね

MAUI 入門、簡単ですね。