SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

Xamarin.Forms Facebookアプリの作成 (軽量 Facebook.SDK for Xamarin.Forms )

【 Xamarin 記事一覧 】


2015.04.29 Xamarin.FormsやFacebook APIのバージョンが上がり、変更された部分があるので、記事を修正・追記します。

現時点で、最新パッケージで再構築しました。
PM> Get-Package
Id Version Description/Release Notes -- ------- -------------------------
ExifLib.PCL 1.0.0 Portable EXIF library for Windows 8, ...
Microsoft.Bcl 1.1.10 This packages enables projects targeting ...
Microsoft.Bcl.Build 1.0.14 This package provides build infrastructure ...
Microsoft.Net.Http 2.2.29 This package includes HttpClient for sending ...
Newtonsoft.Json 6.0.5 Json.NET is a popular high-performance JSON ...
WPtoolkit 4.2013.08.16 Windows Phone toolkit provides a collection ...
Xamarin.Android.Support.v4 21.0.3.0 C# bindings for android support library v4....
Xamarin.Forms 1.4.2.6355 Build native UIs for iOS, Android, and ...
Xamarin.Forms.Labs 1.2.0 Please update to XLabs.Core
Xamarin.Forms.Labs.Services... 1.2.0 Xamarin Forms Labs is a open source project ...

1 FacebookSDK

Xamarinコンポーネント(https://components.xamarin.com/)には、既にFacebookSDKがありますが、2014/10/11現在、iOS用とAndroid用しか提供されていません。

f:id:furuya02:20141011113036p:plain:w400:left

https://components.xamarin.com/view/facebook-sdk

SDKと言っても、単なるWebAPIのラッパーですので、本来、機種に依存するものではないはずです。
しかし、唯一認証の時だけは、「ログイン」や「アクセス許可」のUIをユーザに提供するためブラウザが必要となり、これがSDKを機種ごとにしている理由となっています。

今回は、WebViewコントロールを使用することで、Xamarin.FormsでFacebookアプリを作成してみました。

なお、WebViewを使用するにあたり問題となったのは、以下3点です。
(1) 画面遷移のイベントを処理できない
(2) Cookieを処理できない
(3) JavaScriptの有効無効が制御できない

そして、これらの問題は、すべてレンダラーの実装で対応しました。
現時点では、まだWebViewが非常に非力なため、イベントやプロパティが充実していませんが、今後、拡充されてくれば、これらのレンダラー実装は必要なくなるかも知れません。

次の図は、各デバイスからFacebook認証をしているようすです。

Android
f:id:furuya02:20141011123003p:plain:w100:left f:id:furuya02:20141011122957p:plain:w100:left f:id:furuya02:20141011122958p:plain:w100:left f:id:furuya02:20141011123000p:plain:w100:left f:id:furuya02:20141011123001p:plain:w100:left
iOS
f:id:furuya02:20141011124042p:plain:w100:left f:id:furuya02:20141011124044p:plain:w100:left f:id:furuya02:20141011124038p:plain:w100:left f:id:furuya02:20141011124040p:plain:w100:left f:id:furuya02:20141011124041p:plain:w100:left
Windows Phone
f:id:furuya02:20141011124305p:plain:w100:left f:id:furuya02:20141011124307p:plain:w100:left f:id:furuya02:20141011124308p:plain:w100:left f:id:furuya02:20141011124302p:plain:w100:left f:id:furuya02:20141011124304p:plain:w100:left

※全コードは、GitHubにおきましたので、良かったら見てやってください(2015.04.29更新)
https://github.com/furuya02/Xamarin.Forms.FacebookSample


2 パッケージの追加

FacebookSDKを共有プロジェクトに作成するために、次の2つのライブラリをインストールしました。

・HttpClient(facebookへのHTTPリクエストのため)
PM> Install-Package Microsoft.Net.Http [共通プロジェクトのみ]


・JsonNET(facebookからのJSON形式のデータを処理するため)
PM> Install-Package Xamarin.Forms.Labs.Services.Serialization.JsonNET [共通プロジェクトのみ]


3 レンダラーの実装

WebViewを使用する上で問題となった、「遷移イベント」「クッキー処理」「スクリプトの有効化」については、各デバイスのレンダラーで実装しました。

(1) Formsでのカスタムコントロール

まずは、WebViewを継承した独自コントロールの定義ですが、これは、画面遷移のイベントを定義しただけです。

public class ExWebView :WebView{
    public event NavigateHandler Navigate;

    public delegate void NavigateHandler(string request);

    //ナビゲーション変化時のイベント
    public void OnNavigate(string request) {
        if (Navigate != null) {
            Navigate(request);
        }
    }
}

(2) iOSでのレンダラーの実装

iOSでは、MonoTouch.UIKit.UIWebViewコントロールを取得して、画面遷移のイベント処理のために、ShouldStartLoadをトラップしました。
また、初期化時にクッキーを全部削除しました。

[assembly: ExportRenderer(typeof(ExWebView), typeof(ExWebViewRenderer))]
namespace FacebookSample.iOS {
    public class ExWebViewRenderer : WebViewRenderer {


        protected override void OnElementChanged(VisualElementChangedEventArgs e) {
            base.OnElementChanged(e);

            //Xamarin.Formのコントロール(ExWebView)
            var exWebView = e.NewElement as ExWebView;
            //ネイティブコントロール(MonoTouch.UIKit.UIWebView)
            var webView = this;

            webView.ShouldStartLoad = (w, request, naviType) => {
                //イベントをForms側に送る
                exWebView.OnNavigate(request.Url.AbsoluteString);
                return true;
            };

            //クッキー(ログイン情報)の削除
            var storage = NSHttpCookieStorage.SharedStorage;
            foreach (var cookie in storage.Cookies) {
                storage.DeleteCookie(cookie);
            }

        }
    }
}

(3) Androidでのレンダラーの実装

Androidでは、Android.Webkit.WebViewコントロールを取得できますが、
画面遷移のイベント処理は、WebViewClientでしか捉えないため、さらにWebViewClientのカスタムクラスを定義して、その中で、Xamarin.Formsへイベントを送りました。
また、こちらも、初期化時にクッキーを全部削除しています。

[assembly: ExportRenderer(typeof(ExWebView), typeof(ExWebViewRenderer))]
namespace FacebookSample.Droid {

    //v1.4.x
    //public class ExWebViewRenderer : WebRenderer {
    public class ExWebViewRenderer : WebViewRenderer {

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e) {
            base.OnElementChanged(e);

            //Xamarin.Formのコントロール(ExWebView)
            var exWebView = e.NewElement as ExWebView;
            //ネイティブコントロール(Android.Webkit.WebView)
            var webView = this.Control; //Ver1.4.x

            //WebViewではNavigateのイベントを拾えないためWebViewClientを上書きする
            webView.SetWebViewClient(new MyWebViewClient(exWebView));


            //クッキー(ログイン情報)の削除
            CookieManager.Instance.RemoveAllCookie();
        }

    }

    public class MyWebViewClient : WebViewClient {
        private readonly ExWebView _exWebView;

        public MyWebViewClient(ExWebView exWebView) {
            _exWebView = exWebView;
        }
        public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, string url) {
            //イベントをForms側に送る
            _exWebView.OnNavigate(url);
            return false;
        }
    }
}

[2015.04.29追記 クラス名やコントロール名は少し変わりました]

(4) WindowsPhoneでのレンダラーの実装

WindowsPhoneでは、Microsoft.Phone.Controls.WebBrowserコントロールを取得して、画面遷移のイベント処理のために、Navigatingをトラップしました。
また、ここでも初期化時にクッキーを全部削除しています。
なお、iOS/Androidと違って、ここでの.WebBrowserコントロールは、デフォルトでJavaScriptがOFFになっているため、こちらもONにしておきました。

[assembly: ExportRenderer(typeof(ExWebView), typeof(ExWebViewRenderer))]
namespace FacebookSample.WinPhone {
    public class ExWebViewRenderer : WebViewRenderer {
        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e) {
            base.OnElementChanged(e);

            //Xamarin.Formのコントロール(ExWebView)
            var exWebView = e.NewElement as ExWebView;
            //ネイティブコントロール(Microsoft.Phone.Controls.WebBrowser)
            var webBrowser = Control;


            webBrowser.IsScriptEnabled = true; //デフォルトでOFFになっているJavaScriptをONにしないと認証できない

            webBrowser.Navigating += (s, a) => {
                //イベントをForms側に送る
                exWebView.OnNavigate(a.Uri.AbsoluteUri);
            };

            //クッキー(ログイン情報)の削除
            webBrowser.ClearCookiesAsync();
        }

    }

}


4 Facebook SDKの利用

WebViewを使用する上での問題を解消すれば、後は既存のFacebookSDKをそのまま使えば良いはずでしたが、既存のものはXamarin.FormsのPCLで微妙に参照問題がでて呑み込めませんでした。そこで、取りあえず軽量版(なんとA42枚程度)ですが、Facebook SDK の方も自作しました。
軽量版ですが、必要な機能は全部揃っているはずですので、Facebookアプリを作成するうえで困ることは無いと思います。
SDKの実装については、Xamarinに関係ないので割愛します。興味のある方はGitHubの方をご参照下さい。
Xamarin.Forms.FacebookSample/blob/master/FacebookSample/FacebookSample/FacebookClient.cs

以下は、その作成したFacebookSDK(FacebookClient.cs)を利用したサンプルです。

(1) Graph API Sample (ユーザ情報の取得)
(2) FQL Sample (FQLを使用して、友達のカウント)
(3) Post "Hi" to your wall (自分のウォールにメッセージを投稿する)
(4) Remove "Hi" to your wall(上記メッセージを削除する)

以下は、そのコードと、実行画面です。

(1) Graph API

var json = await _fb.GetTaskAsync("me");
var o = JObject.Parse(json);
var msg = "Name: " + o["name"] + "\n" + "First Name: " + o["first_name"] + "\n" + 
     "Last Name: " + o["last_name"] + "\n" +  "Profile Url: " + o["link"];
await DisplayAlert("Your Info",msg, "OK");

f:id:furuya02:20141011130329p:plain:w100:left f:id:furuya02:20141011130513p:plain:w100:left f:id:furuya02:20141011130616p:plain:w100:left

(2) FQL

var query = string.Format(
    "SELECT uid FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1={0})","me()");
var json = await _fb.GetTaskAsync("fql?q="+query);
var o = JObject.Parse(json);
var msg = string.Format("You have {0} friend(s)", o["data"].ToList().Count);
await DisplayAlert("Info",msg, "OK");

f:id:furuya02:20141011130331p:plain:w100:left f:id:furuya02:20141011130515p:plain:w100:left f:id:furuya02:20141011130618p:plain:w100:left

(3) 投稿する

var json = await _fb.PostTaskAsync("me/feed", new { message = "Hi" });
var o = JObject.Parse(json);
var id = (String)o["id"];
if (!String.IsNullOrEmpty(id)){
    var msg = string.Format("You have posted \"Hi\" to your wall. Id: {0}", id);
    await DisplayAlert("Success", msg, "OK");
}

f:id:furuya02:20141011130326p:plain:w100:left f:id:furuya02:20141011130510p:plain:w100:left f:id:furuya02:20141011130613p:plain:w100:left


(4) 投稿の削除

var result = await _fb.DeleteTaskAsync(_lastMessageId);
if (result == "true"){
    var msg = "You have deleted \"Hi\" from you wall.";
    await DisplayAlert("Success", msg, "OK");
}

f:id:furuya02:20141011130328p:plain:w100:left f:id:furuya02:20141011130512p:plain:w100:left f:id:furuya02:20141011130615p:plain:w100:left



5 App IDの作成要領 2015.04.29追記

APIがv2.xになってから、クライアントでブラウザ認証する場合の設定要領が変わったので記録しておきます。

https://developers.facebook.com/apps/
にアクセスして、「Add a New App」を選択します。
f:id:furuya02:20150429091620p:plain

アプリの種類選択で「anvanced setup」を選択します。
f:id:furuya02:20150429091626p:plain

Display Nameに適当な名前を指定し、カテゴリを選択したのち「Create App ID」を選択します。
f:id:furuya02:20150429091641p:plain

新しく作成されたアプリのダッシュボードが開くので、「AppID」をコピーして「Setting」を選択します。
ここでコピーした「App ID」がプログラムで使用されます。
f:id:furuya02:20150429091647p:plain
「Advanced」タブを選択し、下へスクロールします。
f:id:furuya02:20150429091652p:plain
「Embedded browser OAuth Login」を「YES」に変更し、「Save Changes」ボタンを押して完了です。
f:id:furuya02:20150429091657p:plain

以上で完了です。


【 Xamarin 記事一覧 】