ãã®èšäºã§ã¯ã 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(); } }
TakePhotoïŒïŒã¡ãœããã®å®è£
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(); } }
LoadPhotosFromã¡ãœããã®å®è£
ïŒstring albumNameïŒ 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
ãèµ·åãããããžã§ã¯ãã«è¿œå ããŸãã
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ãã©ã°ã€ã³ã«è¿œå ããŸãã
ãã©ãã®ã£ã©ãªãŒRouterSubscriber 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) {
6çªç® ã ãããApp.csã«ç»é²ããŸãã
ç»é²PhotogalleryRouterSubscriber var routerService = Mvx.Resolve<IRouterService>(); routerService.Register<IPhotogalleryViewModel>(new PhotogalleryRouterSubscriber());
ãããžã§ã¯ããå®è¡ãããã¹ãŠãèšç»ã©ããã«æ©èœããããšã確èªããŸãã
ãããã«
ãã©ãããã©ãŒã ã§äœæ¥ããéã®äž»ãªãã€ã³ãã調ã¹ãŸããã ç§ãäŒãããã£ãäž»ãªèãïŒ
- ããªãã¯äœã«ãã£ãŠãå¶éãããŠããŸããã
- MvvmCrossã詊ããŠãã ããã
- 驿°çã§ãã
èªæžäžã«çŸããæèã®è°è«ãã³ã¡ã³ãã«ç§»ãããšããå§ãããŸãã èªãã§ãããŠããããšãïŒ
èè
ã«ã€ããŠ
Maxim Evtukh -NOTISSIMUS瀟ã®Xamarinãã¬ãŒã ã¯ãŒã¯ã§ã®ã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ã®éçºè
ã 2013幎以éã®ã¢ãã€ã«éçºã 空ãæéã«ã圌女ã¯MvvmCrossãæ¹åããæ°ãããããªã¢ã«ãã¶ã€ã³ã¬ã€ããå®è£
ããããã®GitHubã³ã³ãããŒã«ããµããŒãããåé¡ãç ç©¶ããŠããŸãã
Denis Kretov-NOTISSIMUSã®ãã¯ãã«ã«ãã£ã¬ã¯ã¿ãŒã 圌ã¯ãiBeaconã«åºã¥ãããœãªã¥ãŒã·ã§ã³ã ãã§ãªãããªã³ã©ã€ã³ã¹ãã¢åãã®ã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ã®éçºãå°éãšããŠããŸãã
Xamarinããã°ã®ãã®ä»ã®èšäºã«ã€ããŠã¯ã# xamarincolumnãã芧ãã ããã