tafuji's blog

C#, Xamarin, Azure DevOps を中心に書いています。

Xamarin.Essentials 入門 - #7 アプリ情報

はじめに

Xamarin.Essentials のアプリ情報について記載します。この API を利用することで、アプリケーション名やバージョン情報を取得することができます。

使ってみよう

使い方

Xamarin.Essentials のアプリ情報の機能は、AppInfo クラスで提供されており、以下のことができます。

  • ビルド
  • 名前
  • パッケージ名
  • バージョン文字列
  • アプリケーションの設定の表示
// アプリ名
var appName = AppInfo.Name;

// パッケージ名(Android)、Bundle Identifier (iOS)
var packageName = AppInfo.PackageName;

// アプリのバージョン文字列 (1.0.0)
var version = AppInfo.VersionString;

// アプリのビルド番号
var build = AppInfo.BuildString;

// アプリの設定画面を開く
AppInfo.ShowSettingsUI();

読んでみよう

AppInfo のコードを読んで、プラットフォーム固有の処理を見ていきます。

iOS

アプリ情報の取得には、NSBundle.MainBundle.ObjectForInfoDictionary を利用して、アプリの情報を取得していることが分かります。以下は、iOS プラットフォームのコードの抜粋です。

static string PlatformGetPackageName() => GetBundleValue("CFBundleIdentifier");

static string PlatformGetName() => GetBundleValue("CFBundleDisplayName") ?? GetBundleValue("CFBundleName");

static string PlatformGetVersionString() => GetBundleValue("CFBundleShortVersionString");

static string PlatformGetBuild() => GetBundleValue("CFBundleVersion");

static string GetBundleValue(string key)
    => NSBundle.MainBundle.ObjectForInfoDictionary(key)?.ToString();

設定画面の表示は、Xamarin.Essentials の Launcher を利用しています。

static async void PlatformShowSettingsUI()
    => await Launcher.OpenAsync(UIApplication.OpenSettingsUrlString);

Android

アプリ情報の取得には、Context から ApplicationInfoPackageManager を取得して、それぞれのメソッド、プロパティを利用して、アプリの情報を取得していることが分かります。以下は、Android プラットフォームのコードの抜粋です。

static string PlatformGetPackageName() => Platform.AppContext.PackageName;

static string PlatformGetName()
{
    var applicationInfo = Platform.AppContext.ApplicationInfo;
    var packageManager = Platform.AppContext.PackageManager;
    return applicationInfo.LoadLabel(packageManager);
}

static string PlatformGetVersionString()
{
    var pm = Platform.AppContext.PackageManager;
    var packageName = Platform.AppContext.PackageName;
    using (var info = pm.GetPackageInfo(packageName, PackageInfoFlags.MetaData))
    {
        return info.VersionName;
    }
}

static string PlatformGetBuild()
{
    var pm = Platform.AppContext.PackageManager;
    var packageName = Platform.AppContext.PackageName;
    using (var info = pm.GetPackageInfo(packageName, PackageInfoFlags.MetaData))
    {
#if __ANDROID_28__
        return PackageInfoCompat.GetLongVersionCode(info).ToString(CultureInfo.InvariantCulture);
#else
#pragma warning disable CS0618 // Type or member is obsolete
        return info.VersionCode.ToString(CultureInfo.InvariantCulture);
#pragma warning restore CS0618 // Type or member is obsolete
#endif
    }
}

なお、__ANDROID_28__ が指定されている部分は、API 28 以降の場合に実行されるコードです。API レベル 28 移行では、PackageInfo.versionCode が非推奨になったため、このようなコードが書かれています。

設定画面を開く機能では、Intent のアクションに Settings クラスの ActionApplicationDetailsSettings 定数(AndroidACTION_APPLICATION_DETAILS_SETTINGSに相当)を設定、データとしてアプリのパッケージ名を設定して、ContextStartActivity が利用されています。

static void PlatformShowSettingsUI()
{
    var context = Platform.GetCurrentActivity(false) ?? Platform.AppContext;

    var settingsIntent = new Intent();
    settingsIntent.SetAction(global::Android.Provider.Settings.ActionApplicationDetailsSettings);
    settingsIntent.AddCategory(Intent.CategoryDefault);
    settingsIntent.SetData(global::Android.Net.Uri.Parse("package:" + PlatformGetPackageName()));

    var flags = ActivityFlags.NewTask | ActivityFlags.NoHistory | ActivityFlags.ExcludeFromRecents;

#if __ANDROID_24__
    if (Platform.HasApiLevelN)
        flags |= ActivityFlags.LaunchAdjacent;
#endif
    settingsIntent.SetFlags(flags);

    context.StartActivity(settingsIntent);
}

UWP

アプリ情報の取得には、Windows.ApplicationModel.Package クラスを利用して、アプリの情報を取得していることが分かります。以下は、UWP プラットフォームのコードの抜粋です。

static string PlatformGetPackageName() => Package.Current.Id.Name;

static string PlatformGetName() => Package.Current.DisplayName;

static string PlatformGetVersionString()
{
    var version = Package.Current.Id.Version;
    return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}

static string PlatformGetBuild() =>
    Package.Current.Id.Version.Build.ToString(CultureInfo.InvariantCulture);

設定画面を開く機能では、Windows.System.Launcher.LaunchUriAsync を利用しています。引数に ms-settings:appsfeatures-app を渡して設定画面を開いています。

static void PlatformShowSettingsUI() =>
        Windows.System.Launcher.LaunchUriAsync(new System.Uri("ms-settings:appsfeatures-app")).WatchForError();

まとめ

  • Xamarin.Essentials のアプリ情報では、以下の機能が提供される
    • アプリケーションに関する情報(ビルド、名前、パッケージ名、バージョン文字列)を取得することができる
    • アプリケーションの設定画面を開くことができる
  • 内部では、以下のネイティブ固有のAPI を利用している
    • アプリ情報の取得(ビルド、名前、パッケージ名、バージョン文字列)
      • iOS : NSBundle.MainBundle.ObjectForInfoDictionary を利用して情報を取得している
      • Android : Context から取得した ApplicationInfoPackageManager を使って、各種情報を取得している
      • UWP : Windows.ApplicationModel.Package クラスのプロパティを利用している
    • 設定画面を開く
      • iOS : Xamarin.Essentials の Launcher を利用している設定画面を開いている
      • Android : Intent を作成して、ContextStartActivity を利用して設定画面を開いている
      • UWP : Windows.System.Launcher.LaunchUriAsyncms-settings:appsfeatures-app を渡して設定画面を開いている

参考

Xamarin.UITest Tips - Xamarin.Forms CarouselView

はじめに

この記事では、Xamarin.UITest を利用して、Xamarin.Forms の CarouselView を操作する方法を解説します。

CarouselView のサンプルコード

Xamarin.UITest で操作する SwipeView には、Microsoft の Xamarin.Forms のサンプルの「Horizontal layout Data Template」を利用します。

画面中央に表示されているサルが表示されているの項目を、特定の名前のサルが表示されるまでスワイプする操作を Xamarin.UITest で行います。

sample-app

Xamarin.UITest で CarouselView を操作する

CarouselView の UI 構造

Repl を利用して、UI の構造を調べてみると、サンプルの CarouselView は iOSAndroid で異なるクラスで UI が構築されていることがわかります。

プラットフォーム クラス名
iOS Xamarin_Forms_Platform_iOS_CarouselTemplatedCell
Android CarouselViewRenderer

Xamarin.UITest で操作をする場合には、このクラスに対してクエリを行う必要があります。 また、CarouselView の中の UI 構造も各プラットフォームで異なるため、特定の名前のサルが表示されているかどうかを判定するために取得する UI 要素も異なってきます。

Android の場合

Android の場合は、CarouselViewRenderer が持つ LabelRenderer の Text にサルの名前が表示されています。特定の名前のサルが表示されているかどうかは、LabelRenderer 要素を取得して判断することになります。

[CarouselViewRenderer > ... > Platform_DefaultRenderer] id: "NoResourceEntry-6"
  [FrameRenderer > Platform_DefaultRenderer] id: "NoResourceEntry-8"
    [LabelRenderer] id: "NoResourceEntry-9" text: "Baboon"
    [ImageRenderer] id: "NoResourceEntry-10"
    [LabelRenderer] id: "NoResourceEntry-11" text: "Africa & Asia"
    [LabelRenderer] id: "NoResourceEntry-12" text: "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae."

iOS の場合

iOS の場合は、Xamarin_Forms_Platform_iOS_CarouselTemplatedCell が持つ UILabel の Text にサルの名前が表示されています。特定の名前のサルが表示されているかどうかは、UILabel 要素を取得して判断することになります。

[Xamarin_Forms_Platform_iOS_CarouselTemplatedCell > ... > Xamarin_Forms_Platform_iOS_Platform_DefaultRenderer]
  [Xamarin_Forms_Platform_iOS_LabelRenderer]
    [UILabel] label: "Baboon",  text: "Baboon"
  [Xamarin_Forms_Platform_iOS_ImageRenderer > Xamarin_Forms_Platform_iOS_FormsUIImageView]
  [Xamarin_Forms_Platform_iOS_LabelRenderer]
    [UILabel] label: "Africa & Asia",  text: "Africa & Asia"
  [Xamarin_Forms_Platform_iOS_LabelRenderer]
    [UILabel] label: "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae.",  text: "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae."

CarouselView を操作するコード

ここでは、名前が Tonkin Snub-nosed Monkey というサルが表示するまで、CarouselView をスワイプするサンプルを記載します。

  • スワイプ操作には、IApp.SwipeRightToLeft 利用します。
  • 表示されるまでスワイプ操作を繰り返すには、IApp.WaitFor を利用します
    • このメソッドの引数に、CarouselView をスワイプして、表示されているサルの名前を取得し、表示したい名前のサルが表示されている場合に、処理を終了させます

サンプルは、以下のようになります。

Func<AppQuery, AppQuery> caroucelViewQuery;
string croucelViewClassName;
if(platform == Platform.Android)
{
    croucelViewClassName = "CarouselViewRenderer";
    caroucelViewQuery = x => x.Class(croucelViewClassName).Index(0).Descendant("LabelRenderer").Index(0);
}
else
{
    croucelViewClassName = "Xamarin_Forms_Platform_iOS_CarouselTemplatedCell";
    caroucelViewQuery = x => x.Class(croucelViewClassName).Index(0).Class("Xamarin_Forms_Platform_iOS_LabelRenderer").Index(1).Class("UILabel");
}

app.WaitFor(() =>
{
    var foundItem = false;
    while(!foundItem)
    {
        var carouselItemTitleLabel = app.Query(caroucelViewQuery).FirstOrDefault();
        if(carouselItemTitleLabel.Text == "Tonkin Snub-nosed Monkey")
        {
            foundItem = true;
        }
        else
        {
            app.SwipeRightToLeft(x => x.Class(croucelViewClassName).Index(0), swipeSpeed : 10000);
        }
    }
    return foundItem;
}, timeout: TimeSpan.FromMinutes(1));

参考資料

Xamarin.UITest Tips - Xamarin.Forms SwipeView

はじめに

この記事では、Xamarin.UITest を利用して、Xamarin.Forms の SwipeView を操作する方法を解説します。

SwipeView のサンプルコード

Xamarin.UITest で操作する SwipeView には、Microsoft の Xamarin.Forms のサンプルの「Swipe direction」を利用します。

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SwipeViewDemos.SwipeViewDirectionPage"
             Title="SwipeView swipe direction demo">
    <StackLayout Margin="20">
         <SwipeView>
            <SwipeView.RightItems>
                <SwipeItems>
                    <SwipeItem Text="Favorite"
                               IconImageSource="favorite.png"
                               BackgroundColor="LightGreen"
                               Invoked="OnFavoriteSwipeItemInvoked" />
                    <SwipeItem Text="Delete"
                               IconImageSource="delete.png"
                               BackgroundColor="LightPink"
                               Invoked="OnDeleteSwipeItemInvoked" />
                </SwipeItems>
            </SwipeView.RightItems>
            <Grid HeightRequest="60"
                  WidthRequest="300"
                  BackgroundColor="LightGray">
                <Label Text="Swipe left"
                       HorizontalOptions="Center"
                       VerticalOptions="Center" />
            </Grid>
        </SwipeView>
        <SwipeView>
            <SwipeView.LeftItems>
                <SwipeItems>
                    <SwipeItem Text="Favorite"
                               IconImageSource="favorite.png"
                               BackgroundColor="LightGreen"
                               Invoked="OnFavoriteSwipeItemInvoked" />
                    <SwipeItem Text="Delete"
                               IconImageSource="delete.png"
                               BackgroundColor="LightPink"
                               Invoked="OnDeleteSwipeItemInvoked" />
                </SwipeItems>
            </SwipeView.LeftItems>
            <Grid HeightRequest="60"
                  WidthRequest="300"
                  BackgroundColor="LightGray">
                <Label Text="Swipe right"
                       HorizontalOptions="Center"
                       VerticalOptions="Center" />
            </Grid>
        </SwipeView>
        <SwipeView>
            <SwipeView.BottomItems>
                <SwipeItems>
                    <SwipeItem Text="Favorite"
                               IconImageSource="favorite.png"
                               BackgroundColor="LightGreen"
                               Invoked="OnFavoriteSwipeItemInvoked" />
                    <SwipeItem Text="Delete"
                               IconImageSource="delete.png"
                               BackgroundColor="LightPink"
                               Invoked="OnDeleteSwipeItemInvoked" />
                </SwipeItems>
            </SwipeView.BottomItems>
            <Grid HeightRequest="60"
                  WidthRequest="300"
                  BackgroundColor="LightGray">
                <Label Text="Swipe up"
                       HorizontalOptions="Center"
                       VerticalOptions="Center" />
            </Grid>
        </SwipeView>
        <SwipeView>
            <SwipeView.TopItems>
                <SwipeItems>
                    <SwipeItem Text="Favorite"
                               IconImageSource="favorite.png"
                               BackgroundColor="LightGreen"
                               Invoked="OnFavoriteSwipeItemInvoked" />
                    <SwipeItem Text="Delete"
                               IconImageSource="delete.png"
                               BackgroundColor="LightPink"
                               Invoked="OnDeleteSwipeItemInvoked" />
                </SwipeItems>
            </SwipeView.TopItems>
            <Grid HeightRequest="60"
                  WidthRequest="300"
                  BackgroundColor="LightGray">
                <Label Text="Swipe down"
                       HorizontalOptions="Center"
                       VerticalOptions="Center" />
            </Grid>
        </SwipeView>
        <SwipeView>
            <SwipeView.LeftItems>
                <SwipeItems>
                    <SwipeItem Text="Delete"
                               IconImageSource="delete.png"
                               BackgroundColor="LightPink"
                               Invoked="OnDeleteSwipeItemInvoked" />
                </SwipeItems>
            </SwipeView.LeftItems>
            <SwipeView.RightItems>
                <SwipeItems>
                    <SwipeItem Text="Favorite"
                               BackgroundColor="LightGreen"
                               Invoked="OnFavoriteSwipeItemInvoked" />
                    <SwipeItem Text="Share"
                               BackgroundColor="LightYellow"
                               Invoked="OnShareSwipeItemInvoked" />
                </SwipeItems>
            </SwipeView.RightItems>
            <Grid HeightRequest="60"
                  WidthRequest="300"
                  BackgroundColor="LightGray">
                <Label Text="Swipe left or right"
                       HorizontalOptions="Center"
                       VerticalOptions="Center" />
            </Grid>
        </SwipeView>
    </StackLayout>
</ContentPage>

Xamarin.UITest で SwipeView を操作する

SwipeView の UI の構造

Repl を利用して、UI の構造を調べてみると、SwipeView は iOSAndroid で異なるクラスで UI が構築されていることがわかります。

プラットフォーム クラス名
iOS Xamarin_Forms_Platform_iOS_SwipeViewRenderer
Android SwipeViewRenderer

従って、Xamarin.UITest で UI 操作を行うコントロールを取得するには、各プラットフォームで異なるクラス名を利用して、Func<AppQuery, AppQuery> の式を作る必要があります。

左にスワイプする

IApp.SwipeLeftToRight メソッドを利用して、左にスワイプを行うことができます。

string className;
if(platform == Platform.iOS)
{
    className = "Xamarin_Forms_Platform_iOS_SwipeViewRenderer";
}
else
{
    className = "SwipeViewRenderer";
}
app.SwipeRightToLeft(x => x.Class(className).Index(0));

右にスワイプする

IApp.SwipeLeftToRight メソッドを利用して、左にスワイプを行うことができます。

string className;
if (platform == Platform.iOS)
{
    className = "Xamarin_Forms_Platform_iOS_SwipeViewRenderer";
}
else
{
    className = "SwipeViewRenderer";
}
app.SwipeLeftToRight(x => x.Class(className).Index(1));

上にスワイプする

IApp インタフェースには上にスワイプするメソッドがないので、SwipeView コントロールの座標を取得して、コントロールの中心座標から上の方向にIApp.DragCoordinates を利用してドラッグすることで、上方向にスワイプを行うことができます。

// SwipeView の座標と高さを取得する
string className;
if (platform == Platform.iOS)
{
    className = "Xamarin_Forms_Platform_iOS_SwipeViewRenderer";
}
else
{
    className = "SwipeViewRenderer";
}

var control = app.Query(x => x.Class(className).Index(2)).FirstOrDefault();
var centerX = control.Rect.CenterX;
var centerY = control.Rect.CenterY;
var height = control.Rect.Height;

// SwipeView の中心から高さの分だけ上方向にドラッグする
app.DragCoordinates(centerX, centerY, centerX, centerY - height);

下にスワイプする

IApp インタフェースには上にスワイプするメソッドがないので、SwipeView コントロールの座標を取得して、コントロールの中心座標から下の方向にIApp.DragCoordinates を利用してドラッグすることで、下方向にスワイプを行うことができます。

// SwipeView の座標と高さを取得する
string className;
if (platform == Platform.iOS)
{
    className = "Xamarin_Forms_Platform_iOS_SwipeViewRenderer";
}
else
{
    className = "SwipeViewRenderer";
}

var control = app.Query(x => x.Class(className).Index(3)).FirstOrDefault();
var centerX = control.Rect.CenterX;
var centerY = control.Rect.CenterY;
var height = control.Rect.Height;

// SwipeView の中心から高さの分だけ下方向にドラッグする
app.DragCoordinates(centerX, centerY, centerX, centerY + height);

参考資料