App Center で自動ビルド、UIテストが試せる iOS, Android のサンプルアプリです。
Cognitive Services の Translator Text API を利用して、入力した日本語を英語に翻訳してくれます。
- Visual Studio for Mac がインストールされたMac
- Azure のアカウント
- App Center のアカウント
- Apple Developer Program への加入必要
- iOS11 以上の iPhone 実機
- Apple Developer Program への加入は不要
- (必須ではないがあると望ましい) Android 7.0 以上の Android 実機
Azure ポータルにログインし、「新規」 -> 「translate」 で検索します。
Translator Text API を選択します。
「作成」をクリックします。
項目を入力して「作成」をクリックします。
価格レベルは必ず「F0」(無料)にしてください!
作成した Translator Text API を開いて Key をコピーし保管しておいて下さい。
https://github.com/TomohiroSuzuki128/XamAppCenterSample2018/
にアクセスしてソリューションを clone または zip ダウンロードしてください。
/src/Start/XamAppCenterSample2018.sln を開きます。
/XamAppCenterSample2018/Variables.cs ファイルを開きます。
先ほど作成した API の Key を記述します。
注意:API Key を記述したソースをパブリックなリポジトリにコミットしないで下さい。
まず、ViewModel を作成しましょう。
/XamAppCenterSample2018/ViewModels/MainViewModel.cs ファイルを作成します。
まずは、using を追加します。
using MvvmCross.Commands;
using MvvmCross.ViewModels;
using XamAppCenterSample2018.Services.Interfaces;
MainViewModel を MvxViewModel の派生とします。
public class MainViewModel : MvxViewModel
画面には、「翻訳したい日本語入力欄」「翻訳された英語表示欄」「英語に翻訳するボタン」の要素があります。
これらを入力欄、表示欄はプロパティ、ボタンはコマンドとして実装してきます。
string inputText = string.Empty;
public string InputText
{
get => inputText;
set => SetProperty(ref inputText, value);
}
string translatedText = string.Empty;
public string TranslatedText
{
get => translatedText;
set => SetProperty(ref translatedText, value);
}
public IMvxAsyncCommand TranslateCommand { get; private set; }
コンストラクタでコマンドの処理を実装します。
DI された Service のメソッドをコールするようにします。
public MainViewModel(ITranslateService translateService) : base()
{
TranslateCommand = new MvxAsyncCommand(async () =>
{
TranslatedText = await translateService.Translate(InputText);
});
}
これで、ViewModelは完成です。
完成したコードは以下のようになります。
using MvvmCross.Commands;
using MvvmCross.ViewModels;
using XamAppCenterSample2018.Services.Interfaces;
namespace XamAppCenterSample2018.ViewModels
{
public class MainViewModel : MvxViewModel
{
string inputText = string.Empty;
public string InputText
{
get => inputText;
set => SetProperty(ref inputText, value);
}
string translatedText = string.Empty;
public string TranslatedText
{
get => translatedText;
set => SetProperty(ref translatedText, value);
}
public IMvxAsyncCommand TranslateCommand { get; private set; }
public MainViewModel(ITranslateService translateService) : base()
{
TranslateCommand = new MvxAsyncCommand(async () =>
{
TranslatedText = await translateService.Translate(InputText);
});
}
}
}
iOS のアプリの バンドル識別子 を御自身の固有のものに変更して下さい。
- アプリケーション名 は XamAppCenterSample2018 にして下さい。
- バンドル識別子の Organization Identifier の部分は全世界で固有となるような文字列にして下さい。
iOS の View を作成します。
storyborad、xib は、IDEによって更新部分以外も勝手にコードが更新され、 Git との相性が悪いので、今回はコードで UI を記述します。
/OS/Views/MainView.cs ファイルを作成します。
まずは、using を追加します。
using System;
using UIKit;
using Foundation;
using CoreGraphics;
using MvvmCross.Binding.BindingContext;
using MvvmCross.Platforms.Ios.Presenters.Attributes;
using MvvmCross.Platforms.Ios.Views;
using XamAppCenterSample2018.ViewModels;
MainView を MvxViewController の派生とし、属性を設定します。
[Register("MainView")]
[MvxRootPresentation(WrapInNavigationController = false)]
public class MainView : MvxViewController<MainViewModel>
フォントサイズや UI エレメントのフィールドを定義します。
static readonly nfloat fontSize = 20;
UILabel inputLabel;
UITextView inputText;
UIButton translateButton;
UILabel translatedLabel;
UITextView translatedText;
UI エレメントを初期設定するメソッドを定義します。
void InitUI()
{
}
InitUI の中に UI エレメントの設定値を記述していきます。
画面には、「翻訳したい日本語のラベル」「翻訳したい日本語の入力欄」「翻訳された英語のラベル」「翻訳された英語の表示欄」「英語に翻訳するボタン」の要素があります。
MainView 自体の設定値です。
View.ContentMode = UIViewContentMode.ScaleToFill;
View.LayoutMargins = new UIEdgeInsets(0, 16, 0, 16);
View.Frame = new CGRect(0, 0, 375, 667);
View.BackgroundColor = UIColor.White;
View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
「翻訳したい日本語ラベル」inputLabel の設定値と View への追加、制約の設定です。
inputLabel = new UILabel
{
Frame = new CGRect(0, 0, 375, 20),
Opaque = false,
UserInteractionEnabled = false,
ContentMode = UIViewContentMode.Left,
Text = "翻訳したい日本語",
TextAlignment = UITextAlignment.Left,
LineBreakMode = UILineBreakMode.TailTruncation,
Lines = 0,
BaselineAdjustment = UIBaselineAdjustment.AlignBaselines,
AdjustsFontSizeToFitWidth = false,
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(fontSize),
};
View.AddSubview(inputLabel);
inputLabel.HeightAnchor.ConstraintEqualTo(20).Active = true;
inputLabel.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
inputLabel.TopAnchor.ConstraintEqualTo(View.TopAnchor, 70).Active = true;
inputLabel.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
inputLabel.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
「翻訳したい日本語の入力欄」inputText の設定値と View への追加、制約の設定です。
inputText = new UITextView
{
Frame = new CGRect(0, 0, 375, 200),
ContentMode = UIViewContentMode.ScaleToFill,
TranslatesAutoresizingMaskIntoConstraints = false,
KeyboardType = UIKeyboardType.Twitter,
Font = UIFont.SystemFontOfSize(fontSize),
AccessibilityIdentifier = "inputText",
};
inputText.Layer.BorderWidth = 1;
inputText.Layer.BorderColor = UIColor.LightGray.CGColor;
View.AddSubview(inputText);
inputText.HeightAnchor.ConstraintEqualTo(View.HeightAnchor, 0.3f).Active = true;
inputText.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
inputText.TopAnchor.ConstraintEqualTo(inputLabel.BottomAnchor, 5).Active = true;
inputText.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
inputText.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
入力完了時にソフトキーボードを閉じるボタンの設定です。
var toolBar = new UIToolbar
{
BarStyle = UIBarStyle.Default,
TranslatesAutoresizingMaskIntoConstraints = false,
};
toolBar.HeightAnchor.ConstraintEqualTo(40).Active = true;
toolBar.WidthAnchor.ConstraintEqualTo(View.Frame.Width).Active = true;
var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
var commitButton = new UIBarButtonItem(UIBarButtonSystemItem.Done);
commitButton.Clicked += (s, e) => View.EndEditing(true);
toolBar.SetItems(new UIBarButtonItem[] { spacer, commitButton }, false);
inputText.InputAccessoryView = toolBar;
「英語に翻訳するボタン」translateButton の設定値と View への追加、制約の設定です。
translateButton = new UIButton(UIButtonType.RoundedRect)
{
Frame = new CGRect(0, 0, 375, 20),
Opaque = false,
ContentMode = UIViewContentMode.ScaleToFill,
HorizontalAlignment = UIControlContentHorizontalAlignment.Center,
VerticalAlignment = UIControlContentVerticalAlignment.Center,
LineBreakMode = UILineBreakMode.MiddleTruncation,
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(fontSize),
AccessibilityIdentifier = "translateButton",
};
translateButton.SetTitle("英語に翻訳する", UIControlState.Normal);
View.AddSubview(translateButton);
translateButton.HeightAnchor.ConstraintEqualTo(40f).Active = true;
translateButton.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
translateButton.TopAnchor.ConstraintEqualTo(inputText.BottomAnchor, 20).Active = true;
translateButton.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
translateButton.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
「翻訳された英語のラベル」translatedLabel の設定値と View への追加、制約の設定です。
translatedLabel = new UILabel
{
Frame = new CGRect(0, 0, 375, 20),
Opaque = false,
UserInteractionEnabled = false,
ContentMode = UIViewContentMode.Left,
Text = "翻訳された英語",
TextAlignment = UITextAlignment.Left,
LineBreakMode = UILineBreakMode.TailTruncation,
Lines = 0,
BaselineAdjustment = UIBaselineAdjustment.AlignBaselines,
AdjustsFontSizeToFitWidth = false,
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(fontSize),
};
View.AddSubview(translatedLabel);
translatedLabel.HeightAnchor.ConstraintEqualTo(20).Active = true;
translatedLabel.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
translatedLabel.TopAnchor.ConstraintEqualTo(translateButton.BottomAnchor, 20).Active = true;
translatedLabel.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
translatedLabel.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
「翻訳された英語の表示欄」translatedText の設定値と View への追加、制約の設定です。
translatedText = new UITextView
{
Frame = new CGRect(0, 0, 375, 200),
ContentMode = UIViewContentMode.ScaleToFill,
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(fontSize),
AccessibilityIdentifier = "translatedText",
Editable = false,
};
translatedText.Layer.BorderWidth = 1;
translatedText.Layer.BorderColor = UIColor.LightGray.CGColor;
View.AddSubview(translatedText);
translatedText.HeightAnchor.ConstraintEqualTo(View.HeightAnchor, 0.3f).Active = true;
translatedText.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
translatedText.TopAnchor.ConstraintEqualTo(translatedLabel.BottomAnchor, 5).Active = true;
translatedText.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
translatedText.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
バインディングを設定するメソッドです。
void SetBinding()
{
var set = this.CreateBindingSet<MainView, MainViewModel>();
set.Bind(inputText).To(vm => vm.InputText);
set.Bind(translatedText).To(vm => vm.TranslatedText);
set.Bind(translateButton).To(vm => vm.TranslateCommand);
set.Apply();
}
ViewDidLoad で InitUI, SetBindingをコールします。
public override void ViewDidLoad()
{
base.ViewDidLoad();
InitUI();
SetBinding();
}
これで、iOS の View は完成です。
完成したコードは以下のようになります。
using System;
using UIKit;
using Foundation;
using CoreGraphics;
using MvvmCross.Binding.BindingContext;
using MvvmCross.Platforms.Ios.Presenters.Attributes;
using MvvmCross.Platforms.Ios.Views;
using XamAppCenterSample2018.ViewModels;
namespace XamAppCenterSample2018.iOS.Views
{
[Register("MainView")]
[MvxRootPresentation(WrapInNavigationController = false)]
public class MainView : MvxViewController<MainViewModel>
{
static readonly nfloat fontSize = 20;
UILabel inputLabel;
UITextView inputText;
UIButton translateButton;
UILabel translatedLabel;
UITextView translatedText;
public override void ViewDidLoad()
{
base.ViewDidLoad();
InitUI();
SetBinding();
}
void InitUI()
{
View.ContentMode = UIViewContentMode.ScaleToFill;
View.LayoutMargins = new UIEdgeInsets(0, 16, 0, 16);
View.Frame = new CGRect(0, 0, 375, 667);
View.BackgroundColor = UIColor.White;
View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
inputLabel = new UILabel
{
Frame = new CGRect(0, 0, 375, 20),
Opaque = false,
UserInteractionEnabled = false,
ContentMode = UIViewContentMode.Left,
Text = "翻訳したい日本語",
TextAlignment = UITextAlignment.Left,
LineBreakMode = UILineBreakMode.TailTruncation,
Lines = 0,
BaselineAdjustment = UIBaselineAdjustment.AlignBaselines,
AdjustsFontSizeToFitWidth = false,
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(fontSize),
};
View.AddSubview(inputLabel);
inputLabel.HeightAnchor.ConstraintEqualTo(20).Active = true;
inputLabel.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
inputLabel.TopAnchor.ConstraintEqualTo(View.TopAnchor, 70).Active = true;
inputLabel.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
inputLabel.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
inputText = new UITextView
{
Frame = new CGRect(0, 0, 375, 200),
ContentMode = UIViewContentMode.ScaleToFill,
TranslatesAutoresizingMaskIntoConstraints = false,
KeyboardType = UIKeyboardType.Twitter,
Font = UIFont.SystemFontOfSize(fontSize),
AccessibilityIdentifier = "inputText",
};
inputText.Layer.BorderWidth = 1;
inputText.Layer.BorderColor = UIColor.LightGray.CGColor;
View.AddSubview(inputText);
inputText.HeightAnchor.ConstraintEqualTo(View.HeightAnchor, 0.3f).Active = true;
inputText.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
inputText.TopAnchor.ConstraintEqualTo(inputLabel.BottomAnchor, 5).Active = true;
inputText.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
inputText.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
var toolBar = new UIToolbar
{
BarStyle = UIBarStyle.Default,
TranslatesAutoresizingMaskIntoConstraints = false,
};
toolBar.HeightAnchor.ConstraintEqualTo(40).Active = true;
toolBar.WidthAnchor.ConstraintEqualTo(View.Frame.Width).Active = true;
var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
var commitButton = new UIBarButtonItem(UIBarButtonSystemItem.Done);
commitButton.Clicked += (s, e) => View.EndEditing(true);
toolBar.SetItems(new UIBarButtonItem[] { spacer, commitButton }, false);
inputText.InputAccessoryView = toolBar;
translateButton = new UIButton(UIButtonType.RoundedRect)
{
Frame = new CGRect(0, 0, 375, 20),
Opaque = false,
ContentMode = UIViewContentMode.ScaleToFill,
HorizontalAlignment = UIControlContentHorizontalAlignment.Center,
VerticalAlignment = UIControlContentVerticalAlignment.Center,
LineBreakMode = UILineBreakMode.MiddleTruncation,
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(fontSize),
AccessibilityIdentifier = "translateButton",
};
translateButton.SetTitle("英語に翻訳する", UIControlState.Normal);
View.AddSubview(translateButton);
translateButton.HeightAnchor.ConstraintEqualTo(40f).Active = true;
translateButton.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
translateButton.TopAnchor.ConstraintEqualTo(inputText.BottomAnchor, 20).Active = true;
translateButton.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
translateButton.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
translatedLabel = new UILabel
{
Frame = new CGRect(0, 0, 375, 20),
Opaque = false,
UserInteractionEnabled = false,
ContentMode = UIViewContentMode.Left,
Text = "翻訳された英語",
TextAlignment = UITextAlignment.Left,
LineBreakMode = UILineBreakMode.TailTruncation,
Lines = 0,
BaselineAdjustment = UIBaselineAdjustment.AlignBaselines,
AdjustsFontSizeToFitWidth = false,
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(fontSize),
};
View.AddSubview(translatedLabel);
translatedLabel.HeightAnchor.ConstraintEqualTo(20).Active = true;
translatedLabel.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
translatedLabel.TopAnchor.ConstraintEqualTo(translateButton.BottomAnchor, 20).Active = true;
translatedLabel.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
translatedLabel.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
translatedText = new UITextView
{
Frame = new CGRect(0, 0, 375, 200),
ContentMode = UIViewContentMode.ScaleToFill,
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(fontSize),
AccessibilityIdentifier = "translatedText",
Editable = false,
};
translatedText.Layer.BorderWidth = 1;
translatedText.Layer.BorderColor = UIColor.LightGray.CGColor;
View.AddSubview(translatedText);
translatedText.HeightAnchor.ConstraintEqualTo(View.HeightAnchor, 0.3f).Active = true;
translatedText.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
translatedText.TopAnchor.ConstraintEqualTo(translatedLabel.BottomAnchor, 5).Active = true;
translatedText.LeftAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.LeftAnchor).Active = true;
translatedText.RightAnchor.ConstraintEqualTo(View.LayoutMarginsGuide.RightAnchor).Active = true;
}
void SetBinding()
{
var set = this.CreateBindingSet<MainView, MainViewModel>();
set.Bind(inputText).To(vm => vm.InputText);
set.Bind(translatedText).To(vm => vm.TranslatedText);
set.Bind(translateButton).To(vm => vm.TranslateCommand);
set.Apply();
}
}
}
では、ここでiOSのアプリを実機デバッグしてみましょう。
実機をお持ちの方はせっかくですから実機でデバッグしてみましょう。
お持ちでない方はシミュレータでデバッグしてみましょう。
XamAppCenterSample2018.iOS > Debug > [シミュレータの機種名] に設定します。
「デバッグの開始」を実行します。
アプリが起動します。
飜訳が動作すれば成功です。
実機をお持ちの方は、ここでiOSのアプリを実機デバッグしてみましょう。
iOSのアプリを実機デバッグするにはXcodeでダミーアプリを実行する必要があります。
プロビジョニングプロファイルや証明書の紐付けが自動で行われるようにXcodeでSwiftのダミーアプリを作成します。
[File]->[New]->[Project]でプロジェクトを作成します。
iOSのSingle View Applicationを選択し、[Next]を押します。
Product Name は XamAppCenterSample2018 にして下さい。
Organization Identifier は先ほど決めたものと同一のものにしてください。
[Next]を押します。
XamAppCenterSample2018Xcode
というフォルダを作成し、その中にプロジェクトを保存してください。
Bundle Identifier が正しく設定されているのを確認して下さい。
Signingの部分が自動で修正されて、Provisioning Profile と Signing Certificate の部分にエラーのアイコンが表示されてないことを確認してください。
左上のデバッグ実行の部分にご自分のiPhoneが認識されているのを確認してください。
全て確認できたらデバッグ実行します。
以下の表示が出たら、次の手順で実機の設定で開発元を信頼させます。
実機の設定アプリを開き[プロファイルとデバイス管理]を開きます。
デベロッパAPPに[Xcodeに設定したApple ID]が表示されていますのでタップします。
[Xcodeに設定したApple ID]を信頼をタップして信頼させます。
以下の表示が出た場合、ご自分の iPhoneの中 に XamAppCenterSample2018 と言う名前のアプリが既にインストールされているか確認し、インストールされている場合、アンインストールしてください。
再度、デバッグ実行し、無事アプリが起動して真っ白な画面が表示されたら成功です。
これで、Xcode でのダミーアプリ実行は完了です。
/XamAppCenterSample2018/XamAppCenterSample2018.iOS/Info.plist ファイルを開きます。
「バンドル識別子」の文字列を先ほど Xcode で設定した、Bundle Identifier と一字一句違わないように設定します。
XamAppCenterSample2018.iOS > Debug > [あなたのiPhone名] に設定します。
「デバッグの開始」を実行します。
アプリが起動します。
飜訳が動作すれば成功です。
Android のアプリのパッケージ名を御自身の固有のものに変更して下さい。
- アプリケーション名 は XamAppCenterSample2018 にして下さい。
/Droid/Resources/layout/Main.axml を開きます。
Android の View を作成します。 Android の axml は、Git との相性も問題がないので、そのまま axml に記述します。
「翻訳したい日本語ラベル」inputTextView を追加します。
<TextView
android:text="@string/input"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/inputTextView" />
「翻訳したい日本語の入力欄」inputText を追加します。
また、Binding も記述します。
local:MvxBind="[View のプロパティ名] [ViewModel のプロパティ名]" というフォーマットで記述します。
<EditText
android:inputType="textMultiLine"
android:gravity="top|left"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:lines="7"
local:MvxBind="Text InputText"
android:id="@+id/inputText" />
「英語に翻訳するボタン」translateButton を追加します。
また、Binding も記述します。
<Button
android:id="@+id/translateButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxBind="Click TranslateCommand"
android:text="@string/translate" />
「翻訳された英語のラベル」translatedTextView を追加します。
<TextView
android:text="@string/translated"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/translatedTextView" />
「翻訳された英語の表示欄」translatedText を追加します。
また、Binding も記述します。
<TextView
android:inputType="textMultiLine"
android:gravity="top|left"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:lines="7"
local:MvxBind="Text TranslatedText"
android:id="@+id/translatedText" />
これで、Android の View は完成です。
完成した axml は以下のようになります。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:id="@+id/mainLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="@string/input"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/inputTextView" />
<EditText
android:inputType="textMultiLine"
android:gravity="top|left"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:lines="7"
local:MvxBind="Text InputText"
android:id="@+id/inputText" />
<Button
android:id="@+id/translateButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxBind="Click TranslateCommand"
android:text="@string/translate" />
<TextView
android:text="@string/translated"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/translatedTextView" />
<TextView
android:inputType="textMultiLine"
android:gravity="top|left"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:lines="7"
local:MvxBind="Text TranslatedText"
android:id="@+id/translatedText" />
</LinearLayout>
/Droid/Resources/values/Strings.xml を開きます。
画面に表示する文字列リソースを設定します。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="input">翻訳したい日本語</string>
<string name="translate">英語に翻訳する</string>
<string name="translated">翻訳された英語</string>
<string name="app_name">XamAppCenterSample2018.Droid</string>
</resources>
アプリとしての基本動作は View と ViewModel で完成していますが、入力後にソフトキーボードを消す動作が抜けているので、コードビハインドに記述します。
/Droid/Views/MainActivity.cs を開きます。
まずは、using を追加します。
using Android.App;
using Android.Content;
using Android.Views;
using Android.Views.InputMethods;
using Android.OS;
using Android.Widget;
using MvvmCross.Platforms.Android.Views;
using MvvmCross.Platforms.Android.Binding;
using XamAppCenterSample2018.ViewModels;
MainActivity を MvxActivity の派生とします。
public class MainActivity : MvxActivity<MainViewModel>
UI エレメントのフィールドを定義します。
InputMethodManager inputMethodManager;
LinearLayout mainLayout;
EditText editText;
ソフトキーボードを消すメソッドを実装します。
void HideSoftInput()
{
inputMethodManager.HideSoftInputFromWindow(mainLayout.WindowToken, HideSoftInputFlags.NotAlways);
mainLayout.RequestFocus();
}
画面の何も無いところをタッチしたときに、ソフトキーボードを消すようにします。
public override bool OnTouchEvent(MotionEvent e)
{
HideSoftInput();
return false;
}
ボタンや翻訳後の文章表示部分をタッチしたときに、ソフトキーボードを消すようにします。
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
RequestWindowFeature(WindowFeatures.NoTitle);
SetContentView(Resource.Layout.Main);
editText = (EditText)FindViewById(Resource.Id.inputText);
mainLayout = (LinearLayout)FindViewById(Resource.Id.mainLayout);
inputMethodManager = (InputMethodManager)GetSystemService(Context.InputMethodService);
var button = (Button)FindViewById(Resource.Id.translateButton);
button.Click += (s, e) => HideSoftInput();
var textView = (TextView)FindViewById(Resource.Id.translatedText);
textView.Click += (s, e) => HideSoftInput();
}
これで、コードビハインドは完成です。
完成したコードは以下のようになります。
using Android.App;
using Android.Content;
using Android.Views;
using Android.Views.InputMethods;
using Android.OS;
using Android.Widget;
using MvvmCross.Platforms.Android.Views;
using MvvmCross.Platforms.Android.Binding;
using XamAppCenterSample2018.ViewModels;
namespace XamAppCenterSample2018.Droid
{
[Activity(Label = "XamAppCenterSample2018", MainLauncher = true, Icon = "@mipmap/icon")]
public class MainActivity : MvxActivity<MainViewModel>
{
InputMethodManager inputMethodManager;
LinearLayout mainLayout;
EditText editText;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
RequestWindowFeature(WindowFeatures.NoTitle);
SetContentView(Resource.Layout.Main);
editText = (EditText)FindViewById(Resource.Id.inputText);
mainLayout = (LinearLayout)FindViewById(Resource.Id.mainLayout);
inputMethodManager = (InputMethodManager)GetSystemService(Context.InputMethodService);
var button = (Button)FindViewById(Resource.Id.translateButton);
button.Click += (s, e) => HideSoftInput();
var textView = (TextView)FindViewById(Resource.Id.translatedText);
textView.Click += (s, e) => HideSoftInput();
}
public override bool OnTouchEvent(MotionEvent e)
{
HideSoftInput();
return false;
}
void HideSoftInput()
{
inputMethodManager.HideSoftInputFromWindow(mainLayout.WindowToken, HideSoftInputFlags.NotAlways);
mainLayout.RequestFocus();
}
}
}
では、ここで Android のアプリを実機デバッグしてみましょう。
実機をお持ちの方はせっかくですから実機でデバッグしてみましょう。
お持ちでない方はシミュレータでデバッグしてみましょう。
XamAppCenterSample2018.Droid > Debug > [シミュレータの機種名] に設定します。
「デバッグの開始」を実行します。
アプリが起動し、飜訳が動作すれば成功です。
実機をお持ちの方は、ここで Android のアプリを実機デバッグしてみましょう。
実機の開発者モードを有効にし、USBデバッグを有効にしないと実機デバッグができないので変更します。
「システム」をタップします。
「端末情報」をタップします。
「ソフトウェア情報」をタップします。
「ビルド番号」を連打します。
開発者モードが有効になりました。
「開発者向けオプション」をタップ。
「開発者向けオプション」を ON にし、「USBデバッグ」を ON にします。
これで実機デバッグの準備が整いました。
XamAppCenterSample2018.Droid > Debug > [あなたのAndroidデバイス名] に設定します。
アプリが起動します。
飜訳が動作すれば成功です。
テスト時にアプリを初期化するクラスを作成します。
まずは、using を追加します。
using Xamarin.UITest;
クラスを定義します。
namespace XamAppCenterSample2018.UITests
{
public class AppInitializer
{
}
}
アプリのインスタンスを初期化、アプリを開始するメソッドを定義します。
namespace XamAppCenterSample2018.UITests
{
public class AppInitializer
{
public static IApp StartApp(Platform platform)
{
if (platform == Platform.Android)
{
return ConfigureApp
.Android
.EnableLocalScreenshots()
.PreferIdeSettings()
.InstalledApp("<あなたのアプリのパッケージ名>")
.StartApp();
}
return ConfigureApp
.iOS
.EnableLocalScreenshots()
.PreferIdeSettings()
.InstalledApp("<あなたのアプリのbundle ID>")
.StartApp();
}
}
}
Android のアプリのパッケージ名は以下で確認できます。
iOS のアプリの bundle ID は以下で確認できます。
これで、AppInitializer は完成です。
完成したコードは以下のようになります。
using Xamarin.UITest;
namespace XamAppCenterSample2018.UITests
{
public class AppInitializer
{
public static IApp StartApp(Platform platform)
{
if (platform == Platform.Android)
{
return ConfigureApp
.Android
.EnableLocalScreenshots()
.PreferIdeSettings()
.InstalledApp("<あなたのアプリのパッケージ名>")
.StartApp();
}
return ConfigureApp
.iOS
.EnableLocalScreenshots()
.PreferIdeSettings()
.InstalledApp("<あなたのアプリのbundle ID>")
.StartApp();
}
}
}
テストコードを作成します。
テストコードはiOS, Android で共用します。
まずは、using を追加します。
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using Xamarin.UITest;
クラスを定義します。
namespace XamAppCenterSample2018.UITests
{
[TestFixture(Platform.Android)]
[TestFixture(Platform.iOS)]
public class Tests
{
}
}
フィールドを定義します。
IApp app;
Platform platform;
コンストラクターを定義します。
アプリの起動時に iOS, Android を指定するために、プラットフォームを保持しておきます。
public Tests(Platform platform)
{
this.platform = platform;
}
各テスト実行前にアプリを開始するメソッドを定義します。
[SetUp]
Attribute を付加するとテストメソッドの実行前に実行されます。
[SetUp]
public void BeforeEachTest()
{
app = AppInitializer.StartApp(platform);
}
翻訳が成功するシナリオのテストメソッドを定義します。
[Test]
Attribute を付加するとテストメソッドとして扱われます。app.Tap
で UI エレメントをタップします。c.Marked("inputText")
でタップするUI エレメントを指定します。Marked
で指定するキーは、iOS ではAccessibilityIdentifier
、Android ではandroid:id
で設定します。app.DismissKeyboard()
で、ソフトキーボードを消します。app.Query
で UI エレメントを検索します。Assert.AreEqual
で、UI エレメントに表示された翻訳後のテキストが正しいか確認しています。
[Test]
public async void SucceedTranslate()
{
await Task.Delay(2000);
app.Tap(c => c.Marked("inputText"));
await Task.Delay(2000);
app.EnterText("私は毎日電車に乗って会社に行きます。");
await Task.Delay(2000);
app.DismissKeyboard();
await Task.Delay(2000);
app.Tap(c => c.Button("translateButton"));
await Task.Delay(4000);
var elements = app.Query(c => c.Marked("translatedText"));
await Task.Delay(2000);
Assert.AreEqual("I go to the office by train every day.", elements.FirstOrDefault().Text);
}
翻訳したい日本語が未入力の為、翻訳が失敗するシナリオのテストメソッドを定義します。
StringAssert.Contains
で、UI エレメントに表示された翻訳後のテキストに指定された文字列が入っているか確認しています。
[Test]
public async void FailTranslate()
{
await Task.Delay(2000);
app.Tap(c => c.Button("translateButton"));
await Task.Delay(4000);
var elements = app.Query(c => c.Marked("translatedText"));
await Task.Delay(2000);
StringAssert.Contains("エラーコード: 400005", elements.FirstOrDefault().Text);
}
これで、テストコードは完成です。
完成したコードは以下のようになります。
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using Xamarin.UITest;
namespace XamAppCenterSample2018.UITests
{
[TestFixture(Platform.Android)]
[TestFixture(Platform.iOS)]
public class Tests
{
IApp app;
Platform platform;
public Tests(Platform platform)
{
this.platform = platform;
}
[SetUp]
public void BeforeEachTest()
{
app = AppInitializer.StartApp(platform);
}
[Test]
public async void SucceedTranslate()
{
await Task.Delay(2000);
app.Tap(c => c.Marked("inputText"));
await Task.Delay(2000);
app.EnterText("私は毎日電車に乗って会社に行きます。");
await Task.Delay(2000);
app.DismissKeyboard();
await Task.Delay(2000);
app.Tap(c => c.Button("translateButton"));
await Task.Delay(4000);
var elements = app.Query(c => c.Marked("translatedText"));
await Task.Delay(2000);
Assert.AreEqual("I go to the office by train every day.", elements.FirstOrDefault().Text);
}
[Test]
public async void FailTranslate()
{
await Task.Delay(2000);
app.Tap(c => c.Button("translateButton"));
await Task.Delay(4000);
var elements = app.Query(c => c.Marked("translatedText"));
await Task.Delay(2000);
StringAssert.Contains("エラーコード: 400005", elements.FirstOrDefault().Text);
}
}
}
では、これから App Center でビルドとテストを行います。
以下のコマンドで node.js がインストールされている確認できます。
node -v
以下のように表示されればインストールされていません。
-bash: node: command not found
インストールコマンドを実行します。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
インストールを実行するか確認されるので RETRUN します。
Press RETURN to continue or any other key to abort
インストールが終わるまで待ちます。
以下のように Xcode のコマンドラインツールなどもインストールされますので結構時間がかかります。
Downloading Command Line Tools (macOS High Sierra version 10.13) for Xcode
Downloaded Command Line Tools (macOS High Sierra version 10.13) for Xcode
Installing Command Line Tools (macOS High Sierra version 10.13) for Xcode
途中でパスワードを聞かれたら入力してください。
下記のように表示されればインストール終了です。
==> Installation successful!
==> Homebrew has enabled anonymous aggregate user behaviour analytics.
Read the analytics documentation (and how to opt-out) here:
https://docs.brew.sh/Analytics.html
==> Next steps:
- Run `brew help` to get started
- Further documentation:
https://docs.brew.sh
次にnodebrewをインストールします。
インストールコマンドを実行します。
brew install nodebrew
下記のように表示されればインストール終了です。
🍺 /usr/local/Cellar/nodebrew/1.0.0: 8 files, 38.4KB, built in 5 seconds
念のため、インストールが正常に完了したか確認します。
nodebrew -v
nodebrewのバージョン情報が表示されればインストール完了です。
nodebrew 1.0.0
Usage:
nodebrew help Show this message
nodebrew install <version> Download and install <version> (from binary)
nodebrew compile <version> Download and install <version> (from source)
nodebrew install-binary <version> Alias of `install` (For backword compatibility)
nodebrew uninstall <version> Uninstall <version>
nodebrew use <version> Use <version>
nodebrew list List installed versions
nodebrew ls Alias for `list`
nodebrew ls-remote List remote versions
nodebrew ls-all List remote and installed versions
nodebrew alias <key> <value> Set alias
nodebrew unalias <key> Remove alias
nodebrew clean <version> | all Remove source file
nodebrew selfupdate Update nodebrew
nodebrew migrate-package <version> Install global NPM packages contained in <version> to current version
nodebrew exec <version> -- <command> Execute <command> using specified <version>
Example:
# install
nodebrew install v8.9.4
# use a specific version number
nodebrew use v8.9.4
いよいよ node.js をインストールします。
インストールコマンドを実行します。
nodebrew install-binary latest
怒られてしまいました。
Fetching: https://nodejs.org/dist/v10.7.0/node-v10.7.0-darwin-x64.tar.gz
Warning: Failed to create the file
Warning: /Users/hiro128/.nodebrew/src/v10.7.0/node-v10.7.0-darwin-x64.tar.gz:
Warning: No such file or directory
0.0%
curl: (23) Failed writing body (0 != 1057)
download failed: https://nodejs.org/dist/v10.7.0/node-v10.7.0-darwin-x64.tar.gz
ディレクトリが無いようなので、mkdirをして、
mkdir ~/.nodebrew
mkdir ~/.nodebrew/src
リトライします。
nodebrew install-binary latest
下記のように表示されればインストール終了です。
Fetching: https://nodejs.org/dist/v10.7.0/node-v10.7.0-darwin-x64.tar.gz
######################################################################## 100.0%
Installed successfully
インストールされた node.js のバージョンを確認します。
nodebrew list
インストールされたバージョンと、現在有効なバージョンが表示されます。
v10.7.0
current: none
使用する node.js バージョンを有効化します。
nodebrew use v10.7.0
確認のためにlistを実行し、
nodebrew list
指定したバージョンに変わっていれば成功です。
v10.7.0
current: v10.7.0
以下のコマンドでnodeコマンドへパスをbashrcへ保存します。
echo 'export PATH=$PATH:/Users/<あなたのhome>/.nodebrew/current/bin' >> ~/.bashrc
node.js のバージョンを認します。
node -v
うまくいっている場合は、以下のようにバージョン情報が表示されます。
node -v
v10.7.0
ですが、以下のようなメッセージが出た場合は、うまくいってません。
node -v
-bash: node: command not found
先ほど「.bashrc」に書き込んだパスが読み込まれていないようなので、
「.bash_profile」にコマンドを記述する必要があります。
まず、.bash_profile が存在するかどうか確認します。
ls -la
存在しない場合、ファイル作成します。
touch .bash_profile
作成できたか確認しましょう
ls -la
open ~/.bash_profile
テキストエディタが開くので下記を記述します。
if [ -f ~/.bashrc ] ; then
. ~/.bashrc
fi
設定を反映させます。
source ~/.bash_profile
再度 node.js のバージョンを確認します。
node -v
うまくいっている場合は、以下のようにバージョン情報が表示されます。
node -v
v10.7.0
以上で、node.js のインストールが完了です。
では、次に App Center CLI をインストールします。
これをインストールしないとテストのアップロードなどができません。
コマンドラインから、以下のコマンドでインストール
npm install -g appcenter-cli
権限が無いと怒られて、パッケージのインストールに失敗する場合、下記の手順でnpmのデフォルトディレクトリの権限を変更します。
npm ディレクトリのパスを確認
npm config get prefix
(例)/usr/local が表示された場合、
npm ディレクトリのオーナーを自分のアカウントに変更
sudo chown -R <アカウント名> /usr/local/lib/node_modules
sudo chown -R <アカウント名> /usr/local/bin
sudo chown -R <アカウント名> /usr/local/share
インストールが終われば準備完了です。
ソリューションのソースコードを、VSTS, Github, Bitbucketのいずれかにプッシュし下さい。
Apple Developer Program のサイトで証明書(.cer)、Provisioning Profile(.mobileprovision) を作成し、ローカルの Mac の キーチェーンアクセス で 証明書(.p12) を作成します。
この方法についてはWeb上に情報がたくさんあるので、Webの情報を参考に行なってください。
作成した Provisioning Profile(.mobileprovision)、証明書(.p12)を保管しておきます。
ここからは、実機自動ビルド、シミュレータ自動ビルド共通の手順です。
App Center にログインし、右上の「add new」から「add new app」を選択
App Name, OS, Platform を入力、選択し、「Add new app」をクリック
「Build」を選択し、ソースコードをホストしたサービスを選択します。
「XamAppCenterSample2018」を選択します。
自動ビルドしたいブランチの設定アイコンを選択します。
ビルド設定を選択し、入力し、「Save & Build」を選択。
ビルドが始まるのでしばらく待ち、成功すれば完了です。
XamAppCenterSample2018.UITests を一度ビルドします。
先ほど Azure で作成した API Key をローカルのプロジェクトに記述します。
(API Key を含むソースをプッシュしないで下さい)
iOSプロジェクトを Debug で 実機ビルドに設定します。
「単体テスト」タブを開き、「アプリのテスト」->「Add App Project」
iOSプロジェクトを追加します。
「テストのデバッグ」を実行します。
完了したら、Finder でiOSプロジェクトのフォルダを見てみると、署名はされていませんが、ipaファイルが生成されています。
「Test」 -> 「new test run」をクリック
「Start 30-day trial」をクリック
iOS 11 のデバイスを選択
Test series, System language, Test frameworkを選択
画面に表示されたリファレンスを参考にコマンドを作成する。リファレンスには、--uitest-tools-dir が指定されていないが追加で指定する。
appcenter test run uitest --app <App Center のURLに表示されているアプリの名前>
--devices <デバイスのID> --app-path <ipaのパス> --test-series "master" --locale "ja_JP"
--build-dir <UITestがビルドされたディレクトリのパス> --uitest-tools-dir <test-cloud.exeのディレクトリのパス>
(例)
appcenter test run uitest --app "TomohiroSuzuki128/XamAppCenterSample2018iOS"
--devices 1b6ada99
--app-path "/Users/hiro128/Projects/XamAppCenterSample2018/src/iOS/bin/iPhone/Debug/device-builds/iphone10.2-11.3.1/XamAppCenterSample2018.iOS.ipa"
--test-series "master" --locale "ja_JP"
--build-dir "/Users/hiro128/Projects/XamAppCenterSample2018/src/UITests/bin/Debug/"
--uitest-tools-dir "/Users/hiro128/Projects/XamAppCenterSample2018/src/packages/Xamarin.UITest.2.2.4/tools"
コンソールで App Center にログインします
appcenter login
ブラウザに表示された認証コードをコンソールに入力します。
上で作成した、appcenter test run uitest コマンドを実行します。
テストが実行されます。
テストが成功すれば完了です。
App Center にログインし、右上の「add new」から「add new app」を選択
App Name, OS, Platform を入力、選択し、「Add new app」をクリック
「Build」を選択し、ソースコードをホストしたサービスを選択します。
「XamAppCenterSample2018」を選択します。
自動ビルドしたいブランチの設定アイコンを選択します。
ビルド設定を選択し、入力し、「Save & Build」を選択。
ビルドが始まるのでしばらく待ち、成功すれば完了です。
XamAppCenterSample2018.UITests を一度ビルドします。
先ほど Azure で作成した API Key をローカルのプロジェクトに記述します。 (iOSの時に記述していれば不要) (API Key を含むソースをプッシュしないで下さい)
Androidプロジェクトを Release で シミュレータビルドに設定します。 「発行のためのアーカイブ」を実行します。
ビルドが完了したら、Finder でAndroidプロジェクトのフォルダを見てみると、署名はされていませんが、apkファイルが生成されています。
「Start 30-day trial」をクリック(表示された場合のみ)
Test series, System language, Test frameworkを選択
画面に表示されたリファレンスを参考にコマンドを作成する。リファレンスには、--uitest-tools-dir が指定されていないが追加で指定する。
appcenter test run uitest --app <App Center のURLに表示されているアプリの名前>
--devices <デバイスのID> --app-path <apkのパス> --test-series "master" --locale "ja_JP"
--build-dir <UITestがビルドされたディレクトリのパス> --uitest-tools-dir <test-cloud.exeのディレクトリのパス>
(例)
appcenter test run uitest --app "TomohiroSuzuki128/XamAppCenterSample2018Droid" --devices c8376925 --app-path "/Users/hiro128/Projects/XamAppCenterSample2018/src/Droid/bin/Release/com.hiro128777.XamAppCenterSample2018.apk" --test-series "master" --locale "ja_JP" --build-dir "/Users/hiro128/Projects/XamAppCenterSample2018/src/UITests/bin/Debug/" --uitest-tools-dir "/Users/hiro128/Projects/XamAppCenterSample2018/src/packages/Xamarin.UITest.2.2.4/tools"
コンソールで App Center にログインします(まだログインしていない場合)
appcenter login
ブラウザに表示された認証コードをコンソールに入力します。
上で作成した、appcenter test run uitest コマンドを実行します。
テストが実行されます。