XAML in Xamarin.Forms 基礎篇 電子書

XAML in Xamarin.Forms 基礎篇 電子書
XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書

2018/06/01

Xamarin.Android 執行時期的權限處理說明 2 使用 Plugin.Permissions

這篇文章的說明範例專案,可以從 這裡 取得

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式
在上一篇文章 Xamarin.Android 執行時期的權限處理說明 1 最低的 Android 版本 / 目標 Android 版本 / 目標 Framework ,我們透過了事件聚合器 Event Aggregator 來呼叫 Android 原生的執行時期的權限檢查與授權工作,不過,這樣的處理方式會有些麻煩,因為,我們同時也需要時做出 iOS 上的相關動態權限檢查與請求授權,因此,在這篇文章中,我們將會透過了 Plugin.Permissions 這個 NuGet 套件,讓我們可以在 Xamarin.Forms 的 .NET Standard 專案中,來進行相關的權限檢查與授權需求。而更多關於這個套件的說明與使用注意事項,可以參考 GitHub Plugin.Permissions 文章說明

準備測試專案

  • 首先我們先使用 Prism Template Pack 幫助我們建立一個 Xamarin.Forms 的專案
  • 接著,請在 SCL .NET Standard 專案中,加入這個 Plugin.Permissions 套件
  • 滑鼠窗擊 Xamarin.Android 專案中的 Properties 節點,並且切換到 Android 資訊清單 標籤頁次,找到 目標 Android 版本,在此下拉選單中,選擇任何一個 API 層級大於 23 的版本
  • 打開 Android 專案中的 MainActivity.cs 檔案,使用底下程式碼取代
using Android.App;
using Android.Content.PM;
using Android.OS;
using Prism;
using Prism.Ioc;

[assembly: UsesPermission(Name = Android.Manifest.Permission.ReadExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.Camera)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.CallPhone)]
namespace XFPermPlugin.Droid
{
    [Activity(Label = "XFPermPlugin", Icon = "@mipmap/ic_launcher", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            Plugin.CurrentActivity.CrossCurrentActivity.Current.Init(this, bundle);

            global::Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication(new App(new AndroidInitializer()));
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [Android.Runtime.GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Plugin.Permissions.PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    public class AndroidInitializer : IPlatformInitializer
    {
        public void RegisterTypes(IContainerRegistry container)
        {
            // Register any platform specific implementations
        }
    }
}
  • 在此,我們使用了 [assembly: UsesPermission(Name = Android.Manifest.Permission.WriteExternalStorage)] 宣告我們需要進行外部記憶卡的寫入需求
  • 接著 我們要在 OnCreate 方法中,使用 Plugin.CurrentActivity.CrossCurrentActivity.Current.Init(this, bundle); 方法呼叫,進行這個套件初始化的動作。
  • 另外,我們也需要在 MainActivity 類別中,加入 OnRequestPermissionsResult 的方法覆寫。
  • 打開 MainPage.xaml 檔案,填入底下的 XAML 宣告
<?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="XFPermPlugin.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Button Text="Permission 檢查與授權" Command="{Binding CheckPermissionCommand}" />
    </StackLayout>

</ContentPage>
  • 在這個頁面中,宣告的 UI 相當的簡單,只有一個按鈕
  • 打開 MainPageViewModel.cs,填入底下的 C# 程式碼
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using Prism.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XFPermPlugin.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        public DelegateCommand CheckPermissionCommand { get; set; }
        public readonly IPageDialogService _dialogService;
        public MainPageViewModel(INavigationService navigationService,
            IPageDialogService dialogService) 
            : base (navigationService)
        {
            _dialogService = dialogService;
            Title = "Main Page";
            CheckPermissionCommand = new DelegateCommand(async () =>
            {
                var fooStatus = await Plugin.Permissions.CrossPermissions
                .Current.CheckPermissionStatusAsync(Plugin.Permissions.Abstractions.Permission.Storage);
                if (fooStatus != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
                {
                    if (await Plugin.Permissions.CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync
                    (Plugin.Permissions.Abstractions.Permission.Storage))
                    {
                        await _dialogService.DisplayAlertAsync("Need Storage", "Gunna need that Storage", "OK");
                    }

                    var results = await Plugin.Permissions.CrossPermissions.Current.RequestPermissionsAsync
                    (Plugin.Permissions.Abstractions.Permission.Storage);
                    //Best practice to always check that the key exists
                    if (results.ContainsKey(Plugin.Permissions.Abstractions.Permission.Storage))
                        fooStatus = results[Plugin.Permissions.Abstractions.Permission.Storage];
                }

                if (fooStatus == Plugin.Permissions.Abstractions.PermissionStatus.Granted)
                {
                    await _dialogService.DisplayAlertAsync("Storage Allow", "You can continue to call any storage API.", "OK");
                }
                else if (fooStatus != Plugin.Permissions.Abstractions.PermissionStatus.Unknown)
                {
                    await _dialogService.DisplayAlertAsync("Storage Denied", "Can not continue, try again.", "OK");
                }
            });
        }
    }
}
  • 在這個 ViewModel 中,我們使用了 Plugin.Permissions.CrossPermissions.Current 取得了 IPermissions 這個實作物件,並且使用了 Plugin.Permissions.CrossPermissions.Current.CheckPermissionStatusAsync(Plugin.Permissions.Abstractions.Permission.Storage) 敘述,來檢查現在正在執行的應用程式,是否已經取得使用者授權可以使用 Storage 的使用權限。
    若使用這已經授權使用 Storage 這個使用權限,則您會得到 Plugin.Permissions.Abstractions.PermissionStatus.Granted 值
    若使用者尚未授權,對於 Android 平台,我們需要顯示一段訊息,告知使用者我們需要這樣的權限,如此,應用程式方可正常運作。
    Android Permission
    接下來,我們使用了 Plugin.Permissions.CrossPermissions.Current.RequestPermissionsAsync(Plugin.Permissions.Abstractions.Permission.Storage) 方法,請求使用者授權這個應用程式,可以使用 Storage 的權限。
    Android Permission
    一旦此次請求授權的結果為 Plugin.Permissions.Abstractions.PermissionStatus.Granted ,您就可以開始進行相關 API 呼叫,否則,就不應該呼叫這些尚未授權 API 的呼叫。

2018/05/31

Xamarin.Android 執行時期的權限處理說明 1 最低的 Android 版本 / 目標 Android 版本 / 目標 Framework

這篇文章的說明範例專案,可以從 這裡 取得

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式
當我們在進行 Xamarin.Android 專案建置的時候,我們可以從 Android 專案內的 Properties 中,也就是這個專案的屬性視窗內,看到關於這三個名詞:最低的 Android 版本 / 目標 Android 版本 / 目標 Framework。若您想要更進一步的瞭解這三者的差異,可以參考 了解 Android API 層級 ,底下根據這篇文章內容,列出這三者的說明
  • 目標 Framework (也稱為 compileSdkVersion)
    是在建置階段編譯您的應用程式特定的 Android 架構版本 (API 層級)
  • 最低的 Android 版本 (也稱為 minSdkVersion)
    是 Android OS (亦即,最低 API 層級),可以安裝和執行應用程式的舊版本
  • Android 的版本為目標 (也稱為 targetSdkVersion)
    是應用程式開發介面層級的 Android 裝置,應用程式預期執行。
由於 從 2018年 8 月之後 ,Google Play Console 將會需要新的應用程式目標應用程式開發介面層級 26 (Android 8.0) 或更高版本,這裡所指的是 目標 Android 版本。 現有的應用程式必須以 API 層級 26 或更高版本。如需詳細資訊,請參閱 改善應用程式安全性和效能
因此,在這裡,我們要來做些小測試,了解如何進行執行時期的權限檢查與請求使用者授予使用這些權限的程式設計方法。

建立測試專案

  • 首先,使用 Xamarin.Forms for Prism 建立一個專案
  • 滑鼠雙擊 Android 專案內的 Properties 節點,此時,這個專案的屬性視窗會顯示出來
  • 請切換到 應用程式 標籤頁次,點選 使用下列 Android 版本編譯: (目標 Framework) 下拉選單,確定選擇的是 使用最新平台,這裡是會設定 compileSdkVersion
  • Android Target Version compileSdkVersion
    這裡是英文版的 Visual Studio 2017 看到的內容,我們可以看到這個欄位名稱為 Compile using Android version: (Target Framework)
    Android Target Version compileSdkVersion
  • 請切換到 Android 資訊清單 (Manifest) 標籤頁次,點選 目標 Android 版本 下拉選單,根據文件建議,請在這裡選擇最新的 SDK 版本,這裡是會設定 targetSdkVersion
  • Android Target Version compileSdkVersion
    這裡是英文版的 Visual Studio 2017 看到的內容,我們可以看到這個欄位名稱為 Target Android version
    Android Target Version compileSdkVersion
  • 請打開 Android 專案內的 MainActivity.cs 檔案,在組件層級,也就是命名空間之外,輸入底下程式碼,我們在這宣告了這個 Android 應用程式,需要使用這四種權限 Permission
[assembly: UsesPermission(Name = Android.Manifest.Permission.ReadExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.Camera)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.CallPhone)]
  • 現在,請開始執行這個專案,您可以使用 Android SDK 6.0 以上的模擬器或者是實體手機來進行測試。
  • 當這個應用程式執行完成後,請中止執行,並且在 Android 裝置或模擬器上,打開設定中的應用程式頁面,請在清單中找到您剛剛執行成功的應用程式名稱,並且點選它
  • 當這個應用程式打開之後,您會看到這個應用程式明細頁面中,有個 Permissions 選項,請點選它,接著您就會發現到,這裡有三個 Permissions,不過,他們都是沒有被啟用的。
    Visual Studio for Android Emulator Visual Studio for Android Emulator
    Visual Studio for Android Emulator Visual Studio for Android Emulator
  • 此時,若您的應用程式中,剛好有執行與使用到這三個權限相關的 API,不幸的是,您的應用程式,就會造成閃退的問題。

加入執行時期的所需權限檢查

  • 建立一個 RuntimePermissionEvent 類別,用來定義 事件聚合器需要訂閱的事件類別
public class RuntimePermissionEvent : PubSubEvent<RuntimePermissionPayload>
{

}

public class RuntimePermissionPayload
{

}
  • 請將測試頁面的 XAML 修改成為如下
<?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="XFRunPermission.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Button Text="Permission 檢查與授權" Command="{Binding CheckPermissionCommand}" />
    </StackLayout>

</ContentPage>
  • 請將該頁面的 ViewModel,修改成為底下 C# 程式碼
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XFRunPermission.Events;

namespace XFRunPermission.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        private readonly IEventAggregator _eventAggregator;
        public DelegateCommand CheckPermissionCommand { get; set; }
        public MainPageViewModel(INavigationService navigationService,
            IEventAggregator eventAggregator) 
            : base (navigationService)
        {
            _eventAggregator = eventAggregator;
            CheckPermissionCommand = new DelegateCommand(() =>
            {
                _eventAggregator.GetEvent<RuntimePermissionEvent>().Publish(
                    new RuntimePermissionPayload());
            });
            Title = "Main Page";
        }
    }
}
  • 在 Android 專案內找到 MainActivity.cs 節點,將這個檔案內的內容,使用底下程式碼來替換
using Android.App;
using Android.Content.PM;
using Android.OS;
using Prism;
using Prism.Events;
using Prism.Ioc;
using XFRunPermission.Events;

[assembly: UsesPermission(Name = Android.Manifest.Permission.ReadExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.Camera)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.CallPhone)]
namespace XFRunPermission.Droid
{
    [Activity(Label = "XFRunPermission", Icon = "@mipmap/ic_launcher", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        IEventAggregator fooIEventAggregator;
        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            global::Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication(new App(new AndroidInitializer()));

            var fooContainer = ((App.Current) as Prism.Unity.PrismApplication).Container;
            fooIEventAggregator = fooContainer.Resolve<IEventAggregator>();
            fooIEventAggregator.GetEvent<RuntimePermissionEvent>().Subscribe(x =>
            {
                CheckPermissions();
            });
        }

        void CheckPermissions()
        {
            if ((int)Build.VERSION.SdkInt < 23)
            {
                return;
            }

            var fooRead = CheckSelfPermission(Android.Manifest.Permission.ReadExternalStorage);
            var fooWrite = CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);
            if ((fooRead == (int)Permission.Granted) && (fooWrite == (int)Permission.Granted))
            {
                var foo = 0;
            }
            else
            {
                Android.Support.V4.App.ActivityCompat.RequestPermissions(this,
                    new string[] { Android.Manifest.Permission.ReadExternalStorage, Android.Manifest.Permission.WriteExternalStorage,
                    Android.Manifest.Permission.CallPhone, Android.Manifest.Permission.Camera}, 4);
            }
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
        {
            var foo = 1;
        }
    }

    public class AndroidInitializer : IPlatformInitializer
    {
        public void RegisterTypes(IContainerRegistry container)
        {
            // Register any platform specific implementations
        }
    }
}

開始進行測試

  • 請再度執行這個專案,這個時候,首頁會看到這個畫面,請點選螢幕上的按鈕
    Visual Studio for Android Emulator
  • 當按鈕按下去之後,您將會看到了下圖畫面,系統將會要求您同意授權給予三種應用程式使用權限,在這裡,請在第一個權限使用授權點選 DENY,另外兩個,可以點選 ALLOW
  •  Visual Studio for Android Emulator Visual Studio for Android EmulatorVisual Studio for Android Emulator
  • 完成後,您就可以關閉這個應用程式,回到 設定 > 應用程式,來查看這個應用程式可以使用權限的說明,現在,我們可以看到這個應用程式,經過使用者授權,已經可以使用 Camera & Phone 兩種 Permission 了。
  •  Visual Studio for Android Emulator




2018/05/29

關於 Xamarin.Android 提交到 Google Play 時候,得到 變更應用程式的目標 API 等級 的警告訊息

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式

今天收到學員提問的問題,發現到若您沒有做些調整,當您要把 Xamarin.Forms 的 APK 佈署檔案上傳到 Google Play 時候,會得到底下錯誤訊息
警告
你的應用程式目前的目標 API 等級是 17,請將最低目標 API 等級調整為 26,以確保應用程式採用最新的 API,讓安全性與執行效能達到最佳狀態。
自 2018 年 8 月起,新應用程式的指定目標至少必須是 Android 8.0 (API 等級 26)。
自 2018 年 11 月起,應用程式更新的指定目標必須是 Android 8.0 (API 等級 26)。
Your app currently targets API level 17 and must target at least API level 26 to ensure it is built on the latest APIs optimised for security and performance.
From August 2018, new apps must target at least Android 8.0 (API level 26).
From November 2018, app updates must target Android 8.0 (API level 26).
Meet Google Play's target API level requirement
此時,我連上我的 Google Play Console 來查看之前上架的 App 資訊,從下圖中,您將會看到,在 區別性 APK 詳細資料 欄位中,底下的 目標 SDK 版本並沒有定義支援哪個版本。
Android Target SDK Version
由於有這項新的政策推出,我們在進行 Android 發佈檔案 .apk 的時候,請依照底下步驟進行設定,這樣所產生出來的 .apk 檔案提交到 Goolge Play 上的時候,就不會出現這樣的警告訊息了。
  • 請展開 Android 專案內的 Properties 節點,滑鼠雙擊 AndroidManifest.xml
    AndroidManifest.xml
  • 在 AndroidManifest.xml 加入這行
  • 底下是我這裡測試的檔案內容
    在這裡,我的開發環境的最新 Android SDK 有安裝 SDK 8.1,因此,我將會設定我的目標 Framework 為 API Level 27
    您可以依據您當時的開發環境來進行設定,若您的開發環境沒有安裝 Android SDK 8.1 (API Level 27),您可以選擇 Android SDK 8.0 (API Level 26)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="11" android:versionName="1.3.8" package="com.vulcanlab.LOBForm" android:installLocation="auto">
    <uses-sdk android:minSdkVersion="17" />
    <uses-sdk android:targetSdkVersion="27" />
    <uses-permission android:name="android.permission.INTERNET" />
    <application android:label="企業表單" android:icon="@drawable/icon"></application>
</manifest>
  • 滑鼠雙擊 Android 專案中的 Properties 節點
  • 切換到 應用程式 Application 標籤頁次視窗
  • 確認 使用下列 Android 版本編譯: (目標 Framework) 下拉選單,有選取 使用最新平台 (Android 8.1 (Oreo)) 或者 Android 8.1 (Oreo) Meet Google Play's target API level requirement
  • 現在,您可以進行這個 Android 專案的封存動作,在完成封存與程式碼簽名動作之後,將會得到一個 .apk 檔案,此時,您可以將這個 .apk 檔案上傳到 Google Play 上
  • 如此,您將不會再看到警告訊息,如同下圖所示
Meet Google Play's target API level requirement
當這個 App 上架成功之後,我再度到 Goolge Play Console 中查看這個 APK 詳細資訊,此時, 目標 SDK 版本 就會出現我所指定的 27 版本資訊
Android Target SDK Version

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式