この記事では、 Notissimusのモジュラーアーキテクチャ上に構築されたアプリケーションの「デザイナー」の開発中に生じた興味深い問題とその解決策について学びます。 プロジェクトは活発に開発されているので、コメントであなたの意見を知ってうれしく思います。また、Xamarinの開発者向けの2016年の最終会議にも招待します。 猫の下で興味を持っているみんなにお願いします。

さらに、ナレーションは著者に代わって実施されます。
問題の声明
クライアントは何を望んでいますか?
クライアントは気まぐれな生き物なので、誰もが最終製品(アプリケーション)に対して独自の要件を持っています。 ただし、一般的なウィッシュリストは区別できます。
- 自分用に機能をカスタマイズします。
- デザインを編集または完全に置き換えます。
- ソースコードを所有します。
- /別のチームで開発を続けることができます。
これらの4つのウィッシュリストはクライアントごとに変わりませんが、表示/非表示になる場合があります。 ウィッシュリストを決定したら、単純なプログラマーにとってそれらが何を意味するかを理解する必要があります。
- モジュール性-プラグインの形で追加する特定の基本プロジェクトが必要です。
- 構成の柔軟性-ビジネスロジックとUIモジュールを再定義する機能が必要です。
- ソースコードのライセンスと保護-ソースをサイドに転送することが計画されているため、必須です。
ソリューションスキーム
タスクを決定したら、次のスキームを使用することにしました。
ソリューションのアーキテクチャ
基本モジュール
基本モジュールとは何ですか? まず、API、コア、UIの3つの主要な要素で構成される一種のアーキテクチャユニットです。 第二に、新しいモジュールの迅速なアセンブリと接続のためのすべてのベストプラクティスと基本要素を含む基本的なベースプロジェクト(たとえば、API、LookupService、ラッパーオーバーDB、ベースViewModel、UIViewControllersのベースクラスなど)。 したがって、各モジュールの基礎は、基本的なBaseモジュールの1つまたは複数の部分です。
基本モジュールの例は次のとおりです。
- 承認およびユーザー情報表示モジュール。
- チャットモジュール。
- お気に入りモジュール。
- 連絡先モジュール。
- ナビゲーションモジュール*;
- その他...
UI層でこのナビゲーションを処理するロジックは、選択したナビゲーションのタイプ(メニュー、タブ、またはその他)に大きく依存し、アプリケーションへのエントリポイントも依存するため、ナビゲーションモジュールは*を使用します。 ViewModelを起動します。これにより、アプリケーションが起動します。
トップレベルのモジュール
これらは、プロジェクトが開発されているビジネスセグメントに依存するモジュールです。 それらを個別のレイヤーに分離することが決定された理由は明らかですが、私たちはまだそれらをリストしています:
- ベースレイヤーモジュールはアンロードされ、真にユニバーサルになります。モジュール間で必要な相互作用のために異なる松葉杖を置く必要はありません。
- これらのモジュールは1つのセグメントに属しているため、あるモジュールから別のモジュールを参照することが可能になります。そのセグメント内では、それらは完全に普遍的で再利用できます
- この特定のセグメントに必要なロジックのみを記述する機会が得られます-モジュールとアプリケーションをアンロードします。サイズと速度を小さく保ちます。
そのようなモジュールの例は次のとおりです。
- カタログモジュール。
- カートおよびチェックアウトモジュール。
- 株式およびニュースモジュール。
- モジュールアドレスストア。
- その他...
カタログモジュールからバスケットに商品を追加する必要があり、バスケットモジュールに直接リンクせずに追加のラッパーを使用してこれを実装することは、便利な方法とは言えません。
プロジェクトを開始
これは、クライアントまたは開発者が対話できるプロジェクトです。 次のものが含まれます。
- 作業に必要なすべての接続モジュールおよびパッケージへのリンク。
- アプリケーションの設計で使用されるグラフィックのセット。
- フォントのセット。
- カラーパレット;
- ローカライズによるタイピング;
- クライアント設定のセット。
一般のユーザーがこのプロジェクトでできることは、仕様にガイドされています:
- アイコン/写真を変更します。
- フォントを変更します。
- テキストを変更します。
- 色を変更します。
このプロジェクトで開発者ができること:
- ユーザーと同じ。
- 構成の設定を変更します。
- 接続されたモジュールのロジックの一部を置き換えます(たとえば、ナビゲーションのタイプを変更します)。
- 追加のモジュールを追加します。
モジュールのアーキテクチャ
API
これはポータブルクラスライブラリです。iOSまたはAndroidを問わず、任意のプラットフォームで実行できるライブラリ(プロジェクト)コードです。 標準APIプロジェクトには、次のような要素が含まれます。
- サーバーから受信し、Coreで使用されるモデル。
- APIメソッドが呼び出されるサービス。
- プロジェクトに含まれるすべてのサービスの「レジストラ」。
public interface IAuthService { /// <summary> /// e-mail /// </summary> /// <returns> </returns> /// <param name="email">E-mail</param> /// <param name="password"></param> Task<string> SignIn(string email, string password); /// <summary> /// e-mail . /// </summary> /// <returns> </returns> /// <param name="email">E-mail</param> /// <param name="socialTypeName"> . </param> /// <param name="additionalFields"> </param> Task<string> SignInSocial(string email, string socialTypeName, Dictionary<string, object> additionalFields = null); /// <summary> /// e-mail /// </summary> /// <returns> </returns> /// <param name="email">E-mail</param> /// <param name="password"></param> /// <param name="additionalFields"> </param> Task<string> SignUp(string email, string password, Dictionary<string, object> additionalFields = null); /// <summary> /// /// </summary> /// <returns> </returns> /// <param name="email">E-mail</param> Task<string> RecoveryPassword(string email); /// <summary> /// /// </summary> /// <param name="token"> </param> Task SignOut(string token); } public class AuthService : BaseService, IAuthService { #region IAuthService implementation public async Task<string> SignIn(string email, string password) { return await Post<string>(SIGN_IN_URL, ToStringContent(new { email, password })); } public async Task<string> SignInSocial(string email, string socialTypeName, Dictionary<string, object> additionalFields = null) { return await Post<string>(SIGN_IN_SOCIAL_URL, ToStringContent(new { email, socialTypeName, additionalFields })); } public async Task<string> SignUp(string email, string password, Dictionary<string, object> additionalFields = null) { return await Post<string>(SIGN_UP_URL, ToStringContent(new { email, password, additionalFields })); } public async Task<string> RecoveryPassword(string email) { return await Post<string>(RECOVERY_PASSWORD_URL, ToStringContent(new { email })); } public Task SignOut(string token) { return Post(SIGN_OUT_URL, ToStringContent(new { token })); } #endregion }
プロジェクトにサービスを追加した後、登録するための追加手順は必要ありません;「レジストラ」は、次の行のおかげですべて自分で行います。
CreatableTypes() .EndingWith("Service") .AsInterfaces() .RegisterAsLazySingleton();
コア
これはPCLプロジェクトでもあり、MvvmCrossが提供する機会を使用して完全に構築されています。 標準のコアプロジェクトには、次の要素が含まれます。
- ViewModelのセット—画面の抽象化のみが可能です。ICommandインターフェイスの実装、単純なプロパティ、およびナビゲーションメソッド。
- VmService-特定のViewModelに関連付けられ、すべてのビジネスロジックを含むサービス。 このような各サービスは、たとえば次のような1つの機能を実行します。
public interface IMenuVmService { public IEnumerable BuildItemsFromJsonConfig(); } public class MenuVmService : IMenuVmService { public IEnumerable BuildItemsFromJsonConfig() { ... } }
- モデルとは、CoreおよびUIで使用される追加のモデルです(たとえば、ユーザーのダイアログや通知の作成に使用されるモデル)。 ほとんどの場合、Coreのモデルは、データベースが機能するオブジェクトのセットです。
- サービス—特定のサービスは、原則として、データベースを操作するためのサービス、または宣言されたサービスインターフェイスのみです(このようなサービスの実装は、現在のデバイスに関する情報を受け取る
IDeviceService
などのプラットフォームです)。 - メッセージは、Coreの無関係な部分間でやり取りする(たとえば、あるViewModelに別のアクションを通知する)ため、またはUIレイヤーからCoreにパラメーターを転送するため、またはその逆に使用されるメッセージです。
開発を開始する前に、Coreのロジックのほとんどを再定義でき、実装ごとに完全に置き換えることができることを説明しました。 また、IoCを介したサービスの置き換えですべてが明らかな場合、ViewModelsの置き換えですべてが明らかではありません。 「これを実装する方法は?」という疑問が生じました。 答えはViewModelLookupService
の実装でした。
ViewModelLookupService
これは、ViewModelインターフェースを介して実装を登録できるサービスです。 原則はIoCに似ており、VMインスタンスではViewModelLookupServiceのみが機能しません。 ナビゲーションはどうですか? 実際、ShowViewModel()VMメソッドは、表示するVMのタイプを取り込みます。 したがって、ビューモデルをサービスに登録すると、VMインターフェイスの種類とVM実装の種類に関する完全な情報が取得され、サービスに保存されます。 登録された実装を取得するためにサービスにアクセスするとき、保存されたデータにアクセスし、実装のタイプを返します。
これにより、configsでモデルの実装を設定できます。 例:
... "items": [ { "icon":"res:Images/Menu/catalog.png", "name":"", "type":"AppRopio.ECommerce.Products.Core.ViewModels.IProductsViewModel", "default":true }, { "icon":"res:Images/Menu/basket.png", "name":"", "type":"AppRopio.ECommerce.Basket.Core.ViewModels.IBasketViewModel", "badge":true }, { "icon":"res:Images/Menu/history.png", "name":" ", "type":"AppRopio.ECommerce.OrdersHistory.Core.ViewModels.IOrdersHistoryViewModel" }, { "icon":"res:Images/Menu/favorites.png", "name":"", "type":"AppRopio.ECommerce.Favorites.Core.ViewModels.IFavoritesViewModel" } ] ...
したがって、リストアイテムを設定できます:名前、VMタイプ、アイテムをクリックしてナビゲーションロジックを呼び出すときにViewModelLookupService
から取得しようとし、アイテムのバッジを設定し、アイテムの1つを開始画面として指定する必要があります。
ViewModelLookupService
の導入のおかげでViewModelLookupService
すべてのVMには独自のインターフェイスがあります。これにより、UIレイヤーでVMをバインドするときにロジックを置き換える機能を失うこともなくなります。 また、ViewModelLookupServiceでのViewModelLookupService
の実装の登録は、各モジュールの前提条件です。
RouterService
実際、 ViewModelLookupService
を介してMenuモジュールから移動するのはそれほど簡単でViewModelLookupService
ません。 このメカニズムを実装した後、ナビゲーションモジュールには、ナビゲートされる型への明示的なバインドがなく、メニュー項目にナビゲートする前にいくつかのロジックを実行できるようにする必要があると考えました(たとえば、メニューには個人アカウントまたは注文履歴、ユーザー認証の前にブロックする必要があります)。 したがって、RouterServiceメカニズムを開発することが決定されました。
RouterService
は、VMインターフェイスタイプRouterService
ナビゲーションを制御するサービスです。 彼の呼びかけは次のとおりです。
protected void OnItemSelected(IMenuItemVM item) { if (!RouterService.NavigatedTo(item.Type)) MvxTrace.Trace(MvvmCross.Platform.Platform.MvxTraceLevel.Error, "NavigationError: ", $"Can't navigate to ViewModel of type {item.Type}"); }
任意のタイプのナビゲーションイベントを処理するには、モジュールは、このタイプのIRouterSubscriber
実装をIRouterSubscriber
に登録する必要があります。これには、2つのメソッドのみが含まれます。
public interface IRouterSubscriber { bool CanNavigatedTo(string type); void FailedNavigatedTo(string type); }
サブスクライバーがitem.Type
型で登録されている場合、最初のメソッドはRouterService.NavigatedTo(...)
メソッド内でRouterService.NavigatedTo(...)
れます。 次に、最初のメソッドがfalseを返した場合、またはナビゲーションの他の段階でエラーが発生した場合。
最初のメソッドを実装するとき、サブスクライバーは自分に来た型を処理し、必要なチェックを実行し、合格した場合、 ViewModelLookupService
から登録済みのモデル実装型を取得してナビゲートする必要があります。そうでない場合はfalse
必要があります。 FailedNavigatedTo(...)
実装する場合、制限はありません。
したがって、キーポイントへのナビゲーションの処理はMenuモジュールから取り出され、任意のViewModelsおよび任意のロジックへのナビゲーションが許可されました(たとえば、メニュー項目をタップする場合、画面ではなく会社のWebサイトでのナビゲーションが必要です)
UI
レイヤーは、2種類のプロジェクトで構成されます。
- iOSクラスライブラリ
- Androidクラスライブラリ。
各プロジェクトには必ず次のものが含まれます。
- プラットフォームサービスインターフェイスの実装。
- ユーザーインターフェイス-抽象化によって構築された画面-ViewModel。
プラットフォームサービスの実装については、後ほど説明します。ユーザーインターフェイスの実装は、現在実行しているものと変わらないので、アプリケーションのさまざまなクライアント設定の使用について詳しく調べます。
設定には次の2つのタイプがあります。
- 構成-Coreの操作とモジュール内およびモジュール間の相互作用のロジックに影響を与えます(例は上記のMenuモジュールの構成です)。
- テーマ-モジュールのUIレイヤーのさまざまなコンポーネントのレンダリングに影響します。
設定ファイル自体は.jsonドキュメントです。 設定は、モジュールの起動時に開始される特別なサービスに一度読み込まれます。 構成設定は、ConfigService'yのコア、テーマ別-ThemeServicesのUIにロードされます。 ファイルからjsonをロードする手順はかなり標準的です。ただし、CoreはPCLです。つまり、そこにファイルを操作するツールはありません(.NET Standard 2.0を参照)。 これにより、特別なサービスISettingsService
が導入され、その実装は基本的なBaseモジュールのUIレイヤーにあり、ロジックが問題なく構成情報をロードできるようになりました。
新しいモジュールを開発し、既存のシステムに接続する段階
新しいモジュールを開発する前に、クライアントの個人アカウントからアプリケーションのソースコードを購入してダウンロードする必要があります。 したがって、すでに作成されたアーキテクチャと選択された設定を使用して、2つの起動されたプロジェクト(iOSおよびAndroid用)を備えたソリューションが得られます。 次に、既存のiOSアプリケーション用にゼロからフォトギャラリーモジュールを作成することのみを検討します。 モジュールは、デバイスのカメラから画像を受信し、サーバーに送信し、アルバムに保存して、コレクションに表示します。
アーキテクチャの作成
まず、便宜上、新しいソリューションフォルダーを作成し、フォトギャラリーと呼びます。 その後、3つのプロジェクトがこのフォルダーに追加されます。
- ポータブルライブラリ-Photogallery.API;
- ポータブルライブラリ-Photogallery.Core;
- iOSクラスライブラリ-Photogallery.iOS。
自動的に作成されたMyClass.cs
を削除し、次のリンクをプロジェクトに追加します。
- Photogallery.API-Base.API;
- Photogallery.Core-Base.Core + Photogallery.API;
- Photogallery.iOS-Base.iOS + Base.Core + Base.API + Photogallery.Core + Photogallery.API;
- XamarinMeetUp.iOS-Base.iOS + Base.Core + Base.API + Photogallery.iOS + Photogallery.Core + Photogallery.API
NuGetからMvvmCrossパッケージを各プロジェクトに接続することも必要です。
APIサービスの追加
写真を撮るとき、プラグインは写真をサーバーに送信して履歴を保存します(たとえば、公開用)。 これを行うには、この作業を実行するAPIプロジェクトにサービスを追加します。 プロジェクトにServicesフォルダーを作成し、 IPhotoService
インターフェイスを追加します。ここで、必要な機能を説明します。
public interface IPhotoService { Task SendPhoto(byte[] photoData); }
次に、サービスの実装を記述します。
public class PhotoService : BaseService, IPhotoService { private const string PHOTO_URL = "photo"; #region IPhotoService implementation public async Task SendPhoto(byte[] photoData) { await Post(PHOTO_URL, new ByteArrayContent(photoData)); } #endregion }
BaseモジュールのBase.APIプロジェクトにBaseService
が実装されているため、必要なURLでのクエリは1行だけで実行されます。 同様に、サーバーから写真を撮る方法の実装を追加できます。 APIエントリポイントは、起動されたプロジェクトの設定から取得され、すべてのリクエストのURLプレフィックスとして使用されます。 何らかの理由でPost(...)の実装がメソッドに合わない場合は、クエリサービスに直接連絡できます。
サービスを機能させるには、登録する必要があります。 これを行うには、APIプロジェクトでAppクラスを作成し、次のコードを記述します。
public class App : MvxApplication { public override void Initialize() { CreatableTypes() .EndingWith("Service") .AsInterfaces() .RegisterAsLazySingleton(); } }
ここでは、 Initialize
メソッドで、APIのすべてのサービスを、Coreパーツからの後続の呼び出しの遅延シングルトーンとして自動的に登録します。
ViewModelとそのサービスの作成
このモジュールでは、単純なVMを作成します。このVMには、ユーザーから受け取った写真のリストと、新しい写真を追加するボタンのみが含まれます。 Coreプロジェクトで、ViewModelsフォルダーをIPhotogalleryViewModel
フォルダー内に作成し、新しいIPhotogalleryViewModel
インターフェイスと新しいIPhotogalleryViewModel
クラスを追加しBaseViewModel
。これらは、インターフェイスとBaseViewModel
を継承しBaseViewModel
。
IPhotogalleryViewModelインターフェイスに次の行を追加します。
ObservableCollection<IPhotoItemVM> Items { get; set; } ICommand AddPhotoCommand { get; }
アイテム-表示される写真のリスト、AddPhotoCommand-新しい写真をコレクションに追加します。
すべての写真と新しい写真を取得するためのロジックのダウンロードは、インターフェイスを実装するサービスにあります。
public interface IPhotogalleryVmService { Task<ObservableCollection<IPhotoItemVM>> LoadItems(); Task<IPhotoItemVM> GetPhotoFromUser(); }
新しい写真VmService
取得VmService
ために、 VmService
はデバイスのカメラサービスにアクセスし、その実装は各プラットフォーム上にあり、アルバムから写真をアップロードするためにアルバムサービスにアクセスします。
public interface ICameraService { Task<byte[]> TakePhoto(); } public interface IPhotoAlbumService { Task<List<byte[]>> LoadPhotosFrom(string albumName); }
CoreおよびViewModel'iで利用可能なサービスを登録することのみが残ります(ビューモデルの登録は、その後の置換のために可能です)。 すべてはAPIとの類推によって行われます。App.csが作成され、Initializeメソッドが次のように再定義されます。
public override void Initialize() { (new API.App()).Initialize(); CreatableTypes() .EndingWith("Service") .AsInterfaces() .RegisterAsLazySingleton(); var vmLookupService = Mvx.Resolve<IViewModelLookupService>(); vmLookupService.Register<IPhotogalleryViewModel>(typeof(PhotogalleryViewModel)); }
iOSでのシンプルなレイアウトの開発とプラットフォームサービスの実装
まず、すべてのプラットフォームサービスを実装します。 カメラサービスから始めましょう。 iOSプロジェクトにServicesフォルダーを作成し、CameraServiceを追加します。
public class CameraService : ICameraService { public Task<byte[]> TakePhoto() { throw new NotImplementedException(); } }
public async Task<byte[]> TakePhoto() { var mediaFile = await CrossMedia.Current.TakePhotoAsync( new StoreCameraMediaOptions { DefaultCamera = CameraDevice.Rear }); var stream = mediaFile.GetStream(); var bytes = new byte[stream.Length]; await stream.ReadAsync(bytes, 0, (int)stream.Length); PHAssetCollection assetCollection = null; var userCollection = PHAssetCollection.FetchAssetCollections(PHAssetCollectionType.Album, PHAssetCollectionSubtype.Any, null); if (userCollection != null) assetCollection = userCollection.FirstOrDefault(nsObject => (nsObject as PHAssetCollection).LocalizedTitle == ALBUM_NAME) as PHAssetCollection; if (assetCollection == null) { string assetCollectionIdentifier = string.Empty; PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() => { var creationRequest = PHAssetCollectionChangeRequest.CreateAssetCollection(ALBUM_NAME); assetCollectionIdentifier = creationRequest.PlaceholderForCreatedAssetCollection.LocalIdentifier; }, (bool success, NSError error) => { assetCollection = PHAssetCollection.FetchAssetCollections(new[] { assetCollectionIdentifier }, null).firstObject as PHAssetCollection; PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() => { var assetChangeRequest = PHAssetChangeRequest.FromImage(UIImage.LoadFromData(NSData.FromArray(bytes))); var assetCollectionChangeRequest = PHAssetCollectionChangeRequest.ChangeRequest(assetCollection); assetCollectionChangeRequest.AddAssets(new[] { assetChangeRequest.PlaceholderForCreatedAsset }); }, (bool s, NSError e) => { }); }); } else { PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() => { var assetChangeRequest = PHAssetChangeRequest.FromImage(UIImage.LoadFromData(NSData.FromArray(bytes))); var assetCollectionChangeRequest = PHAssetCollectionChangeRequest.ChangeRequest(assetCollection); assetCollectionChangeRequest.AddAssets(new[] { assetChangeRequest.PlaceholderForCreatedAsset }); }, (bool success, NSError error) => { }); } return bytes; }
フォトアルバムを操作するためのサービスも追加します。
public class PhotoAlbumService : IPhotoAlbumService { public Task<List<byte[]>> LoadPhotosFrom(string albumName) { throw new NotImplementedException(); } }
public Task<List<byte[]>> LoadPhotosFrom(string albumName) { var photos = new List<byte[]>(); var tcs = new TaskCompletionSource<List<byte[]>>(); var userCollection = PHAssetCollection.FetchAssetCollections(PHAssetCollectionType.Album, PHAssetCollectionSubtype.Any, null); if (userCollection != null) { var meetUpAssetCollection = userCollection.FirstOrDefault(nsObject => (nsObject as PHAssetCollection).LocalizedTitle == "Xamarin MeetUp") as PHAssetCollection; if (meetUpAssetCollection != null) { var meetUpPhotoResult = PHAsset.FetchAssets(meetUpAssetCollection, null); if (meetUpPhotoResult.Count > 0) meetUpPhotoResult.Enumerate((NSObject element, nuint index, out bool stop) => { var asset = element as PHAsset; PHImageManager.DefaultManager.RequestImageData(asset, null, (data, dataUti, orientation, info) => { var bytes = data.ToArray(); photos.Add(bytes); if (index == (nuint)meetUpPhotoResult.Count - 1) tcs.TrySetResult(photos); }); stop = index == (nuint)meetUpPhotoResult.Count; }); else return new Task<List<byte[]>>(() => photos); } } else return new Task<List<byte[]>>(() => photos); return tcs.Task; }
NSCameraUsageDescription
およびNSPhotoLibraryUsageDescription
キーをInfo.plistに追加することを忘れないでください。
画面を構成するには、Viewフォルダーをプロジェクトに追加し、その中にPhotogalleryフォルダーを作成して、PhotogalleryViewControllerを追加PhotogalleryViewController
。 UICollectionView
のInterface Builderに2つの要素、 UICollectionView
とUIButton
を_addPhotoBtn
それぞれ_photoCollection
と_addPhotoBtn
のアウトレットを作成しUIButton
。 次に、 BindControls
メソッドでそれらをバインドします。
protected override void BindControls() { _photoCollection.RegisterNibForCell(PhotogalleryCell.Nib, PhotogalleryCell.Key); var dataSource = new MvxCollectionViewSource(_photoCollection, PhotogalleryCell.Key); var set = this.CreateBindingSet<PhotogalleryViewController, IPhotogalleryViewModel>(); set.Bind(dataSource).To(vm => vm.Items); set.Bind(_addPhotoBtn).To(vm => vm.AddPhotoCommand); set.Apply(); _photoCollection.DataSource = dataSource; _photoCollection.ReloadData(); }
これで、モジュールは完全に作業できる状態になりました。メインプロジェクトに接続するだけです。
新しいモジュールをメインプロジェクトに接続する
モジュールを接続するには、6つの手順を実行する必要があります。
最初のもの 。 PluginLoader
クラスをCoreプロジェクトに追加します。これにより、App.csの初期化が開始されます。
public class PluginLoader : IMvxPluginLoader { public static readonly PluginLoader Instance = new PluginLoader(); private bool _loaded; public void EnsureLoaded() { if (_loaded) return; new App().Initialize(); var manager = Mvx.Resolve<IMvxPluginManager>(); manager.EnsurePlatformAdaptionLoaded<PluginLoader>(); MvxTrace.Trace("Auth plugin is loaded"); _loaded = true; } }
二番目 。 ViewControllerおよびプラットフォームサービスが登録されるUIプロジェクトにプラグインクラスを追加します。
public class Plugin : IMvxPlugin { public void Load() { var viewLookupService = Mvx.Resolve<IViewLookupService>(); viewLookupService.Register<IPhotogalleryViewModel, PhotogalleryViewController>(); Mvx.RegisterSingleton<ICameraService>(() => new CameraService()); Mvx.RegisterSingleton<IPhotoAlbumService>(() => new PhotoAlbumService()); } }
第三 。 クラスXMU_PhotogalleryPluginBootstrap
を起動したプロジェクトに追加します。
public class XMU_PhotogalleryPluginBootstrap : MvxLoaderPluginBootstrapAction<PluginLoader, Photogallery.iOS.Plugin> { }
4番目 。 構成のメニューからフォトギャラリーへのナビゲーションを指定します。
{ "icon":"res:Images/Menu/photo.png", "name":"", "type":"Photogallery.Core.ViewModels.Photogallery.IPhotogalleryViewModel" }
5番目 。 ナビゲーションイベント処理をCoreプラグインに追加します。
public class PhotogalleryRouterSubscriber : MvxNavigatingObject, IRouterSubscriber { private string VM_TYPE = (typeof(IPhotogalleryViewModel)).FullName; public override bool CanNavigatedTo(string type) { return type == VM_TYPE ? ShowViewModel(LookupService.Resolve(type)) : false; } public override void FailedNavigatedTo(string type) { //nothing } }
6番目 。 それをApp.csに登録します。
var routerService = Mvx.Resolve<IRouterService>(); routerService.Register<IPhotogalleryViewModel>(new PhotogalleryRouterSubscriber());
プロジェクトを実行し、すべてが計画どおりに機能することを確認します。
おわりに
プラットフォームで作業する際の主なポイントを調べました。 私が伝えたかった主な考え:
- あなたは何によっても制限されていません。
- MvvmCrossを試してください。
- 革新的です。
読書中に現れた思考の議論をコメントに移すことをお勧めします。 読んでくれてありがとう!
著者について
Maxim Evtukh -NOTISSIMUS社のXamarinフレームワークでのモバイルアプリケーションの開発者。 2013年以降のモバイル開発。 空き時間に、彼女はMvvmCrossを改善し、新しいマテリアルデザインガイドを実装するためのGitHubコントロールをサポートする問題を研究しています。
Denis Kretov-NOTISSIMUSのテクニカルディレクター。 彼は、iBeaconに基づいたソリューションだけでなく、オンラインストア向けのモバイルアプリケーションの開発を専門としています。
Xamarinブログのその他の記事については、# xamarincolumnをご覧ください。