XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

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 快速入門] 的使用方式


2018/05/23

Xamarn.Forms TabbedPage 標籤式頁面的不同情境之使用

在從事 Xamarin.Forms 教學經驗中,有許多人都在詢問要如何設計出具有 TabbedPage 標籤式頁面 功能,不過,因為我個人比較少使用這個頁面,通常遇到這樣的視覺需求,我都是自行設計使用者控制向來完成這樣的需求,因此,在這篇文章中,我們將要來體驗 TabbedPage 標籤式頁面在 Xamarin.Forms 中要如何使用?不過,我們在這裡所練習的架構,是採用 Prism 框架下的 MVVM 設計模式,並且需要使用到 Prism7 的版本才能夠時做出來。
這篇文章所提到的所有專案原始碼,可以從 這裡 取得
在我們的練習情境之中,我們需要設計一個應用程式的首頁為 TabbedPage 標籤式頁面,在這個 TabbedPage 標籤式頁面中,共會存在四個標籤 (在 Prism7 中,我們可以使用這個查詢字串 createTab 來完成宣告,這個標籤頁面總共需要有多少個頁面存在),不過,我們需要指定第三個標籤頁面為我們預設顯示的頁面,底下為我們實際執行後的螢幕截圖,預設顯示第三頁面內容 (在 Prism7 中,我們可以使用這個查詢字串 selectedTab 來指定預設顯示的頁次是哪個)。
TabbedPage 標籤式頁面
另外,我們希望切換到第二個標籤頁面的時候,他是具有導航工具列的效果,也就是說,我們切換到第二頁的時候,在第二頁內會有一個按鈕,按下這個按鈕之後,將會導航到另外一個新的頁面,在此同時,螢幕上會有導航工具列出現,如同下圖:
TabbedPage 標籤式頁面 TabbedPage 標籤式頁面
不過,若我們沒有使用 Prism7 所提供的標籤頁面之特定查詢字串,此時,我們將會變成這樣的情境,這樣的結果將不是我們所期望需要的。
TabbedPage 標籤式頁面 TabbedPage 標籤式頁面
接下來,我們來實際練習開發出這樣的應用。

建立練習專案

  • 首先,我們使用 Prism Template Pack (現在使用的 2.0.9 版本)建立起一個 Xamarin.Forms 開發專案
  • 我們需要建立一個 TabbedPage 頁面,我們把它命名為 MainTabbedPage,在這個頁面 View 與頁面檢視 ViewModel 中,我們不需要做任何特別處理。
  • 另外,因為我們需要有許多標籤頁次需要顯示,因此,我們需要建立出五個 ContentPage,所以,我們產生出五個 ContentPage,分別名稱為 Page1, Page2, Page3, Page4, Page21。 Visual Studio 2017 Solution Explorer
  • 接下來,我們需要分別將這五個 ContentPage 的 View 和 ViewModel 進行設計
  • 最後,我們需要修正 Xamarin.Forms 專案的進入點 ( Entry Point ),也就是 App.xaml.cs 檔案,我們將其打開,在 OnInitialized 方法內,將 await NavigationService.NavigateAsync 方法內需要的引數,修改成為 MainTabbedPage?createTab=Page1&createTab=NavigationPage|Page2&createTab=Page3&createTab=Page4&selectedTab=Page3
    在這裡,我們透過了查詢字串中 createTab 這個參數,指定這個標籤式頁面需要那些 ContentPage,由於我們在這個練習中,總共需要用到四個標籤頁面,所以,我們在查詢字串中,總共需要使用到四次的 createTab 。其中,您將會看到在第二個 createTab 中,其設定值似乎與其他的不太一樣,在這裡,我們需要在頁面2中,提供導航到其他頁面的機制,因此,我們需要在 createTab 參數名稱之後,先使用 NavigationPage 緊接著使用管道 | 字元,最後再加上 Page2,這表示了,第二個標籤頁面中,將會顯示出具有導航工具列的效果。
    TabbedPage 標籤式頁面
    而在頁面2中,我們放置了一個按鈕,當您按下這個按鈕之後,將會導航到 Page21 這個頁面,底下是我們所期望呈現的結果。我們可以看到,最上方會出現導航工具列,並且會有回上頁的按鈕出現,不過,新的頁面還是出現在 TabbedPage 標籤式頁面的第二個頁次中。
    TabbedPage 標籤式頁面
    若您的專案需要不想要做到這樣的結果,您可以參考頁面1的設計,在這個頁面中,我們放置了一個按鈕,按下這個按鈕,就一樣會導航到 Page21,不過,當您切換到 頁面1 頁次的時候,看到的是如下螢幕截圖,他與頁面2 的內容完全不一樣,您知道為什麼會有這樣的結果嗎?
    TabbedPage 標籤式頁面
    當我們在頁面1點選了這個按鈕,此時,將會出現如下圖內容。
    TabbedPage 標籤式頁面

MainTabbedPage 的 View 內容

<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
            prism:ViewModelLocator.AutowireViewModel="True"
            x:Class="XFTabbed2.Views.MainTabbedPage">

</TabbedPage>

Page1 的 View 內容

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFTabbed2.Views.Page1"
             Title="{Binding Title}"
             BackgroundColor="LightBlue">

    <StackLayout
        >
        <Button
            Text="Go Page21"
            Command="{Binding GoNextCommand}"/>
    </StackLayout>
</ContentPage>

Page1ViewModel 的 ViewModel 程式碼

using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;

namespace XFTabbed2.ViewModels
{
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    public class Page1ViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Title { get; set; }
        private readonly INavigationService _navigationService;
        public DelegateCommand GoNextCommand { get; set; }
        public Page1ViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
            Title = "頁面 1";
            GoNextCommand = new DelegateCommand(() =>
            {
                _navigationService.NavigateAsync("Page21");
            });
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }
}

Page2 的 View 內容

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFTabbed2.Views.Page2"
             Title="{Binding Title}"
             BackgroundColor="LightGoldenrodYellow">

    <StackLayout
        >
        <Button
            Text="Go Page21"
            Command="{Binding GoNextCommand}"/>
    </StackLayout>

</ContentPage>

Page2ViewModel 的 ViewModel 程式碼

using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;

namespace XFTabbed2.ViewModels
{
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    public class Page2ViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Title { get; set; }
        private readonly INavigationService _navigationService;
        public DelegateCommand GoNextCommand { get; set; }
        public Page2ViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
            Title = "頁面 2";
            GoNextCommand = new DelegateCommand(() =>
            {
                _navigationService.NavigateAsync("Page21");
            });
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }
}

Page21 的 View 內容

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFTabbed2.Views.Page21"
             Title="{Binding Title}"
             BackgroundColor="LightPink">

</ContentPage>

Page21ViewModel 的 ViewModel 程式碼

using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;

namespace XFTabbed2.ViewModels
{
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    public class Page21ViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Title { get; set; }
        private readonly INavigationService _navigationService;

        public Page21ViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
            Title = "頁面 21";
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }
}

Page3 的 View 內容

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFTabbed2.Views.Page3"
             Title="{Binding Title}"
             BackgroundColor="LightSalmon">

</ContentPage>

Page3ViewModel 的 ViewModel 程式碼

using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;

namespace XFTabbed2.ViewModels
{
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    public class Page3ViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Title { get; set; }
        private readonly INavigationService _navigationService;

        public Page3ViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
            Title = "頁面 3";
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }
}

Page4 的 View 內容

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFTabbed2.Views.Page4"
             Title="{Binding Title}"
             BackgroundColor="LightSteelBlue">

</ContentPage>

Page4ViewModel 的 ViewModel 程式碼

using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;

namespace XFTabbed2.ViewModels
{
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    public class Page4ViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Title { get; set; }
        private readonly INavigationService _navigationService;

        public Page4ViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
            Title = "頁面 4";
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }
}



2018/05/22

在 Xamarin-Forms 專案中,第一次體驗 Firebase 資料庫的使用

因為進行 Xamarin 教學課程上的需要,因此,最近特別來研究與體驗如何使用 Firebase 的資料庫功能。
這篇文章的專案原始碼,可以從 這裡 取得
首先,建立起一個 Xamarin.Forms for Prism 專案,讓我們來體驗一下在 C# 中,使用 Firebase 來存取資料庫的應用。
  • 在這個專案中,加入 FirebaseDatabase.net NuGet 套件
  • 接著,我們建立一個資料模型類別,用來宣告要儲存在 Firebase 資料庫內的內容。在這裡,我們宣告一個 MyMoney 類別,用來記錄每筆消費紀錄項目。
public class MyMoney
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public string InvoiceNo { get; set; }
    public int Cost { get; set; }
}
  • 現在,讓我們開始來使用 Firebase,在這個測試範例程式碼中,我們將會做到:將整個資料表刪除、查詢現在所有的紀錄、刪除指定的紀錄、找出特定的紀錄、修改某筆紀錄、新增10筆紀錄等功能。

View 的內容

底下是我們測試的頁面宣告
<?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="XFFirebase.Views.MainPage"
             Title="Firebase 資料庫存取測試">

    <Grid
       >
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="80"/>
        </Grid.RowDefinitions>

        <Editor
            Text="{Binding Output}"/>
        <Button
            Grid.Row="1"
            Text="Start"
            Command="{Binding StartCommand}"/>
    </Grid>

</ContentPage>

ViewModel 的內容

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Firebase.Database.Query;


namespace XFFirebase.ViewModels
{
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using XFFirebase.Models;

    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Output { get; set; }
        public DelegateCommand StartCommand { get; set; }
        private readonly INavigationService _navigationService;

        public MainPageViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
            StartCommand = new DelegateCommand(async () =>
            {
                Output = "建立與 Firebase 連線";
                var client = new Firebase.Database.FirebaseClient("https://xamarindb-3408d.firebaseio.com");
                var child = client.Child("MyMoneys");

                Output = Environment.NewLine + Environment.NewLine + "刪除掉所有的資料";
                await child.DeleteAsync();

                Console.WriteLine("產生 10 筆購物紀錄");
                for (int i = 1; i < 10; i++)
                {
                    await child.PostAsync<MyMoney>(new MyMoney()
                    {
                        Id = Guid.NewGuid(),
                        Title = $"冷泡茶 {i} 瓶",
                        InvoiceNo = $"0000 {i}",
                        Cost = 20 * i,
                    });
                }

                Output += Environment.NewLine + Environment.NewLine + "列出 Firebase 中所有的紀錄";
                var fooPosts = await child.OnceAsync<MyMoney>();
                foreach (var item in fooPosts)
                {
                    Output += Environment.NewLine + $"購買商品:{item.Object.Title} 價格:{item.Object.Cost}";
                }

                Output += Environment.NewLine + Environment.NewLine + "查詢購物價格小於 90 的紀錄";
                var fooRec = fooPosts.Where(x => x.Object.Cost <= 90);
                foreach (var item in fooRec)
                {
                    Output += Environment.NewLine + $"購買商品:{item.Object.Title} 價格:{item.Object.Cost}";
                }

                Output += Environment.NewLine + Environment.NewLine + "刪除購物價格小於 90 的紀錄";
                var fooRecDeleted = fooPosts.Where(x => x.Object.Cost <= 90);
                foreach (var item in fooRecDeleted)
                {
                    await child.Child(item.Key).DeleteAsync();
                    Output += Environment.NewLine + $"購買商品:{item.Object.Title} 價格:{item.Object.Cost} 已經被刪除";
                }


                Output += Environment.NewLine + Environment.NewLine + "列出 Firebase 中所有的紀錄";
                fooPosts = await child.OnceAsync<MyMoney>();
                foreach (var item in fooPosts)
                {
                    Output += Environment.NewLine + $"購買商品:{item.Object.Title} 價格:{item.Object.Cost}";
                }

                Output += Environment.NewLine + Environment.NewLine + "查詢購物價格等於 140 的紀錄";
                var foo140Rec = fooPosts.FirstOrDefault(x => x.Object.Cost == 140);
                foo140Rec.Object.Cost = 666;
                await child.Child(foo140Rec.Key).PutAsync(foo140Rec.Object);
                Output += Environment.NewLine + $"購買商品:{foo140Rec.Object.Title} 的價格已經修正為 價格:{foo140Rec.Object.Cost}";

                Output += Environment.NewLine + Environment.NewLine + "列出 Firebase 中所有的紀錄";
                fooPosts = await child.OnceAsync<MyMoney>();
                foreach (var item in fooPosts)
                {
                    Output += Environment.NewLine + $"購買商品:{item.Object.Title} 價格:{item.Object.Cost}";
                }
            });
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }
}
底下是我們測試頁面的Vidwmodel 相關程式碼

執行結果

Firebase執行結果