XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2017/04/22

Xamarin FAQ 2-27 : 在XAML內,設定多個列舉值

問題

有些 XAML 的屬性是個列舉,可以設定多個列舉值,例如, FontAttributes 就是其中一列,可是,當我需要在 XAML 中定義多個列舉值的時候,我該如何定義呢?

解答

您可以參考底下用法,多個列舉值,可以使用逗號 , 將其分開即可
        <Label Text="多奇數位創意有限公司" FontAttributes="Italic,Bold"/>
Xamarin-跨平台手機應用程式設計入門-粉絲團

2017/04/21

使用 Xamarin + Azure 無法進行 Facebook 身分驗證

最近,公司有個專案需要提供 Facebook 身分驗證的機制,當然,就會想到使用 Xamarin.Auth 這個套件來使用;我之前在 Xamarin 工作紡電子書中,也有交代如何透過 Xamarin.Auth ,進行市面上一般社群網站的 oAuth2 的身分驗證開發方法。
可是,很不幸的,不論是依據電子書所交代的步驟,或者直接開啟墊子書中所提供的範例專案,都無法進行身分驗證。
問題發生在,當輸入完成 Facebook 的帳號與密碼之後,因為是第一次使用,需要需要同意使用 Facebook App,不過,此時,畫面是被凍結住的,在這個網頁中,您無法點選任何操作與按鈕。
這個時候,當然會想到,會不會是 Xamarin.Auth 這個套件出了問題,不過,我之前也寫過了使用 Azure Mobile App 的身分驗證電子書,理所當然的,就會打開該電子書的範例專案來測試看看;不幸的是,兩者的情況都是一樣,網頁被凍結住了。
經過一番調查,看到這篇文章:
原來又是 Facebook 動了手腳,只有在 iOS 的環境中,無法正常地進行 Facebook 的身分驗證,而同樣的程式,在 Android 平台下,卻是可以正常運作的。
因此,按照該篇文章的指示,進行調整 Azure Mobile 的 MOBILESERVICESDOTNET_EXTENSION_VERSION 服務,最後的情況,當然也是一樣的,畫面被凍結了。
Update the MOBILESERVICESDOTNET_EXTENSION_VERSION value from 1.0.478 to 1.0.479.
這個時候,突然想到9年以前處理的問題,便針對 iOS 的 WebKit 調整他的 User-Agent Webviews and User-Agent strings ,將 iOS 環境中的 WebKit 瀏覽器的 User-Agent 調整成為 Facebook for iOS User-Agent string : Mozilla/5.0 (iPhone; CPU iPhone OS 8_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12D508 [FBAN/FBIOS;FBAV/27.0.0.10.12;FBBV/8291884;FBDV/iPhone7,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/8.2;FBSS/3; FBCR/vodafoneIE;FBID/phone;FBLC/en_US;FBOP/5]
底下為設定 WebKit User-Agent 的方法
string userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 8_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12D508 [FBAN/FBIOS;FBAV/27.0.0.10.12;FBBV/8291884;FBDV/iPhone7,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/8.2;FBSS/3; FBCR/vodafoneIE;FBID/phone;FBLC/en_US;FBOP/5]";

NSDictionary dictionary = NSDictionary.FromObjectAndKey(NSObject.FromObject(userAgent), NSObject.FromObject("UserAgent"));
NSUserDefaults.StandardUserDefaults.RegisterDefaults(dictionary);
最後,當然是如期解決了問題,iOS 平台下,可以使用 Azure Mobile App 來進行 Facebook 的身分驗證了。

Xamarin應用程式,一執行就閃退的解決方案

在這個星期,遇到兩次這樣的問題,花了一些時間來處理這類問題,不過,其實這樣的問題,有一個比較簡潔的處理方式。

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式
當您的應用程式一啟動之後,就發現會自然的閃退,這個時候,若您有設定了中斷點,不論您設定在哪個地方(原生專案或者核心PCL專案內),似乎,您的程式都無法在這些中斷點停下來,還是會自動閃退。
通常來說,您會看到底下的錯誤訊息
Objective-C exception thrown.  Name: NSInternalInconsistencyException Reason: Application windows are expected to have a root view controller at the end of application launch
不論您的程式是剛剛開始進行開發,還是已經寫了很多內容了,這個時候,請都不要緊張,我強烈建議您採用 刪去法 開始進行這類問題除錯。
首先,請先建立一個空白的 ContentPage,並且在 App.xaml.cs 內,指定 MainPage 到這個 ContentPage;並且,再度執行一次,看看是否可以正常運作。
若您還是遇到不幸的情況,請在原生專案的進入點方法內,看看是否有執行甚麼初始化的方法,若有的話,請記得將其註解起來;接著再度執行一次,看看能否正常執行。
經過這樣幾次,不斷地將程式碼刪去之後,應該可以找到問題所在。
我遇到的兩個情況分別是:
  1. XAML 語法錯誤,導致執行時期,要顯示這個頁面的時候,造成執行時期的閃退。
  2. 有個靜態屬性,當進行物件值初始化的時候,發生了異常,導致程式直接閃退;當然,這個類別內的其他靜態屬性物件,也無法被存取了。

iOS 取消 ATS App Transport Security 限制

預設,Xamarin.iOS 的App,需要使用 Https 來進行網路存取,可是,有些時候,您期望還是要使用不加密的方式來存取網路;這個時候,您可以在 Info.plist 檔案內,加入底下的設定:
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
更多這方面的說明,可以參考 App Transport Security

如何清空 Mac 電腦中的 Provisioning Profile 快取資料

有些時候,若您修改的 Provisioning Profile 設定資訊後,發現到在 Windows 端的 Visual Studio 沒有辦法取得最新的 Provisioning Profile 設定資訊。
有個終極作法,那就是,在 Mac 電腦端,把這些快取的 Provisioning Profile 檔案先全部刪除掉,接著,再透過 Xcode 將其全部從 Apple Developer 網站中下載下來。
您可以切換到底下目錄,將這個目錄下的所有物件全部都刪除掉即可
~/Library/MobileDevice/Provisioning\ Profiles

2017/04/17

Xamarin.Forms 與 Azure Mobile App Lab 13

在 Xamarin.Forms 專案,修正進行 Azure 行動應用推播中樞註冊

在這裡,我們將要把我們寫好的 Xamarin.Forms 專案,設定使用 Azure 行動應用的推播服務功能,也就是要在應用程式啟動時候,與 Azure 行動應用的推播中樞進行註冊,並且可以接收與顯示推播訊息內容。

第一次準備工作

在您原先做好的 Xamarin.Forms 專案中,需要加入 Google Cloud Messaging Client 元件

加入 Google Cloud Messaging Client 元件

  • 使用 Visual Studio 2015 打開 XFDoggy.sln 專案
  • 滑鼠右擊 原生Android專案的 Components,選擇 Get More Components
  • 在搜尋文字輸入盒內,填入 Google Cloud Messaging Client 搜尋這個元件
  • 點選 Google Cloud Messaging Client 元件,接著點選 Add to App 按鈕

修改 原生 Android 專案

在這裡,我們僅需要修正 原生 Android 專案,讓 Android 應用程式可以與行動中樞溝通。

設計自己的 GcmServiceBase 類別

  • 滑鼠右擊 原生Android專案中的 Infrastructure 資料夾,選擇 加入 > 類別
  • 在對話窗中,點選 Visual C# > Class
  • 在名稱欄位中,輸入 GcmService
  • 最後,點選 新增 按鈕
  • 將底下程式碼置換這個 GcmService 類別
    請解析要加入的適當命名空間
  • 請將這行程式碼 public static string[] SENDER_IDS = new string[] { "300143732939" }; 的 300143732939 文字,置換成為您申請到的 FCM 寄件者 ID
using Android.App;
using Android.Content;
using Android.Media;
using Android.Support.V4.App;
using Android.Util;
using Gcm.Client;
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using XFDoggy.Helpers;

[assembly: Permission(Name = "@PACKAGE_NAME@.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "@PACKAGE_NAME@.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "com.google.android.c2dm.permission.RECEIVE")]
[assembly: UsesPermission(Name = "android.permission.INTERNET")]
[assembly: UsesPermission(Name = "android.permission.WAKE_LOCK")]
//GET_ACCOUNTS is only needed for android versions 4.0.3 and below
[assembly: UsesPermission(Name = "android.permission.GET_ACCOUNTS")]

namespace XFDoggy.Droid.Infrastructure
{
    [Service]
    public class GcmService : GcmServiceBase
    {
        public static string RegistrationID { get; private set; }

        public GcmService()
            : base(PushHandlerBroadcastReceiver.SENDER_IDS) { }

        protected override void OnRegistered(Context context, string registrationId)
        {
            Log.Verbose("PushHandlerBroadcastReceiver", "GCM Registered: " + registrationId);
            RegistrationID = registrationId;

            var push = MainHelper.client.GetPush();

            MainActivity.CurrentActivity.RunOnUiThread(() => Register(push, null));
        }

        public async void Register(Microsoft.WindowsAzure.MobileServices.Push push, IEnumerable<string> tags)
        {
            try
            {
                const string templateBodyGCM = "{\"data\":{\"message\":\"$(messageParam)\"}}";

                JObject templates = new JObject();
                templates["genericMessage"] = new JObject
                {
                    { "body", templateBodyGCM}
                };

                await push.RegisterAsync(RegistrationID, templates);
                Log.Info("Push Installation Id", push.InstallationId.ToString());
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                Debugger.Break();
            }
        }

        protected override void OnMessage(Context context, Intent intent)
        {
            Log.Info("PushHandlerBroadcastReceiver", "GCM Message Received!");

            var msg = new StringBuilder();

            if (intent != null && intent.Extras != null)
            {
                foreach (var key in intent.Extras.KeySet())
                    msg.AppendLine(key + "=" + intent.Extras.Get(key).ToString());
            }

            //Store the message
            var prefs = GetSharedPreferences(context.PackageName, FileCreationMode.Private);
            var edit = prefs.Edit();
            edit.PutString("last_msg", msg.ToString());
            edit.Commit();

            string message = intent.Extras.GetString("message");
            if (!string.IsNullOrEmpty(message))
            {
                createNotification("多奇數位創意通知", message);
                return;
            }

            string msg2 = intent.Extras.GetString("msg");
            if (!string.IsNullOrEmpty(msg2))
            {
                createNotification("New hub message!", msg2);
                return;
            }

            createNotification("Unknown message details", msg.ToString());
        }

        void createNotification(string title, string desc)
        {
            //Create notification
            var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;

            //Create an intent to show ui
            var uiIntent = new Intent(this, typeof(MainActivity));

            //Use Notification Builder
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

            //Create the notification
            //we use the pending intent, passing our ui intent over which will get called
            //when the notification is tapped.
            var notification = builder.SetContentIntent(PendingIntent.GetActivity(this, 0, uiIntent, 0))
                    .SetSmallIcon(Android.Resource.Drawable.SymActionEmail)
                    .SetTicker(title)
                    .SetContentTitle(title)
                    .SetContentText(desc)

                    //Set the notification sound
                    .SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification))

                    //Auto cancel will remove the notification once the user touches it
                    .SetAutoCancel(true).Build();

            //Show the notification
            notificationManager.Notify(1, notification);
        }

        protected override void OnUnRegistered(Context context, string registrationId)
        {
            Log.Error("PushHandlerBroadcastReceiver", "Unregistered RegisterationId : " + registrationId);
        }

        protected override void OnError(Context context, string errorId)
        {
            Log.Error("PushHandlerBroadcastReceiver", "GCM Error: " + errorId);
        }
    }

    [BroadcastReceiver(Permission = Gcm.Client.Constants.PERMISSION_GCM_INTENTS)]
    [IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_MESSAGE }, Categories = new string[] { "@PACKAGE_NAME@" })]
    [IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new string[] { "@PACKAGE_NAME@" })]
    [IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new string[] { "@PACKAGE_NAME@" })]
    public class PushHandlerBroadcastReceiver : GcmBroadcastReceiverBase<GcmService>
    {
        public static string[] SENDER_IDS = new string[] { "300143732939" };
    }

}

修改 MainActivity

  • 在 原生Android專案中,開啟 MainActivity.cs
  • 將底下程式碼置換這個 GcmService 類別
    請解析要加入的適當命名空間
using System;

using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Prism.Unity;
using Microsoft.Practices.Unity;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.MobileServices;
using XFDoggy.Helpers;

using Gcm.Client;
using XFDoggy.Droid.Infrastructure;

[assembly: UsesPermission(Android.Manifest.Permission.AccessNetworkState)]
[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Android.Manifest.Permission.CallPhone)]
namespace XFDoggy.Droid
{
    [Activity(Label = "多奇數位創意", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity, IAuthenticate
    {
        #region Firebase 的推播設定用程式碼
        // Create a new instance field for this activity.
        static MainActivity instance = null;

        // Return the current activity instance.
        public static MainActivity CurrentActivity
        {
            get
            {
                return instance;
            }
        }

        #endregion

        #region Azure 行動應用之身分驗證用到程式碼

        // Define a authenticated user.
        private MobileServiceUser user;


        public async Task<bool> Authenticate(MobileServiceAuthenticationProvider p登入方式)
        {
            // 驗證結果
            var success = false;
            // 要顯示的訊息
            var message = string.Empty;
            try
            {
                // 呼叫 Azure Mobile 用戶端的 LoginAsync 方法,依據指定的登入類型,進行身分驗證登入
                user = await MainHelper.client.LoginAsync(this, p登入方式);
                if (user != null)
                {
                    message = string.Format("you are now signed-in as {0}.", user.UserId);
                    success = true;
                }
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }

            // 顯示登入成功或者失敗.
            //AlertDialog.Builder builder = new AlertDialog.Builder(this);
            //builder.SetMessage(message);
            //builder.SetTitle("Sign-in result");
            //builder.Create().Show();

            return success;
        }
        #endregion

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

            base.OnCreate(bundle);

            global::Xamarin.Forms.Forms.Init(this, bundle);

            #region Azure 行動應用之身分驗證用到程式碼
            // 將在 Android 原生平台實作的 IAuthenticate 物件,指定到 核心PCL 專案內
            App.Init((IAuthenticate)this);
            #endregion

            #region Firebase 的推播設定用程式碼
            // Set the current instance of MainActivity.
            instance = this;
            #endregion

            LoadApplication(new App(new AndroidInitializer()));

            #region Firebase 的推播設定用程式碼
            try
            {
                // Check to ensure everything's set up right
                GcmClient.CheckDevice(this);
                GcmClient.CheckManifest(this);

                // Register for push notifications
                System.Diagnostics.Debug.WriteLine("Registering...");
                GcmClient.Register(this, PushHandlerBroadcastReceiver.SENDER_IDS);
            }
            catch (Java.Net.MalformedURLException)
            {
                CreateAndShowDialog("There was an error creating the client. Verify the URL.", "Error");
            }
            catch (Exception e)
            {
                CreateAndShowDialog(e.Message, "Error");
            }
            #endregion
        }

        #region Firebase 的推播設定用程式碼
        private void CreateAndShowDialog(String message, String title)
        {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);

            builder.SetMessage(message);
            builder.SetTitle(title);
            builder.Create().Show();
        }
        #endregion

        #region Firebase 的推播設定用程式碼

        #endregion
    }

    public class AndroidInitializer : IPlatformInitializer
    {
        public void RegisterTypes(IUnityContainer container)
        {

        }
    }
}

執行與測試

  • 您需要有安裝 Google Play 服務的裝置,才能夠進行底下的測試

Xamarin.Forms 與 Azure Mobile App Lab 12

在 Azure 中,加入訊息推播設定

建立通知中樞

  • 首先,開啟 Microsoft Azure 找到 XamarinAzureDay 行動App ,並且開啟它
  • 從 XamarinAzureDay - 推送 刀鋒視窗中,點選 推送
  • 在右邊橘色列中,點選 連結
  • 在 通知中樞 刀鋒視窗中,點選 + 通知中樞
  • 在 通知中樞 新增通知中樞 刀鋒視窗中的 通知中樞 欄位,輸入 DoggyEnterpriseHub
  • 在命名空間欄位下,點擊 或建立新的
  • 請在 新建立的命名空間 欄位中輸入 DoggyEnterpriseNamespace
  • 勾選底下的 釘到儀表板,並點選 確定 按鈕
  • 等候 通知中樞 部署完成

設定 Goolge Firebase Cloud Messaging (FCM) 的連結

  • 開啟 Firebase 網站 https://console.firebase.google.com/
  • 點選 建立專案
  • 請在建立專案對話窗中,輸入
    專案名稱 : DoggyEnterpriseHub
    國家/地區 : 台灣
  • 在 Firebase 專案建立完成後,點選 將Firebase加入您的Android應用程式
  • 請在 將Firebase加入您的Android應用程式對話窗中,輸入
    套件名稱 : com.miniasp.xfdoggyEnterprise
    應用程式暱稱 : DoggyEnterpriseHub
    偵錯簽署憑證 SHA-1 (選填) : EC:D3:A8:EA:DD:01:E3:58:5A:46:A5:76:5A:A6:85:13:AD:D3:60:B5
    其中,套件名稱 可以點選滑鼠雙擊 Android 原生專案的 Properties 節點,從 Android Manifest 頁次中的 Package Name來取得
    關於 偵錯簽署憑證 SHA-1 欄位,您可以開啟電腦中的 命令提示字元視窗,輸入:
    "C:\Program Files\Java\jdk1.8.0_102\bin\keytool" -exportcert -list -v -alias androiddebugkey -keystore "C:\Users\%username%\AppData\Local\Xamarin\Mono for Android\debug.keystore"
    其中 keytool 這個工具您可以從您電腦中的 Java JDK 安裝路徑中找到
    您的除錯用的程式碼簽名檔案,將位於 C:\Users\%username%\AppData\Local\Xamarin\Mono for Android\debug.keystore
    執行結果如下所示,請複製 SHA1 的欄位值,貼到網頁上即可
    另外,預設密碼為 android
  • 最後,請點選 新增應用程式 按鈕
  • 接著,點選 繼續 按鈕
  • 點選,完成 按鈕
  • 請在 DoggyEnterprise 專案頁面中,點選齒輪圖示
  • 在彈出功能表中,選擇 專案設定
  • 在設定頁面,切換到 CLOUD MESSENAGING 標籤頁次
    請將 伺服器金鑰 / 寄件者 ID 兩個欄位值複製起來,等下會用到

將 Firebase 設定 綁定到 Azure 推播中樞

  • 現在回到 Azure 頁面,點選 設定推播通知服務
  • 在 推播通知服務 DoggyEnterpriseHub 刀鋒視窗中,點選 `Google (GCM)
  • 在 Google (GCM) DoggyEnterpriseHub 刀鋒視窗內的 API 金鑰欄位,輸入在 Firebase 內取得的 伺服器金鑰
  • 接著,點選 儲存 按鈕