C#
初心者
Xamarin
スマホアプリ
学習記録

Xamarin開発準備~実行まで(二週目の学習)

モチベーションも大事だと思うので覚書として使用します。

二週目感想
二週目はXamarin開発準備~実行までの流れを掴みました。
重要なのは共通プロジェクトでDependencyService準備、各OSプロジェクトで実装し、xaml.csにインスタンス呼出、xamlで表示する流れだと思いました。
悩んで結局Xamarin公式のクイックスタート選んだけど、コピペしてるだけだという事に気付き、
色々な情報に躓きながら構文毎に何をしているか学習しながら1週間を終えました。
色々な情報とは特にXamarinは発展途上で更新が早く1ヶ月後には仕様が違う事がザラなので…
さらにクロスプラットフォームなので各人の環境が微妙に違うため情報が交錯するのは仕方ない!
それよりも思ったより多くのハイブリッド開発環境がある事に驚いた。
WebViewを通してCordova系のIonicという選択肢は魅力的でした。
Inject作業分なくなると思うと…ただWebをアプリに表示となると色々上限がありそうなのでやっぱりXamarinにすぐ戻ったけど、WEBのように扱う情報なら工数圧縮できるwebviewいいな~と思いました。
あとは、GoogleのFlutterやFacebookのReact Nativeは一度試さねばと思った次第です。


学習題材:英数字の電話番号に翻訳&通話アプリ
参考:Xamarin公式
参考:REGREX株式会社

準備

  • VisualStudio(Xamarin)とAndroid実機(Digno)+接続端子
    • PCでAndroidに合うUSBドライバをDL
    • VSでツール>AndroidSDKマネージャー>実機OSverに合うSDKをDL(実機の端末情報で確認する)
    • Androidでデベロッパー、USBデバッグ、ワイヤレス認証をON

作成

  • 新規プロジェクト>モバイルアプリ(名称:Phoneword)作成
    • .Androidのプロパティ>アプリケーション>ターゲット>実機に合わせる
    • .Androidのプロパティ>マニフェスト>ターゲット>SDKバージョン使用したコンパイルの使用
    • 一旦そのままビルドして正常ならOK!

編集

オブジェクト(ボタン)設置(.xaml)

構文詳細

今回は翻訳と通話ボタンで電話番号翻訳&通話アプリMainPage.xaml
※ XAMLファイル選択時(0x8000FFFFエラー吐く場合現状解決策無しなのでエンコード付きのエディタで開き解決)
  ※XAMLとはHTMLと同じマークアップ言語(タグで囲んで文章や視覚表現をインスタンス化)
  ※ちなみにC#はプログラミング言語、SQLはデータベース言語
  ※APIはiOSがUIKit,Androidが無数にある
  ※ちなみにネイティブAPIとはネイティブが端末内で処理、webAPIがWEBサーバで処理
   (TwitterがWebAPI提供していて登録すればTogetterやTwibotを作れる)
  ※但し、アクセスキーやシークレットキー等は不正利用されるので成果物をGithub等であげる際は注意
  ※APIのIがインターフェイス(境界面)AがアプリPがプログラミング、アプリとマシンの境界面
  ※ちなみにUIはユーザーとマシンのインターフェイス
  ※またAPIはライブラリとは違う!ネイティブAPIでもライブラリ関数のみならずプロトコル
   (ケーブル~iPv4~P2P技術までネットワーク関連)関数も扱うし元々の根源違うから
<?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="Phoneword.MainPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="20, 40, 20, 20" />
            <On Platform="Android, UWP" Value="20" />
        </OnPlatform>
    </ContentPage.Padding>
    <StackLayout>
      <Label Text="Enter a Phoneword:" />
      <Entry x:Name="phoneNumberText" Text="1-855-XAMARIN" />
      <Button x:Name="translateButon" Text="Translate" Clicked="OnTranslate" />
      <Button x:Name="callButton" Text="Call" IsEnabled="false" Clicked="OnCall" />
    </StackLayout>
</ContentPage>

オブジェクトの条件定義(xaml.cs)

構文詳細

先程おいたOnTranslateOnCallメソッドの定義MainPage.xaml.cs

InitializeComponent ();名前付の再利用可能なインスタンス(下記翻訳と通話ボタン定義)(App.xaml.csに てここで定義された情報を使用する。必須のメソッド)
このApp.xaml.csとはActivityのライフサイクルのクラス←このサイクル理解してないと編集できない、アプリやってて縦画面のアプリを横画面にしたら止まったり変な画面なるのはこのせい!
一番分かりやすかった解説と図解


using System;
using Xamarin.Forms;

namespace Phoneword
{
    public partial class MainPage : ContentPage
    {
        string translatedNumber;

        public MainPage ()
        {
            InitializeComponent ();
        }

        void OnTranslate (object sender, EventArgs e)
        {
            translatedNumber = PhonewordTranslator.ToNumber (phoneNumberText.Text);
            if (!string.IsNullOrWhiteSpace (translatedNumber)) {
                callButton.IsEnabled = true;
                callButton.Text = "Call " + translatedNumber;
            } else {
                callButton.IsEnabled = false;
                callButton.Text = "Call";
            }
        }

        async void OnCall (object sender, EventArgs e)
        {
            if (await this.DisplayAlert (
                    "Dial a Number",
                    "Would you like to call " + translatedNumber + "?",
                    "Yes",
                    "No")) {
                var dialer = DependencyService.Get<IDialer> ();//各プラで生成したインスタンス呼出
                if (dialer != null)//実行結果条件
                    dialer.Dial (translatedNumber);//条件2
            }
        }
    }
}

オブジェクトのクラス追加と定義(.cs)

構文詳細

翻訳ボタンについて(英字を電話番号に変換)PhoneTranslator.cs

using System.Text;

namespace Phoneword
{
    public static class PhonewordTranslator
    {
        public static string ToNumber(string raw)
        {
            if (string.IsNullOrWhiteSpace(raw))
                return null;

            raw = raw.ToUpperInvariant();

            var newNumber = new StringBuilder();
            foreach (var c in raw)
            {
                if (" -0123456789".Contains(c))
                    newNumber.Append(c);
                else
                {
                    var result = TranslateToNumber(c);
                    if (result != null)
                        newNumber.Append(result);
                    // Bad character?
                    else
                        return null;
                }
            }
            return newNumber.ToString();
        }

        static bool Contains(this string keyString, char c)
        {
            return keyString.IndexOf(c) >= 0;
        }

        static readonly string[] digits = {
            "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"
        };

        static int? TranslateToNumber(char c)
        {
            for (int i = 0; i < digits.Length; i++)
            {
                if (digits[i].Contains(c))
                    return 2 + i;
            }
            return null;
        }
    }
}

オブジェクトのクラス追加とinterface定義(.cs)

構文詳細

電話ボタンについて(電話かける)interface使って共通コードIDialer実装IDialer.cs
namespace Phoneword
{
    public interface IDialer//インターフェイス定義
    {
        bool Dial(string number);インターフェイス定義
    }
}



以上、共通プロジェクト(オブジェクトとinterface定義完了これでDependencyService実装可能)

|>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━<|

以下、interface継承しiOSとAndroidにDependencyService(iDialer)登録、定義


iOSにiDialerのクラス追加と継承と定義(.cs)

構文詳細

電話ボタンについて(電話かける)iOSプロジェクトに先程interfaceにて定義した共通コードIDialer呼出実装PhoneDialer.cs
次項Androidのintentに対し、このiOSプロジェクトではinterfaceでIDialerにアクセスした。
using Foundation;
using Phoneword.iOS;
using UIKit;//
using Xamarin.Forms;

[assembly: Dependency(typeof(PhoneDialer))]
namespace Phoneword.iOS
{
    public class PhoneDialer : IDialer//先程定義したinterface IDialer実装
    {
        public bool Dial(string number)
        {
            return UIApplication.SharedApplication.OpenUrl (
                new NSUrl ("tel:" + number));
        }
    }
}

AndroidにiDialerのクラス追加と継承と定義(.cs)

構文詳細

電話ボタンについて(電話かける)継承させてintentでアクションセットしてくPhoneDialer.cs
   ※using System.Linq;//言語統合型クエリ Linq
   Language-Integrated(言語が統合された) Query(問い合わせ)
   クラスと同じくクエリ構文宣言してフィルタ処理等を利用(DB保管して他でも使えるように)
   SQLデータベース、ADO.NET データセット、XMLドキュメントとストリーム、.NET コレクション
   (コンテナ)内のデータを照会および変換。書き方は別学習にて

   ※URIはURLとURNの総称(URNは永続的xmlns="urn:schemas-microsoft-com:asm.v1")

※ついでに(アプリが電話かけるアクセス許可)Phoneword.Androidのプロパティ>マニフェスト>アクセス許可>CALL_PHONE

  

intentとは

主に画面遷移(明示的)とアクションだけ指定(暗黙的)
※intent(意思・故意) 地図やブラウザ、Dialer起動する
ここでは電話かけるためDialer起動のためにintent使う。
Intent を生成して、パラメータをセットして、(その Intent を処理できる Service あるいは
Activity があることを確認して、)これを投げることで電話をかける(iosの方法は違うので上の項で)

  ※アクティビティは画面、UIの上の階層表現、Androidにおけるクラスの1つ
メイン画面から次画面に移る(=メインアクティビティから次のアクティビティへと遷移する)
   クラスとしてのActivityは色々な用途に使われているためなるべくFragmentで複数張り付けが望ましい。

var intent=new intent(intent.アクション);
intent.SetData("識別キー")+;
context.StartActivity*(intent);
return true/false

1// Intent のインスタンス(値まで入れた実体クラス)を生成。
この時の第二引数に遷移先のクラス(.csの)を渡す
   var intent = new Intent(this,typeof(Activity1));
// StartACtivity メソッドを使いインテントを起動する。
   StartActivity(intent);

  例2Intent intent = new Intent(Settings.ACTION_DEVICE_INFO_SETTINGS);
  startActivity(intent);
  
  例3Intent intent = new Intent(AlarmClock.ACTION_SHOW_ALARMS);
    startActivity(intent);


今回の例
[assembly: Dependency(typeof(PhoneDialer))]

namespace Phoneword.Droid
{
    public class PhoneDialer : IDialer
    {
        public bool Dial(string number)
        {
            var context = MainActivity.Instance;
            if (context == null)
                return false;

            var intent = new Intent (Intent.ActionDial);
            intent.SetData (Uri.Parse ("tel:" + number));//setして(”識別キー”+値)

            if (IsIntentAvailable (context, intent)) {
         //setされた値はUriに変換されcontext, intentになる、それがAvailable(有効)なら実行
                context.StartActivity (intent);//実行
                return true;//true
            }

            return false;//それ以外全部false
        }

DependencyInjection(依存注入)とは

設計上依存関係にあるが各プラットフォームで個別に有する機能を、共通コード定義して扱うための必要になるインジェクター
それを実際のコーディングで定義、注入するのはDependencyService(使用する際メタデータ属性で登録する必要ある)
メタデータ属性は追加情報(メタデータ)を追加する仕組み(属性)
(youtubeの動画にタグをつける/登録する、本の出版元や著者を本というデータににつける/登録する、プログラミングだとクラスというデータにassemblyから展開したメタデータ属性をつける/登録する)
interfaceで共通コード定義したクラス(今回はiDialerクラス)が、以下iOSとAndroidでのクラスに依存させてる。
依存と継承の違いは何のか…?どちらも上が下に影響する。
依存注入使う際は[assembly: Dependency(typeof(<クラス名>))]を各platformの(クラス名.cs)に必須
usingとnamespaceの間にありDependencyの他に多くのinjectorある。
DependencyService.Get<IDeviceInfo>().GetUniqueIdentifier();
これで依存関係コードを見つけ使いたいInjectorをインターフェイスに実装
AutoFac(オートファクとりー)…その名の通り多くのInjector含むIoCコンテナ工場。
IoCコンテナとは、日本語で反転制御。例えば今回の場合本来依存させてるiDialerクラスをiosやAndroidクラスでも制御する概念?
クラスAに依存したBだが、汎用性あるコンテナBにする事で分離して単体テストできたりソーススッキリ?
とにかく依存注入と同じ感覚。実装はContainerBuilder _builder = new ContainerBuilder();
そしたら下記コードでビルダーに登録できる
_builder.RegisterInstance<IDeviceInfo>(new DeviceInfo()).SingleInstance();
三行目_builder.Build();//次は登録したクラスの使用だが必要になったらする
他に多くのフレームワークがあり、使う価値ある。
Xamarin Help

using Android.Content;//DBにデータを取得/保存する時に利用するクラス
using Android.Telephony;//アクセス情報/電話情報を監視/操作できるAPI
using Phoneword.Droid;
using System.Linq;//言語統合型クエリLanguage-Integrated(言語が統合された) Query(問い合わせ)
using Xamarin.Forms;
using Uri = Android.Net.Uri;//Android.NetクラスライブラリのUriクラス

[assembly: Dependency(typeof(PhoneDialer))]//メタデータ登録 継承
namespace Phoneword.Droid
{
    public class PhoneDialer : IDialer
    {
        public bool Dial(string number)//Intentここから 定義
        {
            var context = MainActivity.Instance;
            if (context == null)
                return false;

            var intent = new Intent (Intent.ActionDial);
            intent.SetData (Uri.Parse ("tel:" + number));

            if (IsIntentAvailable (context, intent)) {
                context.StartActivity (intent);
                return true;
            }

            return false;
        }//intentここまで

        public static bool IsIntentAvailable(Context context, Intent intent)
        {
            var packageManager = context.PackageManager;

            var list = packageManager.QueryIntentServices (intent, 0)
                .Union (packageManager.QueryIntentActivities (intent, 0));

            if (list.Any ())
                return true;

            var manager = TelephonyManager.FromContext (context);
            return manager.PhoneType != PhoneType.None;
        }
    }
}

Androidのメインアクティビティにinstance追加(.Android\MainActivity.cs)

構文詳細

Androidにメインアクティビティ(メイン画面)デザインのため(MainActivity.cs`)

mipmap/iconをマニフェストに登録
スタートアップ プロジェクトに登録
デバイス名 ▷←この三角ボタンで実行テスト

グローバル名前空間とは

最上位の名前空間であり、暗黙的に定義されている無名の名前空間
public class MyClass { //グローバル名前空間内のクラス
  public void MyMethod() {
    //////////
  }
}
namespace MyNamespace { // グローバル名前空間内の名前空間
  public class MyClass {
    public void MyMethod() {
      //////////
    }
  }
}

  • instansプロパティ追加のため編集を行ったがiOSでは特に必要ない。
    • チュートリアルでも記述なし、ちなみに最下行の保存は誤翻訳。
    • XmarinでMainActivity作成について質問
    • 具体的にMainActivityはMainActivity内はthisで取得、DepandencyServiceMainActivityを取得するには、MainActivityのOnCreateでstatic(クラス外呼出可能な)なグローバル変数にセット
    • 1行で呼び出す方法もあるらしいインスタンスを取得する方法

using Android.App;
using Android.Content.PM;
using Android.OS;

namespace Phoneword.Droid
{
    [Activity(Label = "Phoneword", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true,
              ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity//global::~が
    {
        internal static MainActivity Instance { get; private set; }

        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);
            Instance = this;
            global::Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication(new App());
        }
    }
}

一旦割愛UWPにオブジェクトのクラス追加と継承と定義(.cs)

構文詳細

UWPは(UniversalWindowsPlatform)要するに共通のWindows専用プラットフォーム
つまりAndroid、iOSに次いでWindowsOSの時代を目指す。Windowsストアに登録する事でリリースできる($19払いきり)
特徴はUWPのあるWindowsOSであればPCのみならずタブレットやモバイルでも動く点

Phoneword.UWPにクラスPhoneDialer.cs追加

using Phoneword.UWP;
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Calls;
using Windows.UI.Popups;
using Xamarin.Forms;

[assembly: Dependency(typeof(PhoneDialer))]
namespace Phoneword.UWP
{
    public class PhoneDialer : IDialer
    {
        bool dialled = false;

        public bool Dial(string number)
        {
            DialNumber(number);
            return dialled;
        }

        async Task DialNumber(string number)
        {
            var phoneLine = await GetDefaultPhoneLineAsync();
            if (phoneLine != null)
            {
                phoneLine.Dial(number, number);
                dialled = true;
            }
            else
            {
                var dialog = new MessageDialog("No line found to place the call");
                await dialog.ShowAsync();
                dialled = false;
            }
        }

        async Task<PhoneLine> GetDefaultPhoneLineAsync()
        {
            var phoneCallStore = await PhoneCallManager.RequestStoreAsync();
            var lineId = await phoneCallStore.GetDefaultLineAsync();
            return await PhoneLine.FromIdAsync(lineId);
        }
    }
}

結果

  • iOS関連エラーだけで問題無さげな感じになりました。

    • iOSはLivePlayerがサポートやめ今後もない&代わりのエミュもMac必須なので次は別の方法(DevOps)でテストしてみたいと思います。
    • App CenterはVSTSと統合しDevOpsになり、これにより①boardsでタスク管理②Pipelinesでクラウド上のOSでビルド・テスト③reposでレポジトリ管理できるようなり使い分け必要なくなった。githubもMS買収)
    • DevOps一択になりそうなので、これでまた準備~実行までしたいと思います。
  • 流れは把握したがまだ学習してない演算子やアクティビティのライフサイクルの理解の必要が出てきて、特に最後のアクティビティについてコード内最初の[Activityも、最後のglobal::も、最終的にInstanceプロパティにthisを何故代入したかったのか(これはMainActivity.cs編集だが画面作成と関係ない)を理解できないので次回のXmarin学習で解決したい。