در این قسمت قصد داریم تا با زدن کدهای Platform Specific در Xamarin آشنا شویم. صد البته که در Xamarin Forms به کتابخانههای NET. ای دسترسی داریم و مواردی چون Entity Framework Core، Auto Mapper، Autofac و ... را میتوانیم استفاده کنیم و در کنار اینها، مواردی چون Linq, Parallel Linq, Socket و ... نیز در دسترس ما هستند. در رابطه با مواردی چون کار با Clipboard, Geocoding, Gyroscope, Secure Store و ... نیز میتوان از کتابخانه فوق العاده کاربردی
Xamarin Essentials استفاده کرد که با یک کد CSharp میتوانید روی Android/iOS/Windows جواب بگیرید.
اما فرض کنید که جستجو کرده اید و کد Cross Platform آمادهای برای استفاده نیافتهاید؛ یا پیدا کردهاید، ولی صد در صد منطبق با نیازهای شما نیست. حال باید چه کنید؟ ابتدا باید کد مربوطه را بدانید که در Android/iOS/Windows (بسته به نیازتان) چگونه باید نوشت. در مورد Windows، خب تمامی امکانات سیستم عامل ویندوز را در زبان CSharp هم دارید. خبر خوب این است که این مهم نه تنها برای ویندوز که در مورد Android و iOS نیز برقرار است. به علاوه مستندات استفاده از آنها به زبان CSharp نیز موجود است. برای مثال نگاهی بیاندازید به روش Platform Specific استفاده از
Bluetooth در Windows و
AR Kit 2 در iOS و
Job Scheduler در Android
صد البته که کتابخانه فوق العاده
BluetoothLE وجود دارد و یک بار نوشتن کد، نه تنها روی Windows/Android/iOS که بر روی macOS و tvOS هم کار میکند!
با مثال گرفتن "ورژن برنامه" شروع میکنیم. هر چند با استفاده از
Xamarin Essentials می شود با یک خط کد، ورژن برنامه را در هر پلتفرمی که باشیم گرفت؛ ولی فرض کنید که نمیشود. برای پیاده سازی این قابلیت ابتدا یک Interface را تعریف میکنیم و آن را در فولدر Contracts در پروژه XamApp قرار میدهیم:
public interface IAppVersionService
{
string GetAppVersion();
}
سپس در پروژه XamApp.Android، در فولدر Implementations، کلاس زیر را میسازیم: (چون این کلاس در پروژه Android است، به 100% امکانات Android دسترسی داریم)
public class AndroidAppVersionService : IAppVersionService
{
public Android.Content.Context Context { get; set; }
public string GetAppVersion()
{
return Context.PackageManager.GetPackageInfo(Context.PackageName, 0).VersionName;
}
}
این کد را از روی
این جواب در StackOverFlow نوشتهام. همانطور که میبینید، دو کد، ساختاری شبیه به یکدیگر دارند. فقط تفاوت این است که Context.
GetPackageManager در Java، در CSharp به Context.PackageManager تبدیل میشود؛ زیرا در Java چیزی به صورت Property و Get,Set وجود ندارد و Context.PackageManager در Java معادل میشود با دو متد Context.
GetPackageManager و Context.
SetPackageManager
تقریبا برای هر کاری در Android نیاز به Context دارید که میتوانید آن را با Property Injection دریافت کنید.
سپس در فایل MainActivity.cs در کلاس XamAppPlatformInitializer، در متد RegisterTypes داریم:
containerBuilder.RegisterType<AndroidAppVersionService>()
.As<IAppVersionService>()
.PropertiesAutowired(PropertyWiringOptions.PreserveSetValues);
برای پیاده سازی همین امکان در iOS داریم:
public class iOSAppVersionService : IAppVersionService
{
public string GetAppVersion()
{
var infoDictionary = NSBundle.MainBundle.InfoDictionary;
return infoDictionary?["CFBundleShortVersionString"] as NSString;
}
}
که از روی
این جواب به دست آمده است. البته جواب مربوطه علاوه بر ورژن، نام برنامه را نیز به دست میآورد که نیاز ما نیست. اگر سایر جوابها را نگاه کنید، میبینید که جوابهای مربوط به Swift برای برنامه نویسان CSharp خوانایی دارند، ولی این در مورد کدهای Objective-C خیلی صادق نیست(!) برای حل این مشکل، کد Objective-C را در
این سایت به Swift تبدیل کرده و سپس معادل CSharp آن را بنویسید.
و در نهایت برای UWP از روی این جواب داریم:
public string GetAppVersion()
{
return $"{Package.Current.Id.Version.Major}.{Package.Current.Id.Version.Minor}";
}
که این دو نیز در AppDelegate.cs برای iOS و MainPage.xaml.cs برای UWP رجیستر میشوند.
برای استفاده نیز کافی است در هر View Model ای که قصد استفاده از این سرویس را دارید، یک Property از جنس IAppVersionService را تعریف کنید. در صورت Pull کردن آخرین تغییرات پروژه XamApp، میتوانید نتیجه را در View و View Model با نام PlatformSpecificSamples ببینید.
خبر خوب این است که تمامی کدها به زبان CSharp نوشته میشوند و اگر مثلا وسط یک کد Platform Specific برای Android احتیاج به Auto Mapper پیدا کردید، میتوانید از آن استفاده کنید. همچنین تمامی این کدها در Visual Studio دیباگ میشوند که خود نعمتی است.
حال اگر در ادامه کار، به یک کتابخانه 3rd Party که با Java نوشته شده نیاز پیدا کردیم چه؟ برای مثال
این کتابخانه اطلاعاتی را در مورد Ringer گوشی، در اختیار ما قرار میدهد!
در Xamarin میتوانید فایلهای JAR و AAR و Headerهای Objective-C و Swift را در پروژه اضافه کنید و Wrapper به زبان CSharp تحویل بگیرید! علاوه بر مستندات مفصل خود Xamarin در این مورد که برای
Android/
iOS می توانید آنها را بخوانید. افراد زیادی بر همین اساس امکان استفاده از کتابخانههای 3rd Party زیادی را به Xamarin اضافه کردهاند. برخی از ابزارها نیز در این زمینه کاربردی هستند؛ برای مثال، برای ساخت C# Wrapper از روی C++,C از ابزار
CppSharp می توانید استفاده کنید.
در نظر داشته باشید، اگر بخواهید کدی بزنید که فقط تفاوت رفتار در Android/iOS/Windows را دارد، یا بسته به گوشی، تبلت یا دسکتاپ بودن قرار است رفتارش تفاوت کند، مثلا یک پیام را فقط به دارندگان گوشیهای اندرویدی نشان دهید، ولی با IUserDialogs که در هر سه پلتفرم کار میکند میخواهید این کد را بنویسید، احتیاجی به این کارها نیست و به سادگی تعریف یک Property با نام IDeviceService میتوانید جواب لازم را بگیرید:
async Task ShowSomeAlertToAndroidPhoneUsersOnly()
{
if (DeviceService.RuntimePlatform == RuntimePlatform.Android && DeviceService.Idiom == TargetIdiom.Phone)
{
await UserDialogs.AlertAsync("Some alert to android phone users only!", "Test");
}
}
در برخی مواقع ما قصد سفارشی سازی کردن کنترلهای UI را داریم. برای مثال زمانیکه از Entry در Xamarin Forms استفاده میکنیم، این به کنترل معادل Native خودش در هر پلتفرم تبدیل میشود، که همین باعث میشود بگوییم UI در Xamarin Forms به صورت Native است. حال در iOS که ما UITextField را به عنوان معادل Native کنترل Entry داریم، یک ویژگی داریم به نام ClearButtonMode که وقتی به مقدار WhileEditing تنظیم شود، در موقع تایپ کردن در UITextField، آن X پاک کردن متن باقی میماند. این رفتار پیش فرض نیست و اگر ما قصد تغییر آن را داشته باشیم، یکی از متداولترین راهها، نوشتن Custom Renderer است. برای همین در iOS از EntryRenderer ارث بری میکنیم و سفارشی سازی مربوطه را انجام میدهیم و در نهایت EntryRenderer خودمان را رجیستر میکنیم.
public class XamAppEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement != null) /* e.NewElement is a Xamarin Forms' Entry */
{
Control.ClearButtonMode = UITextFieldViewMode.WhileEditing; // Control is UITextField
}
}
}
برای Register کردن نیز داریم:
[assembly: ExportRenderer(typeof(Entry), typeof(XamAppEntryRenderer))]
در واقع این کد میگوید که از این به بعد، Entryها در iOS، با کلاس جدید Render شوند. برای درک بهتر این مهم، فایل XamAppEntryRenderer.cs را در فولدر Renderer در پروژه XamApp.iOS مشاهده کنید.