XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2019/04/18

Xamarin.Forms 之 XAML 設計預覽

當 Visual Studio 2019 推出之後,我個人覺得最為振奮的一個新功能就是,Xamarin.Froms 的頁面預覽功能,也就是說,當在進行 Xamarin.Forms 的頁面內容設計的時候,只要在 XAML 上進行各種修正或者增刪動作的時候,可以即時看到這個頁面的實際執行時期的效果。

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式
現在,使用 Prism Template Pack 專案樣板來建立一個專案(在這個時間點,所使用的 Prism Template Pack 擴充功能版本為 2.1.6 ),並且打開 MainPage.xaml 這個檔案,在 MainPage.xaml 視窗的右上方,可以參考下圖,會有兩個按鈕圖示,可以用於切換該頁面要顯示 XAML 設計語言或者是預覽實際執行結果。
因此,想要進行預覽頁面的時候,請點選 [設計] 圖示,而想要查看這個頁面的 XAML 語言的時候,可以點選 [來源] 圖示;不過,當在進行設計頁面的時候,需要這樣反覆的切換,似乎也顯得不太方便,因此,個人通常會使用該視窗右下角的 [垂直分割] 這個功能,將這個 XAML 語言與預覽部分,同時顯示在螢幕上。
可是,現在看到在預覽子視窗中看到底下的錯誤訊息,並且是無法預覽到這個頁面的執行結果畫面。
Xamarin.Forms 預覽程式已經更新,現在需要 Xamarin.Forms 3.6 或更新版本。請將您的專案升級成最新版的 Xamarin.Forms 來啟用預覽程式。
因此,使用滑鼠右擊 Xamarin.Forms 的 [方案],選擇 [管理方案的 NuGet 套件],接著點選 [更新] 這個標籤頁次
勾選 [選取所有封裝] 這個檢查盒,接著,點選 [更新] 按鈕,將所有的套件更新到最新版本
下圖為此次更新的所有相關套件清單,直接點選 [確定] 按鈕,完成更新動作
完成後,將 [MainPage.xaml] 視窗關閉起來,並且重新開啟這個檔案,如此,就會看到該頁面的預覽效果了
現在,請在這個 MainPage.xaml 頁面上,填入底下的 XAML 語言,此時,就會看到在螢幕右方的 XAML Previewer 預覽器畫面,即時呈現出這個剛剛加入的 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="BlankApp5.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Label Text="XAML Preview" />
        <Button Text="確定"/>
        <BoxView Color="Red"/>
        <ProgressBar Progress="70"/>
    </StackLayout>

</ContentPage>



2019/04/17

自動轉換生成 iOS 與 Android 使用的各種不同尺寸之應用程式 Icon

自動轉換生成 iOS 與 Android 使用的各種不同尺寸之應用程式 Icon

當使用 Prism Template Pack 專案樣板產生一個 Xamarin.Forms 專案後,並且開發完成相關需求程式碼,此時,將會發現到,不論 iOS 或者 Android 平台下的該應用程式圖示,並不是程式設計師所想要到,那麼,要怎麼進行設定與變更了。

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

不論在 iOS 或者 Android 平台下,對於應用程式圖示 Application Icon,需要提供不同縮放比例大小的圖片檔案,而且,在不同平台下,對於這些圖片資源檔案的命名規則也都不盡相同,在 iOS 平台下,每個圖片都會放置在 Resource 的目錄下,並且每個圖片都可以有三種尺寸,正常大小、放大兩倍與放大三倍,不同放大倍率的圖片,則需要該圖片檔案的檔名(在 .副檔案名稱前)後,加入 @1x, @2x, @3x,另外,對於正常大小的 @1x 這個文字,是可以省略的。
而對於 Android 平台下,不同放大倍率的圖片,其圖片檔案名稱都是相同的,不過,不同放大倍率的圖片,需要放置到 Resources 目錄下的不同目錄下,在 Android 平台下,共有這些放大倍率可以選擇:
附加名稱DPI放大倍率
mdpi160
hdpi2401.5×
xhdpi320
xxhdpi480
xxxhdpi640

對於應用程式圖示 Application Icon ,需要在不同放大倍率代表附加名稱前面,要加上 mipmap- 這樣的文字,例如:mipmap-mdpi,對於一般的圖片資源,需要在不同放大倍率的代表附加名稱前面,要加上 drawable 這樣的文字,例如: drawable-xhdpi。
因此,當視覺設計師完成該應用程式的圖片檔案設計之後,需要請視覺設計師產出這些不同平台需要用到的放大倍率圖片,以及根據該行動平台的規範,將不同倍率的圖片,設定為適當的檔案名稱,或者要放大適當的目錄下。
為了要解決這些問題,所以設計了 AppIconBuilder 這個專案,該專案是由兩個類別來組成 iOSImageDefinition / AndroidImageDefinition ,而完整的原始碼可以從從 Github 取得
C Sharp / C#
namespace AppIconBuilder
{
    public class iOSImageDefinition
    {
        public List<iOSImageItem> Images { get; set; }
        public Info info { get; set; }
        public void CalculateSize()
        {
            foreach (var item in Images)
            {
                item.CalculateSize();
            }
        }
        public void GenerateIcons(string mainPath)
        {
            if (Directory.Exists(mainPath) == false)
            {
                Directory.CreateDirectory(mainPath);
            }
            foreach (var item in Images)
            {
                using (Image<Rgba32> image = Image.Load("icon.png"))
                {
                    image.Mutate(x => x
                         .Resize((int)item.Width, (int)item.Height));
                    string filename = Path.Combine(mainPath, item.Filename);
                    image.Save(filename); // Automatic encoder selected based on extension.
                }
            }

        }
    }
    public class iOSImageItem
    {
        public string Idiom { get; set; }
        public string Size { get; set; }
        public string Scale { get; set; }
        public string Filename { get; set; }
        public double Width { get; set; }
        public double Height { get; set; }

        public void CalculateSize()
        {
            var fooSize = Convert.ToDouble(Size.Split('x')[0]);
            var fooScale = Convert.ToInt32(Scale.Replace("x", ""));
            Width = fooSize * fooScale;
            Height = fooSize * fooScale;
        }
    }

    public class Info
    {
        public int Version { get; set; }
        public string Author { get; set; }
    }
}
C Sharp / C#
namespace AppIconBuilder
{
    public class AndroidImageDefinition
    {
        public List<AndroidImageItem> Images { get; set; }
        public void Initialization()
        {
            Images = new List<AndroidImageItem>();
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-hdpi",
                Filename = "ic_launcher.png",
                Width = 72,
                Height = 72,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-ldpi",
                Filename = "ic_launcher.png",
                Width = 36,
                Height = 36,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-mdpi",
                Filename = "ic_launcher.png",
                Width = 48,
                Height = 48,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-xhdpi",
                Filename = "ic_launcher.png",
                Width = 96,
                Height = 96,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-xxhdpi",
                Filename = "ic_launcher.png",
                Width = 144,
                Height = 144,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-xxxhdpi",
                Filename = "ic_launcher.png",
                Width = 192,
                Height = 192,
            });
        }
        public void ChangeRole()
        {
            foreach (var item in Images)
            {
                item.Path = item.Path.Replace("mipmap", "drawable");
                item.Filename = item.Filename.Replace("ic_launcher", "icon");
            }
        }
        public void GenerateIcons(string mainPath)
        {
            foreach (var item in Images)
            {
                string path = Path.Combine(mainPath, item.Path);
                if (Directory.Exists(path) == false)
                {
                    Directory.CreateDirectory(path);
                }
                using (Image<Rgba32> image = Image.Load("icon.png"))
                {
                    image.Mutate(x => x
                         .Resize((int)item.Width, (int)item.Height));
                    string filename = Path.Combine(path, item.Filename);
                    image.Save(filename); // Automatic encoder selected based on extension.
                }
            }

        }
    }
    public class AndroidImageItem
    {
        public string Filename { get; set; }
        public string Path { get; set; }
        public double Width { get; set; }
        public double Height { get; set; }
    }
}
底下為這兩個類別的使用方式
C Sharp / C#
namespace AppIconBuilder
{
    class Program
    {
        // http://iconhandbook.co.uk/reference/chart/
        static async Task Main(string[] args)
        {
            string mainPath = "iOS";
            var fooContents = await File.ReadAllTextAsync("Contents.json");
            iOSImageDefinition imageDefinition = JsonConvert.DeserializeObject<iOSImageDefinition>(fooContents);
            imageDefinition.CalculateSize();
            imageDefinition.GenerateIcons(mainPath);

            mainPath = "Android_Launcher";
            AndroidImageDefinition androidImageDefinition = new AndroidImageDefinition();
            androidImageDefinition.Initialization();
            androidImageDefinition.GenerateIcons(mainPath);

            mainPath = "Android_Resource";
            androidImageDefinition.ChangeRole();
            androidImageDefinition.GenerateIcons(mainPath);

            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }
}



2019/04/13

使用 Xamarin.Essentials 進行檔案的同步或非同步存取

使用 Xamarin.Essentials 進行檔案的同步或非同步存取

Xamarin.Essentials 提供非常多豐富好用的功能,其中一個 [檔案系統協助程式] 功能,可以用於在應用程式所在的儲存空間內使用快取與資料目錄存取功能,一旦取得了該特定目錄下的檔案之後,就可以使用 .NET Framework 的 System.IO 命名空間下的 API,進行目錄的新增、建立或刪除,也可以針對檔案進行建立、寫入、刪除的功能。
當在開發一個行動應用 App 的時候,往往需要能夠將這個 App 當時的執行狀態寫入到裝置內,等到下次這個 App 再度重新啟動的時候,可以從裝置的儲存體中將資料讀取回來。絕大部分的行動開發者應該都會直接想使用到 SQLite 來做到這樣的事情,但是作者卻比較偏好輕巧的解決方案,那就是把程式中的 .NET 物件經過 Json.NET 序列化之後,把這些 JSON 文字寫入到檔案內;當想要取回的時候,只需要從檔案把這些字串讀取出來,接著透過 Json.NET 反序列化這些 JSON 字串,這樣,當初的 .NET 中使用的物件,就還原回來了。
由於 Xamarin.Forms 屬於一個 GUI 類型的應用程式,也就是說,所有要針對 UI 相關控制項變更的行為需求,都需要在 UI 執行緒 Thread 下來進行執行,否則會發生例外異常;因此,當要進行檔案存取的動作是在 UI 執行緒執行的時候,會有可能因為檔案存取的動作需要花費些時間,因此,會造成整個應用程式的畫面不太流暢;故,這個時候可以將這些檔案 I/O 相關的動作,以非同步的方式來使用,就可以解決這些問題。
在這個範例中,將會說明如何使用同步與非同步的方式來進行檔案的存取。

建立一個 使用 Xamarin.Essentials 進行檔案存取的 專案

  • 開啟 Visual Studio 2019 程式
  • 當 Visual Studio 2019 開始 視窗 出現之後,請點選左下角的 [建立新專案] 選項
  • 當 [建立新專案] 對話窗出現之後,請在中間最上方的搜尋文字輸入盒中輸入 [prism] 關鍵字,搜尋所有與 Prism 有關的專案樣板
  • 請選擇 [Prism Blank App (Xamarin.Forms)] 這個專案樣板
  • 當出現 [設定新的專案] 對話窗,請在 [專案名稱] 輸入 [FileAccess]
  • 最後點選該對話窗右下方的 [建立] 按鈕
  • 現在將會看到 [PRISM PROJECT WIZARD] 對話窗,請勾選 ANDROID, iOS, UWP 三個行動裝置平台,接著在底下 [Container] 下拉選單,選擇 Unity 項目
  • 最後,點選 [CREATE PROJECT] 按鈕,以便產生 Xamarin.Forms 專案

安裝需要用到的 PropertyChanged.Fody NuGet 套件

  • 當這個 Xamarin.Forms 專案建立成功之後,請在該方案中,找到 Xamarin.Forms 使用的專案,請在該專案中,使用滑鼠右擊 [相依性] 節點,選擇 [管理 NuGet 套件] 選項
  • 在 [NuGet: XXX] 視窗中,點選 [瀏覽] 標籤頁次,並且在下方的搜尋文字輸入盒中,輸入 [propertychanged.fody] 關鍵字,搜尋出這個 NuGet 套件
  • 當出現 [PropertyChanged.Fody] NuGet 套件,請點選該套件,並且點選右方的 [安裝] 按鈕,將這個套件安裝到 Xamarin.Forms 專案內
  • 請查看 Xamarin.Forms 專案內,並沒有 [FodyWeavers.xml] 這個檔案,因此,使用滑鼠右擊 Xamarin.Forms 專案節點,選擇 [建置] 選項
  • 當建置完成之後,在這個 Xamarin.Forms 專案內將會出現 [FodyWeavers.xml] 檔案

安裝需要用到的 Newtonsoft.Json NuGet 套件

  • 在 [NuGet: XXX] 視窗中,搜尋文字輸入盒中,輸入 [Newtonsoft.Json] 關鍵字,搜尋出這個 NuGet 套件
  • 當出現 [Newtonsoft.Json] NuGet 套件,請點選該套件,並且點選右方的 [安裝] 按鈕,將這個套件安裝到 Xamarin.Forms 專案內

安裝需要用到的 Xamarin.Essentials NuGet 套件

  • 在 [NuGet: XXX] 視窗中,搜尋文字輸入盒中,輸入 [Xamarin.Essentials] 關鍵字,搜尋出這個 NuGet 套件
  • 當出現 [Xamarin.Essentials] NuGet 套件,請點選該套件,並且點選右方的 [安裝] 按鈕,將這個套件安裝到 Xamarin.Forms 專案內

修正因為安裝 Xamarin.Essentials 帶來的錯誤

現在,可以從 Visual Studio 2019 的錯誤視窗中,看到底下的錯誤訊息
NU1107    偵測到 Xamarin.Android.Support.Compat 有版本衝突。請將 Xamarin.Android.Support.Compat 28.0.0.1 直接安裝/參考到專案 FileAccess.Android 來解決此問題。 
 FileAccess.Android -> FileAccess -> Xamarin.Essentials 1.1.0 -> Xamarin.Android.Support.Compat (>= 28.0.0.1) 
 FileAccess.Android -> Xamarin.Android.Support.Design 27.0.2.1 -> Xamarin.Android.Support.Compat (= 27.0.2.1).    FileAccess.Android    D:\Vulcan\GitHub\Xamarin2019\FileAccess\FileAccess\FileAccess.Android\FileAccess.Android.csproj    1
想要解決此一問題:
  • 使用滑鼠右擊方案節點,選擇 [管理方案的 NuGet 套件]
  • 點選 [更新] 標籤頁次
  • 勾選該標籤頁次內的所有項目
  • 點選右上方的更新按鈕,就可以升級這些套件到最新版本了

建立資料存取模型

  • 滑鼠右擊 Xamarin.Forms 專案,選擇 [加入] > [新增資料夾]
  • 將新增資料夾的名稱設定為 [DataModels]
  • 滑鼠右擊 [DataModels] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗下方的 [名稱] 欄位中,輸入 [UserInfo]
  • 點選右下方的 [新增] 按鈕
  • 將底下程式碼填入到這個新建立的類別檔案內
C Sharp / C#
using System;
using System.Collections.Generic;
using System.Text;

namespace FileAccess.DataModels
{
    public class UserInfo
    {
        public string Account { get; set; }
        public string Password { get; set; }
    }
}

修正 View 與 ViewModel

  • 在 Xamarin.Forms 專案內的 [Views] 資料夾內,找到 MainPage.xaml 檔案,並且打開它
  • 使用底下 XAML 語言替換掉這個檔案內的 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="FileAccess.Views.MainPage"
             Title="檔案存取之開發">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
                 Margin="20">
        <Label Text="帳號" />
        <Entry Text="{Binding Account}" Placeholder="請輸入帳號"/>
        <Label Text="密碼" />
        <Entry Text="{Binding Password}" Placeholder="請輸入密碼"/>
        <Label Text="檔案路徑" />
        <Button Text="清空輸入" Command="{Binding CleanCommand}"/>
        <Label Text="{Binding FilePath}" FontSize="14" />
        <StackLayout Orientation="Horizontal">
            <Button Text="同步讀取" Command="{Binding SyncFileReadCommand}"/>
            <Button Text="同步寫入" Command="{Binding SyncFileWriteCommand}"/>
        </StackLayout>
        <StackLayout Orientation="Horizontal">
            <Button Text="簡易非同步讀取" Command="{Binding AsyncSimpleFileReadCommand}"/>
            <Button Text="簡易非同步寫入" Command="{Binding AsyncSimpleFileWriteCommand}"/>
        </StackLayout>
        <StackLayout Orientation="Horizontal">
            <Button Text="非同步方法讀取" Command="{Binding AsyncFileReadCommand}"/>
            <Button Text="非同步方法寫入" Command="{Binding AsyncFileWriteCommand}"/>
        </StackLayout>
    </StackLayout>

</ContentPage>
  • 在 Xamarin.Forms 專案內的 [ViewModels] 資料夾內,找到 MainPageViewModel.cs 檔案,並且打開它
  • 使用底下 C# 敘述替換掉這個檔案內的 C# 敘述
C Sharp / C#
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FileAccess.ViewModels
{
    using System.ComponentModel;
    using System.IO;
    using System.Threading.Tasks;
    using FileAccess.DataModels;
    using Newtonsoft.Json;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using Xamarin.Essentials;

    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Account { get; set; }
        public string Password { get; set; }
        public string FilePath { get; set; }
        public DelegateCommand CleanCommand { get; set; }
        public DelegateCommand SyncFileReadCommand { get; set; }
        public DelegateCommand SyncFileWriteCommand { get; set; }
        public DelegateCommand AsyncSimpleFileReadCommand { get; set; }
        public DelegateCommand AsyncSimpleFileWriteCommand { get; set; }
        public DelegateCommand AsyncFileReadCommand { get; set; }
        public DelegateCommand AsyncFileWriteCommand { get; set; }
        string filename = "User.txt";
        private readonly INavigationService navigationService;

        public MainPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;
            CleanCommand = new DelegateCommand(() =>
            {
                Account = ""; Password = "";
            });
            SyncFileReadCommand = new DelegateCommand(() =>
            {
                string path = Path.Combine(FileSystem.AppDataDirectory, "Datas");
                if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
                FilePath = Path.Combine(path, $"Sync{filename}");
                if (File.Exists(FilePath))
                {
                    var content = File.ReadAllText(FilePath);
                    var userInfo = JsonConvert.DeserializeObject<UserInfo>(content);
                    Account = userInfo.Account;
                    Password = userInfo.Password;
                }
            });
            SyncFileWriteCommand = new DelegateCommand(() =>
            {
                string path = Path.Combine(FileSystem.AppDataDirectory, "Datas");
                if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
                FilePath = Path.Combine(path, $"Sync{filename}");
                var userInfo = new UserInfo()
                {
                    Account = Account,
                    Password = Password,
                };
                var content = JsonConvert.SerializeObject(userInfo);
                File.WriteAllText(FilePath, content);
            });
            AsyncSimpleFileReadCommand = new DelegateCommand(async () =>
            {
                string path = Path.Combine(FileSystem.AppDataDirectory, "Datas");
                if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
                FilePath = Path.Combine(path, $"AsyncSimple{filename}");
                if (File.Exists(FilePath))
                {
                    var content = await Task.Run(() =>
                    {
                        return File.ReadAllText(FilePath);
                    });
                    var userInfo = JsonConvert.DeserializeObject<UserInfo>(content);
                    Account = userInfo.Account;
                    Password = userInfo.Password;
                }
            });
            AsyncSimpleFileWriteCommand = new DelegateCommand(async () =>
            {
                string path = Path.Combine(FileSystem.AppDataDirectory, "Datas");
                if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
                FilePath = Path.Combine(path, $"AsyncSimple{filename}");
                var userInfo = new UserInfo()
                {
                    Account = Account,
                    Password = Password,
                };
                var content = JsonConvert.SerializeObject(userInfo);
                await Task.Run(() =>
                {
                    File.WriteAllText(FilePath, content);
                });
            });
            AsyncFileReadCommand = new DelegateCommand(async () =>
            {
                string path = Path.Combine(FileSystem.AppDataDirectory, "Datas");
                if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
                FilePath = Path.Combine(path, $"Async{filename}");
                if (File.Exists(FilePath))
                {
                    using (var fileStream = File.Open(FilePath, FileMode.Open))
                    {
                        using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
                        {
                            var content = await streamReader.ReadToEndAsync();
                            var userInfo = JsonConvert.DeserializeObject<UserInfo>(content);
                            Account = userInfo.Account;
                            Password = userInfo.Password;
                        }
                    }
                }
            });
            AsyncFileWriteCommand = new DelegateCommand(async () =>
            {
                string path = Path.Combine(FileSystem.AppDataDirectory, "Datas");
                if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
                FilePath = Path.Combine(path, $"Async{filename}");
                using (var fileStream = File.Create(FilePath))
                {
                    using (var streamWriter = new StreamWriter(fileStream, Encoding.UTF8))
                    {
                        var userInfo = new UserInfo()
                        {
                            Account = Account,
                            Password = Password,
                        };
                        var content = JsonConvert.SerializeObject(userInfo);
                        await streamWriter.WriteAsync(content);
                    }
                }
            });
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
        }

    }
}

同步檔案讀寫的程式碼用法

當要寫入 .NET 物件到檔案內,在這裡範例中,將會把使用者輸入的帳號與密碼寫入到 UserInfo 類別物件內,接著使用 JsonConvert.SerializeObject(userInfo) 敘述將這個 .NET 物件轉換成為 JSON 字串表示內容。
然後透過 Xamarin.Essentinals 的 檔案系統協助程式 所提供的 API,取得該應用程式專屬的檔案存取沙箱目錄,這裡使用 FileSystem.AppDataDirectory 來取得,在這個沙箱目錄下所建立的檔案,僅僅提供該應用程式來存取,其他的應用程式、甚至該手機的使用者,無法看到與讀寫這些存在於沙箱目錄內的檔案。
在 Android 平台下,這個沙箱目錄會類似這樣的字串:/data/user/0/com.companyname.appname/files/Datas/SyncUser.txt
在 iOS 平台下,這個沙箱目錄會類似這樣的字串:/Users/vulcan/Library/Developer/CoreSimulator/Devices/EED56A19-C96E-4AF5-A5FA-83E07AB7E2A3/data/Containers/Data/Application/177E5C36-AC6F-450E-97EF-DA1AF562D29F/Library/Datas/SyncUser.txt
有了一個檔案系統下的目錄,接著便可以使用 System.IO 命名空間所提供的 File.WriteAllText API 來將剛剛產生的 JSON 字串寫入到指定的目錄下,而想要讀取特定檔案,可以使用 File.ReadAllText API 來讀取該檔案內的所有字串內容,再取得這些內容之後,便可以使用 JsonConvert.DeserializeObject(content) 這個敘述,將原先的 JSON 內容,還原成為 .NET 物件。
不過,在 .NET Standard 2.0 下,對於 System.IO 命名空間下的 File.WriteAllText / File.ReadAllText 這兩個 API,僅提供了同步呼叫的使用方式,若想要使用非同步方式來讀寫文字檔案內容,可以參考底下兩種做法;至於為什麼要使用非同的方式來進行檔案的讀寫工作呢?這一切都是要保持該 Xamarin.Forms 應用程式的 GUI 以最佳流暢的狀態下來執行。

簡單將同步檔案讀寫的程式碼轉換成為 Task 物件之用法

首先,最簡單的做法就是把這些同步運作的程式碼,指定到 Task.Run(() =>{}) 這個靜態方法內的委派方法內,因此,就會得到一個 Task 的物件,代表一個非同步的工作。現在,可以在這個方法內使用 await 關鍵字來等候剛剛取得的 工作 Task 類別物件,不過,要再方法函式內使用 await 關鍵字,需要在該方法回傳型別前,加入 async 這個修飾詞,並且該函式的迴船型別僅能為 Task, Task, void 這三種而已,若指定了其他型別,會造成編譯時期的錯誤。

非同步檔案讀寫的程式碼用法

當然 System.IO 命名空間內還提供了其他的關於檔案存取的非同步 API,在此,可以透過 File.Creeate 方法建立一個 FileStream 的物件,準備針對這個產生的檔案進行寫入的動作;有了這個 FileStream 物件,接著使用 StreamWriter 類別,使用剛剛的 FileStream 物件來建立起一個 StreamWriter 物件,如此,便可以使用 await streamWriter.WriteAsync 這樣的敘述來將文字內容,以非同步呼叫方式來寫入到檔案內。
反之,若要使用非同步方式讀取出檔案內的內容,可以使用 File.Open API,開啟指定的檔案(當然,最好還是事先檢查一下該檔案是否存在於該裝置的檔案系統內),便可以得到一個 FileStream 的物件,然後使用該物件來建立起一個 StreamReader 物件,如此,便可以使用 await streamReader.ReadToEndAsync() 這樣的非同步程式碼寫法,將檔案內的字串讀取出來,接著,使用 JsonConvert.DeserializeObject() 方法,把剛剛讀取出來的字串,還原成為 .NET 中的物件。

建置與執行和測試結果

現在,可以分別在不同的行動平台下來執行這個專案,只要將內容寫入到應用程式沙箱目錄下的檔案,除非該應用程式移除後又重新安裝起來,否則,該檔案會持續存在於這個裝置內,就算該應用程式進行升級動作,這些檔案也同樣的會持續存在。因此,可以嘗試將已經啟動的 App,強制進行關閉,讓這個 App 不再存在於裝置記憶體中,接著重新再度啟動,將會發現到,還是可以看到剛剛寫入的檔案內容。

在 Android 上進行測試

  • 請設定預設起始專案為 Android 的專案
  • 指定要在哪個模擬器或者實體裝置下來執行這個專案
  • 在工具列上點選率色三角形按鈕,執行這個 Android 專案
  • 底下是在 Android 平台下執行結果

在 iOS 上進行測試

  • 請設定預設起始專案為 iOS 的專案
  • 在工具列視窗中,在平台方案之下拉選單中,選擇 [iPhoneSimulator] 這個選項
  • 在平台方案右方綠色三角形之啟動按鈕,點選該按鈕右方的下拉選單的黑色三角形符號,現在可以看到該 Mac 電腦上所有可用的模擬器,在此選擇 [iPhone 8 iOS 12.2] 這個選項
  • 現在可以直接點選剛剛的綠色按鈕,啟動這個專案在 iOS 模擬器上來執行
  • 底下是在 iOS 平台下執行結果