Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Facebook Parseの世界

13 805 vues

Publié le

2015年5月14日 マルレク「Facebook Parseの世界」

Publié dans : Mobile
  • DOWNLOAD THIS BOOKS INTO AVAILABLE FORMAT (Unlimited) ......................................................................................................................... ......................................................................................................................... Download Full PDF EBOOK here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... Download Full EPUB Ebook here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... ACCESS WEBSITE for All Ebooks ......................................................................................................................... Download Full PDF EBOOK here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... Download EPUB Ebook here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... Download doc Ebook here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... ......................................................................................................................... ......................................................................................................................... .............. Browse by Genre Available eBooks ......................................................................................................................... Art, Biography, Business, Chick Lit, Children's, Christian, Classics, Comics, Contemporary, Cookbooks, Crime, Ebooks, Fantasy, Fiction, Graphic Novels, Historical Fiction, History, Horror, Humor And Comedy, Manga, Memoir, Music, Mystery, Non Fiction, Paranormal, Philosophy, Poetry, Psychology, Religion, Romance, Science, Science Fiction, Self Help, Suspense, Spirituality, Sports, Thriller, Travel, Young Adult,
       Répondre 
    Voulez-vous vraiment ?  Oui  Non
    Votre message apparaîtra ici

Facebook Parseの世界

  1. 1. Parseの世界 @maruyama097 丸山 不二夫
  2. 2. “I think the biggest mistake that we made, as a company, is betting too much on HTML5 as opposed to native…” Mark Zuckerburg 2012年9月
  3. 3. “Facebook does not hate the Web.” Tom Occhino f8 2015年4月
  4. 4. Agenda  Part I モバイル・アプリをめぐる動向  Part II Parseの世界  Part III Web技術の新しい展開
  5. 5. モバイル・アプリをめぐる動向 このところ、モバイル・アプリの開発スタイルをめぐっ て、注目すべき動きが連続している。アプリ開発のス タイルの大きな変革期に差し掛かっていると考えてい いと思う。 Part I
  6. 6. Part I モバイルアプリをめぐる動向  モバイル・アプリをめぐる新しい動き  新しいモバイル・アプリの諸特徴とその背景  FacebookとWebテクノロジー
  7. 7. Facebook : “Parse” https://parse.com/
  8. 8. Amazon : “AWS Mobile SDK” http://goo.gl/Nc5Bta
  9. 9. Microsoft : “Azure Mobile App Services” http://goo.gl/dcWlRF
  10. 10. Twitter : "Fabric" https://goo.gl/dV3Zsb
  11. 11. モバイルアプリをめぐる新しい動き  Facebook : Parse https://parse.com/  Amazon : AWS Mobile SDK http://goo.gl/Nc5Bta  Microsoft : Azure Mobile App Services http://goo.gl/dcWlRF  Twitter : Fabric https://goo.gl/dV3Zsb
  12. 12. 新しいモバイル・アプリの 諸特徴とその背景 これらの動きは、幾つかの特徴を共有している。ここ では、これらの動きを典型的に表していると思われる Facebook Parseを中心に、旧来のWebアプリと の比較でその特徴を見ることにしよう。あわせて、そ の背景を見ておこう。
  13. 13. 新しいモバイル・アプリの 諸特徴とその背景  Server/Clientから、Cloud/Mobileへ  Server-sideから、Mobile-sideへ  同期型から、非同期型へ  PULLモデルから、PUSHのサポートへ
  14. 14. Server/Clientから Cloud/Mobileへ ネットワーク・プログラミングでのサーバーとクライアン トの役割は、現在も基本的なものである。ただ、その 役割の主要な担い手は、クラウドとモバイルに、大きく 変わりつつある。現在、もっとも一般的なアプリ開発 の舞台は、クラウドとモバイルであることに注意しよう 。
  15. 15. Parse Old Web Apps Cloud & Mobile Server & Client
  16. 16. モバイルは、人類史上最大の プラットフォーム
  17. 17. Mooreの法則で チップは、こんなにも小さくなった ウエアラブル用 Cortex A7 UP 500MHz, 0.36mm2 IOT用 Cortex M0 40MHz, 0.05mm2 ハイエンド ウエアラブル用 Cortex A7 MP2 500MHz, 1.1mm2 モバイル用 Cortex A7 MP2 1.3GHz, 2.2mm2
  18. 18. Qualcomm のフラグシップ・モバイルCPUの パフォーマンスの変化 指数関数的に、スピードアップしている
  19. 19. PCとモバイルのプロセッサー 歴史的には、モバイルのプロセ ッサーは、PCに数年遅れていた。 ただし、現在(2014年)では、処 理能力でもメモリーのアクセス・ スピードでも、PCに並び、コア数 では、それを追い越している。 処理速度 コア数 メモリー・アクセス
  20. 20. Server-sideから Mobile-sideへ 旧来のエンタープライズ・アプリの中心は、サーバー・ サイドのWebアプリであった。ただ、現在進行中の変 化は、モバイル・サイドのアプリ開発が、今後、主流に なるだろうことを示している。この点では、Thin Server Architectureという考えが非常に参考にな る。
  21. 21. Parse Old Web Apps Mobile-side Programming Server-side + Cloud Code Programming Code Code Cloud Code
  22. 22. サーバーサイドのWebアプリのスタイル の見直しの進行 サーバーサイドの Webアプリ + HTML5の新機能 Android Native クライアントのデバイスの処理能力が 飛躍的に向上して、サーバーの生成 するHTMLをブラウザーでレンダリング する以上の能力を持ち始めた。 サーバーの負担・ネットワー ク帯域の増大等、いろいろな 困難がうまれていて、 それらの見直しが進行。 サーバー側 クライアント側
  23. 23. アプリケーション開発の多様化 J2EE,Spring .ASP,.NET 等のサーバー サイドアプリの クライアント Application Cacheを利用 したアプリ Widget Packaged Web App chrome Tizen Firefox OS JavaScript MVC Frameworks Android Native 現実に進行したのは、 「サーバーサイドWebアプリ +HTML5」の世界の一方的 拡大ではなかった。 サーバーサードのWebアプリと、 クライアント中心のNativeアプリ の中間に、様々のアプリケーショ ンの形態が生まれた。
  24. 24. Webアプリのサーバーとクライアントへの 分岐とPackaged Web Appへの関心 J2EE,Spring .ASP,.NET 等のサーバー サイドアプリの クライアント Application Cacheを利用 したアプリ Widget JavaScript MVC Frameworks Android Native 従来型のWebアプリ (サーバーサイド) 新しいタイプのWebアプリ (クライアントサイド) 外部にWebサーバー を必要としない 外部にWebサーバー を必要とする Packaged Web App Chrome Tizen Firefox OS
  25. 25. サーバーとクライアントの 役割の見直しの一般的な背景 1. ハードの性能が、特にクライアント側で飛躍的に向上した こと。クラウド・デバイスは、PCより、はるかにリッチなクラ イアントである。 2. サーバーの負荷の増大 3. ネットワーク・トラフィックの増大 4. プログラムとViewの分離の難しさ  全てがサーバー側でコントロールされ、特に、両者が 密に絡み合っているようなWebアプリでは、どちらの 変更も、開発の工程に大きなインパクトを与える可能 性がある。  プログラマはデザインの変更を嫌い、デザイナーは、 プログラムの変更を嫌う。
  26. 26. Thin Server Architectureについて  Thin Server Architecture というのは、2008 年の初め頃に、Mario Valenteを中心とするグ ループが考えだしたコンセプト。  残念ながら、発表当時は、大きな反響を起こすこ ともなく、忘れられていた。今また、こうした数年前 のコンセプトに照明があたると言うのも、興味深 いことである。 https://sites.google.com/a/thinserverarchit ecture.com/home/Home
  27. 27. Thin Server Architectureの定義  Thin Server Architecture(TSA)は、今日の Webアプリケーション・フレームワークの、慢心と 複雑さに対する、反応である。  TSAは、全てのプレゼンテーション層のロジックを 、サーバーから、クライアント(Webブラウザー) 上の、JavaScript(あるいは他)のロジックに移し 変えることを提案する。  これによって、次の三つのポジティブなことが得ら れる。
  28. 28. Thin Server Architectureの定義 1. サーバー側の開発者は、ビジネス・ロジックに集 中出来る。 2. クライアントが分離して開発されるので、アプリケ ーションは、より複雑でなくなる。 3. サーバーとクライアントの通信は、データを他方 の、あるいは将来のシステムとの間のデータのイ ンポート、エクスポート、あるいは表現に利用可 能な、プロトコルを利用する。
  29. 29. Thin Server Architectureの図式 クライアント サーバー
  30. 30. 資料:「Package Web Appについて --- AndroidとChromeの統合」  2013年 6月24日 マルレク第一回  概要: http://bit.ly/1tNxHWH  資料: http://bit.ly/1wdbBfX  「アプリ開発の新しい動向」 http://bit.ly/1E2hOxD
  31. 31. 同期型から 非同期型へ 関数の呼び出し、プロセス間・ノード間の通信のスタイ ルも大きく変わろうとしている。相手の応答があるまで ブロックして待機する同期型の処理の見直しと、非同 期型の処理への移行が進んでいる。Parseでは、メ ソッドの呼び出しは、デフォールトで非同期型である。
  32. 32. Parse Old Web Apps Async Sync
  33. 33. 1ナノ秒を1秒だとした場合の実行時間 val socket = Socket() val packet = socket.readFromMemory() // 3日間ブロックする // 例外が発生しない時には、次の処理へ val confirmation = socket.sendToEurope(packet) // 5年間ブロックする // 例外が発生しない時には、次の処理へ “Principles of Reactive Programming” Coursera lecture 2014 by Erik Meijer https://class.coursera.org/reactive-001/lecture/51
  34. 34. JSConf 2009 http://goo.gl/I4HLUc
  35. 35. John Resig
  36. 36. Reactive Extension (Rx) by Microsoft http://msdn.microsoft.com/en- us/data/gg577609.aspx
  37. 37. interface IObservable<out T> { IDisposable Subscribe(IObserver<T> observer); } interface IObserver<in T> { void OnNext(T value); void OnError(Exception ex); void OnCompleted(); } Rxの最も重要なインターフェース Observable Observerが 実装する3つの タイプのメソッド
  38. 38. Rx 資料  2010年 11月 DevCamp Keynote “Rx: Curing your asynchronous programming blues” http://channel9.msdn.com/Blogs/codefest/DC2010T010 0-Keynote-Rx-curing-your-asynchronous-programming- blues  2012年 6月 TechEd Europe “Rx: Curing your asynchronous programming blues” http://channel9.msdn.com/Events/TechEd/Europe/2012/ DEV413
  39. 39. JavaOne 2012 http://goo.gl/rLr91w
  40. 40. JavaOne 2012 http://goo.gl/rLr91w
  41. 41. Reactive プログラミング 資料  2014年 1月21日 マルレク第八回  概要: http://bit.ly/1tDnESC  資料: http://bit.ly/1kkJelH
  42. 42. PULLモデルから PUSHのサポートへ 現在主流のWebアプリは、Webページの閲覧と一緒 で、ユーザーがアクセスしないと起動しないPULL型 のモデル。クラウドからモバイルへの、一斉通知の PUSHのサポートに対するニーズが高まっている。
  43. 43. Parse Old Web Apps Push Pull
  44. 44. https://goo.gl/4V7vRL Google GCM
  45. 45. https://goo.gl/lhJ8bl Apple APNS
  46. 46. Facebook Push Notification https://goo.gl/CuLNqf
  47. 47. FacebookとWebテクノロジー FacebookのWebテクノロジーに対するスタンスは、 興味深いものがある。Nativeによるユーザー・エクス ペリエンスの向上を追求しながら、ポストHTML5の Webコンポーネント技術を、最大限、取り入れている ように見える。Polymerと Reactを比較されたい。
  48. 48. FacebookのHTML5からの離脱 TechCrunch誌とのインタビュー 2012年9月11日 I think the biggest mistake that we made, as a company, is betting too much on HTML5 as opposed to native… because it just wasn’t there. 会社として、我々が犯した最大の誤りは、ネーティブに対して、 HTML5に、あまりに賭けすぎたことだと思う。
  49. 49. Facebook does not hate the Web. But we're realists. We just can't use the Web right now to build the types of user experiences that we want Tom Occhino f8 2015 React Native
  50. 50. Facebookの新しいWebテクノロジー  Flux https://github.com/facebook/flux  React https://github.com/facebook/react  Parse + React http://blog.parse.com/learn/parse-and-react-shared- chemistry/  React Native https://goo.gl/O9MjKI  GraphQL + Relay https://goo.gl/TXKRXQ
  51. 51. React Native  F8 2015  React Native & Relay: Bringing Modern Web Techniques to Mobile https://goo.gl/FDVQdK  By Tom Occhino
  52. 52. Parseの世界 ここでは、新しいモバイル・アプリ開発の代表的なプラ ットフォームとしてFacebook Parseを取り上げる。 Parse + React, React Nativeについては、Part III で紹介する。 Part II
  53. 53. Part II Parseの世界  Parse Object  Relational Data の表現  Queries / Query Samples  Promises  PUSH  Cloud Code / Cloud Code Trigger
  54. 54. Parse Object Parseの最大の特徴は、Parse Objectにある。 それは、モバイルとクラウドの間を自由に行き来する Remote Objectである。と同時に、それは、データ の担い手として Data Objectである。
  55. 55. Parse Old Web Apps Data Object MVC HTML/HTTP get/Fetch Save
  56. 56. Parse.Object クラスとインスタンスの生成 Parse JavaScript // Parse.objectのサブクラスを生成する簡単なシンタックス var GameScore = Parse.Object.extend("GameScore"); // このクラスの新しいインスタンスを生成する。 var gameScore = new GameScore(); // 先の方法の代わりに、典型的なBackboneのシンタックスを利用することもできる var Achievement = Parse.Object.extend({ className: "Achievement" });
  57. 57. A complex subclass of Parse.Object // Parse.objectのサブクラスを生成する少し複雑ななシンタックス var Monster = Parse.Object.extend(“Monster”, // 第一引数 クラス名 { // 第二引数 インスタンスのメソッド達 hasSuperHumanStrength: function () { return this.get("strength") > 18; }, // インスタンスのプロパティの初期値は、initialize で定義する initialize: function (attrs, options) { this.sound = "Rawr" } }, { // 第三引数 クラスのメソッド達 spawn: function(strength) { var monster = new Monster(); monster.set("strength", strength); return monster; } }); var monster = Monster.spawn(200); alert(monster.get(‘strength’)); // spawnでsetされた 200を表示 alert(monster.sound); // インスタンスの soundの初期値 Rawr を表示.
  58. 58. Data Objectとしての Parse.Object var number = 42; var string = "the number is " + number; var date = new Date(); var array = [string, number]; var object = { number: number, string: string }; var BigObject = Parse.Object.extend("BigObject"); var bigObject = new BigObject(); bigObject.set("myNumber", number); bigObject.set("myString", string); bigObject.set("myDate", date); bigObject.set("myArray", array); bigObject.set("myObject", object); bigObject.set("myNull", null); bigObject.save();
  59. 59. Parse.Object クラスとインスタンスの生成 Parse Android Java ParseObject gameScore = new ParseObject("GameScore"); Parse iOS PFObject *gameScore = [PFObject objectWithClassName:@"GameScore"]; Parse .NET ParseObject gameScore = new ParseObject("GameScore"); Parse iOS Swift var gameScore = PFObject(className:"GameScore")
  60. 60. Parse.Object オブジェクトの保存 save var GameScore = Parse.Object.extend("GameScore"); var gameScore = new GameScore(); gameScore.set("score", 1337); gameScore.set("playerName", "Sean Plott"); gameScore.set("cheatMode", false); gameScore.save( null, { success: function(gameScore) { // オブジェクトの保存に成功した後で、実行されるべき処理 alert('New object created with objectId: ' + gameScore.id); }, error: function(gameScore, error) { // オブジェクトの保存に失敗した後で、実行されるべき処理 // error は、 Parse.Errorで、 error code と messageを持っている alert('Failed to create new object, with error code: ' + error.message); } });
  61. 61. フィールドを、 saveの第一引数で、直接に設定 var GameScore = Parse.Object.extend("GameScore"); var gameScore = new GameScore(); gameScore. save({ score: 1337, playerName: "Sean Plott", cheatMode: false }, { success: function(gameScore) { // The object was saved successfully. }, error: function(gameScore, error) { // The save failed. // error is a Parse.Error with an error code and message. } });
  62. 62. Saveされるデータ objectId: “xWMyZ4YEGZ”, score: 1337, playerName: “Sean Plott”, cheatMode: false, createdAt: “2011-06-10T18:33:42Z”, updatedAt: "2011-06-10T18:33:42Z" SaveされたオブジェクトのユニークID Saveされた時点で追加・変更されるフィールド
  63. 63. オブジェクトの保存(非同期で行われる) Parse Android Java gameScore.put("score", 1337); gameScore.put("playerName", "Sean Plott"); gameScore.put("cheatMode", false); gameScore.saveInBackground(); Parse .NET gameScore["score"] = 1337; gameScore["playerName"] = "Sean Plott"; gameScore[“cheatMode”] = false; await gameScore.SaveAsync(); Parse iOS Swift gameScore["score"] = 1337 gameScore["playerName"] = "Sean Plott” gameScore["cheatMode"] = false gameScore.saveInBackgroundWithBlock { … }
  64. 64. Parse.Object オブジェクトの取得 get var GameScore = Parse.Object.extend("GameScore"); var query = new Parse.Query(GameScore); query.get( "xWMyZ4YEGZ", { success: function(gameScore) { // オブジェクトの取得に成功 }, error: function(object, error) { // オブジェクトの取得に失敗 // error は Parse.Errorで、error codeとmessageを持つ. } });
  65. 65. オブジェクトの取得 (Android) ParseQuery<ParseObject> query = ParseQuery.getQuery( "GameScore"); query.getInBackground( "xWMyZ4YEGZ", new GetCallback<ParseObject>() { public void done(ParseObject object, ParseException e) { if (e == null) { // objectは、ゲームのスコア } else { // なんか、うまくいかない } } });
  66. 66. オブジェクトの取得 Parse .NET ParseQuery<ParseObject> query = ParseObject.GetQuery("GameScore"); ParseObject gameScore = await query.GetAsync("xWMyZ4YEGZ"); Parse iOS Swift var query = PFQuery(className:"GameScore") query.getObjectInBackgroundWithId("xWMyZEGZ") { (gameScore: PFObject?, error: NSError?) -> Void in if error == nil && gameScore != nil { println(gameScore) } else { println(error) } }
  67. 67. データベースとの同期 Fetch myObject.fetch({ success: function(myObject) { // オブジェクトの更新に成功 }, error: function(myObject, error) { // オブジェクトの更新に失敗 // error は Parse.Errorで、error codeとmessageを持つ. } }); myObject.fetchInBackground( new GetCallback<ParseObject>() { public void done(ParseObject object, ParseException e) { if (e == null) { // Success! } else { // Failure! } } }); await myObject.FetchAsync();
  68. 68. オブジェクトの更新 // オブジェクトの生成 var GameScore = Parse.Object.extend("GameScore"); var gameScore = new GameScore(); gameScore.set("score", 1337); gameScore.set("playerName", "Sean Plott"); gameScore.set("cheatMode", false); gameScore.set("skills", ["pwnage", "flying"]); gameScore.save(null, { success: function(gameScore) { // 保存に成功したら、新しいデータで更新しよう // この場合、cheatModeとscoreがクラウドに送られて // playerNameは、変わらない。 gameScore.set("cheatMode", true); gameScore.set("score", 1338); gameScore.save(); } });
  69. 69. オブジェクトの削除 destroy myObject. destroy({ success: function(myObject) { // objectは、Parse Cloudから削除された }, error: function(myObject, error) { // 削除失敗。 // error は Parse.Errorで、error codeとmessageを持つ. } }); // unsetメソッドで、オブジェクトの一つのフィールドの削除ができる // このあと、 playerName フィールドは、空になる myObject.unset("playerName"); // 削除されたフィールドをParse Cloudに保存する myObject.save();
  70. 70. Relational Data の表現 Parse Objectでは、Relationalなデータの表現が 可能である。あとで見る Parse Queryを使って、デ ータの検索が可能となる。
  71. 71. 一対一、一対多の関係の表現 // 型を宣言する var Post = Parse.Object.extend("Post"); var Comment = Parse.Object.extend("Comment"); // postを生成する var myPost = new Post(); myPost.set("title", "I'm Hungry"); myPost.set("content", "Where should we go for lunch?"); // commentを生成する var myComment = new Comment(); myComment.set("content", "Let's do Sushirrito."); // postをcommentの parentの値として設定する myComment.set("parent", myPost); // myPost とmyCommentの両方が保存される myComment.save(); MyPost MyComment parent MyPost MyComment parent
  72. 72. 内部的には、Parseのフレームワークは、整合性を維持するため に、参照されたオブジェクトを一箇所のみに格納する。オブジェク トIDを使った場合のみ、次のように、オブジェクトのリンクは可能 である。 var post = new Post(); post.id = "1zEcyElZ80"; myComment.set("parent", post); myComment.put(“parent”, ParseObject.createWithoutData("Post", "1zEcyElZ80")); myComment[“parent”] = ParseObject.CreateWithoutData("Post", "1zEcyElZ80"); myComment[“parent”] = PFObject(withoutDataWithClassName:“Post”,objectId:"1zEcyElZ80")
  73. 73. var post = fetchedComment. get("parent"); post.fetch({ success: function(post) { var title = post.get("title"); } }); 関連付けられたオブジェクトの取得  オブジェクトを取り出しても、デフォールトでは、 Parse.Objectに関連付けられたオブジェクトは取り出せ ない。これらのオブジェクトの値は、次のようにしないと取 り出せない。 post fetchedComment post fetchedComment get(“parent”)
  74. 74. var post = myComment[“parent”] as PFObject post.fetchIfNeededInBackgroundWithBlock { (post: PFObject?, error: NSError?) -> Void in let title = post?[“title”] as? NSString // do something with your title variable } ParseObject post = fetchedComment.Get<ParseObject>(“parent”); await post.FetchIfNeededAsync(); fetchedComment.getParseObject(“post”) .fetchIfNeededInBackground( new GetCallback<ParseObject>(){ public void done(ParseObject post, ParseException e) { String title = post.getString(“title”); // Do something with your new title variable } });
  75. 75. 多対多の関係の表現 relation  Many-to-manyの関係は、Parse.Relation を使って モデル化される。このモデルは、一つのキーに、 Parse.Objectの配列を格納するのに似ている。ただし、全 ての関係のオブジェクトを一回で取り出す必要がないことを 除いては。  加えて、このことで Parse.Relation は、 Parse.Object の配列を利用するアプローチと比較して、より多くのオブジ ェクトにスケールすることが可能になる。 var user = Parse.User.current(); var relation = user.relation("likes"); relation.add(post); user.save(); 1 2 User Post likes 1 3 2
  76. 76. ParseUser user = ParseUser.getCurrentUser(); ParseRelation<ParseObject> relation = user.getRelation("likes"); relation.add(post); user.saveInBackground(); var user = ParseUser.CurrentUser; var relation = user.GetRelation<ParseObject>(“likes”); relation.Add(post); await user.SaveAsync(); var user = PFUser.currentUser() var relation = user.relationForKey(“likes”) relation.addObject(post) user.saveInBackground() var user = Parse.User.current(); var relation = user.relation("likes"); relation.add(post); user.save();
  77. 77. 関係リストの取得  デフォールトでは、次のような関係のオブジェクトのリスト はダウンロードできない。  この場合には、queryで返される Parse.Query を利用 すれば、ユーザーが「いいね」をつけたポストのリストを、 取得できる。次のようなコードになる。 relation.add( [post1, post2, post3] ); user.save(); relation.query().find({ success: function(list) { // リストは、ユーザーがいいねをしたpostを含んでいる });
  78. 78. relation.getQuery().findInBackground( new FindCallback<ParseObject>() { void done(List<ParseObject> results, ParseException e) { if (e != null) { // There was an error } else { // results have all the Posts the current user liked. } }}); IEnumerable<ParseObject> relatedObjects = await relation.Query.FindAsync(); relation.query().findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in if let error = error { // There was an error } else { // objects has all the Posts the current user liked. }}
  79. 79. 関係リストの項目の削除 remove // postをParse.Relationから削除できる relation.remove(post); user.save(); // saveを呼ぶ前に、複数回removeを呼べる relation.remove(post1); relation.remove(post2); user.save();
  80. 80. Queryの制限条件  ポストのサブセットが欲しいならば、次のように制限条件を Parse.Queryに課す。 var query = relation.query(); query.equalTo("title", "I'm Hungry"); query.find({ success:function(list) { // list は、現在のユーザーによって「いいね」されたポストで、タイトルが // "I'm Hungry” であるものを含んでいる。 } });
  81. 81. Queries Parseの強力な機能の一つは、クラウド上のデータベ ースに対する検索機能が備わっていることである。 Parse.Queryが、それを可能にする。
  82. 82. 基本的な Query var GameScore = Parse.Object.extend("GameScore"); var query = new Parse.Query(GameScore); query.equalTo("playerName", "Dan Stemkoski"); query.find({ success: function(results) { alert("Successfully retrieved " + results.length + " scores."); // 返された Parse.Objectの値を処理 for (var i = 0; i < results.length; i++) { var object = results[i]; alert(object.id + ' - ' + object.get('playerName')); } }, error: function(error) { alert("Error: " + error.code + " " + error.message); } });
  83. 83. 基本的な Query ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore"); query.whereEqualTo("playerName", "Dan Stemkoski"); query.findInBackground( new FindCallback<ParseObject>() { public void done( List<ParseObject> scoreList, ParseException e) { if (e == null) { Log.d("score", "Retrieved " + scoreList.size() + " scores"); } else { Log.d("score", "Error: " + e.getMessage()); } } });
  84. 84. 基本的な Query var query = from gameScore in ParseObject.GetQuery("GameScore") where gameScore.Get<string>("playerName") == "Dan Stemkoski" select gameScore; IEnumerable<ParseObject> results = await query.FindAsync(); PFQuery *query = [PFQuery queryWithClassName:@"GameScore"]; [query whereKey:@"playerName" equalTo:@"Dan Stemkoski"]; NSArray* scoreArray = [query findObjects]; .NET版 Parseでは、LINQが使える。 await ... async 構文と合わせて、なかなか、格好がいい。
  85. 85. フィールドの選択 select var GameScore = Parse.Object.extend("GameScore"); var query = new Parse.Query(GameScore); query.select("score", "playerName"); query.find().then(function(results) { // resultsは、 “score”, “playerName” のみのフィールドを含む });
  86. 86. Query の制約条件  複数の制約条件をqueryに与えることができる。全ての条件に マッチしたオブジェクトのみが結果に含まれる。別の言い方を すれば、それは、制約条件のANDに似ている。 query.notEqualTo("playerName", "Michael Yabuti"); query.greaterThan("playerAge", 18);  limitを設定することで、結果の数を制限できる。デフォールト では、結果は100個に制限されている。1から1000までの任意 の設定ができる。 query.limit(10); // 最大 10個の値に制限する
  87. 87. Query の制約条件  一つの結果だけが必要なら first を利用できる。 var GameScore = Parse.Object.extend("GameScore"); var query = new Parse.Query(GameScore); query.equalTo("playerEmail", "dstemkoski@example.com"); query.first({ success: function(object) { // オブジェクトの取得に成功 }, error: function(error) { alert("Error: " + error.code + " " + error.message); } });  skipを設定して、最初の結果をスキップできる。 query.skip(10); // 最初の10この結果をスキップ
  88. 88. ソートと比較演算 // score フィールドを昇順に整列する query.ascending("score"); // scoreフィールドを降順に整列する query.descending("score"); // ソート可能な型に対して、比較演算が可能である // wins < 50 query.lessThan("wins", 50); // wins <= 50 query.lessThanOrEqualTo("wins", 50); // wins > 50 query.greaterThan("wins", 50); // wins >= 50 query.greaterThanOrEqualTo("wins", 50);
  89. 89. メンバー、存在、非存在のチェック // Jonathan, Dario, Shawnのスコアを検索する query.containedIn("playerName", ["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]); // Jonathan, Dario, Shawn以外のスコアを検索する query.notContainedIn("playerName", ["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]); // スコアが設定されているオブジェクトを検索 query.exists("score"); // スコアが設定されていないオブジェクトを検索 query.doesNotExist("score");
  90. 90. // wins < 50という条件で絞る query = from gameScore in query where gameScore.Get<int>("wins") < 50 select gameScore; // wins <= 50という条件で絞る query = from gameScore in query where gameScore.Get<int>("wins") <= 50 select gameScore; // wins > 50という条件で絞る query = from gameScore in query where gameScore.Get<int>("wins") > 50 select gameScore; // wins >= 50という条件で絞る query = from gameScore in query where gameScore.Get<int>("wins") >= 50 select gameScore; .NET ParseでのLINQの利用
  91. 91. var Team = Parse.Object.extend("Team"); var teamQuery = new Parse.Query(Team); teamQuery.greaterThan("winPct", 0.5); var userQuery = new Parse.Query(Parse.User); userQuery.matchesKeyInQuery("hometown", "city", teamQuery); userQuery.find({ success: function(results) { // ホームタウンで、勝ち越しているチームの検索結果 } }); ParseQuery<ParseObject> teamQuery = ParseQuery.getQuery(“Team”); teamQuery.whereGreaterThan(“winPct”, 0.5); ParseQuery<ParseUser> userQuery = ParseUser.getQuery(); userQuery.whereMatchesKeyInQuery(“hometown”, “city”, teamQuery); userQuery.findInBackground( new FindCallback<ParseUser>() { void done(List<ParseUser> results, ParseException e) { // results has the list of users with a hometown team with // a winning record } }); ホームタウンで、勝ち越しているユーザーの検索
  92. 92. var teamQuery = from team in ParseObject.GetQuery("Team") where team.Get<double>("winPct") > 0.5 select team; var userQuery = from user in ParseUser.Query join team in teamQuery on user["hometown"] equals team["city"] select user; IEnumerable<ParseUser> results = await userQuery.FindAsync(); // results will contain users with a hometown team with a winning record var teamQuery = PFQuery(className:"Team") teamQuery.whereKey("winPct", greaterThan:0.5) var userQuery = PFUser.query() userQuery!.whereKey("hometown", matchesKey:"city", inQuery:teamQuery) userQuery!.findObjectsInBackgroundWithBlock { (results: [AnyObject]?, error: NSError?) -> Void in if error == nil { // results will contain users with a hometown team with a winning record } }
  93. 93. var losingUserQuery = new Parse.Query(Parse.User); losingUserQuery.doesNotMatchKeyInQuery( "hometown", "city", teamQuery); losingUserQuery.find({ success: function(results) { // results has the list of users with a hometown team with // a losing record } }); ParseQuery<ParseUser> losingUserQuery = ParseUser.getQuery(); losingUserQuery.whereDoesNotMatchKeyInQuery( "hometown", "city", teamQuery); losingUserQuery.findInBackground( new FindCallback<ParseUser>() { void done(List<ParseUser> results, ParseException e) { // results has the list of users with a hometown team with // a losing record } }); ホームタウンで、負け越しているユーザーの検索
  94. 94. Parse Query Sample 以下、いくつかのサンプルを紹介する。Parse Query が、SQLやLINQと、ほぼ同等の機能を持つことを確 認されたい。
  95. 95. Relationalな検索 // Parse.Object myPost は、すでに作られているとしよう var query = new Parse.Query(Comment); query.equalTo("post", myPost); query.find({ success: function(comments) { // comments は、myPostに対するコメントである } }); ParseQuery<ParseObject> query = ParseQuery.getQuery("Comment"); query.whereEqualTo("post", myPost); query.findInBackground( new FindCallback<ParseObject>() { public void done(List<ParseObject> commentList, ParseException e) { // commentList now has the comments for myPost } }); MyPost MyComment “post”
  96. 96. // Assume ParseObject myPost was previously created. var query = from comment in ParseObject.GetQuery("Comment") where comment["post"] == myPost select comment; var comments = await query.FindAsync(); // comments now contains the comments for myPost PFQuery *query = [PFQuery queryWithClassName:@"Comment"]; [query whereKey:@"post" equalTo:myPost]; [query findObjectsInBackgroundWithBlock: ^(NSArray *comments, NSError *error) { // comments now contains the comments for myPost }];
  97. 97. 画像付きのコメントの検索 var Post = Parse.Object.extend("Post"); var Comment = Parse.Object.extend("Comment"); var innerQuery = new Parse.Query(Post); innerQuery.exists("image"); var query = new Parse.Query(Comment); query.matchesQuery("post", innerQuery); query.find({ success: function(comments) { // postに対するコメントで、画像付きのコメントの検索結果 } }); ParseQuery<ParseObject> innerQuery = ParseQuery.getQuery("Post"); innerQuery.whereExists("image"); ParseQuery<ParseObject> query = ParseQuery.getQuery("Comment"); query.whereMatchesQuery("post", innerQuery); query.findInBackground(new FindCallback<ParseObject>() { public void done(List<ParseObject> commentList, ParseException e) { // comments now contains the comments for posts with images. }});
  98. 98. var imagePosts = from post in ParseObject.GetQuery("Post") where post.ContainsKey("image") select post; var query = from comment in ParseObject.GetQuery("Comment") join post in imagePosts on comment["post"] equals post select comment; var comments = await query.FindAsync(); // comments now contains the comments for posts with images PFQuery *innerQuery = [PFQuery queryWithClassName:@"Post"]; [innerQuery whereKeyExists:@"image"]; PFQuery *query = [PFQuery queryWithClassName:@"Comment"]; [query whereKey:@"post" matchesQuery:innerQuery]; [query findObjectsInBackgroundWithBlock: ^(NSArray *comments, NSError *error) { // comments now contains the comments for posts with images }];
  99. 99. 画像なしのコメントの検索 var Post = Parse.Object.extend("Post"); var Comment = Parse.Object.extend("Comment"); var innerQuery = new Parse.Query(Post); innerQuery.exists("image"); var query = new Parse.Query(Comment); query.doesNotMatchQuery("post", innerQuery); query.find({ success: function(comments) { // postに対するコメントで、画像なしのコメントの検索結果 } }); var post = new Post(); post.id = "1zEcyElZ80"; query.equalTo("post", post);
  100. 100. 最新のコメント10個の検索 var query = new Parse.Query(Comment); // Retrieve the most recent ones query.descending("createdAt"); // Only retrieve the last ten query.limit(10); // Include the post data with each comment query.include("post"); query.find({ success: function(comments) { // Comments now contains the last ten comments, and the "post" field // has been populated. For example: for (var i = 0; i < comments.length; i++) { // This does not require a network access. var post = comments[i].get("post"); } } });
  101. 101. オブジェクトを数える count var GameScore = Parse.Object.extend("GameScore"); var query = new Parse.Query(GameScore); query.equalTo("playerName", "Sean Plott"); query.count({ success: function(count) { // The count request succeeded. Show the count alert("Sean has played " + count + " games"); }, error: function(error) { // The request failed } });
  102. 102. Queryの組み合わせ or var lotsOfWins = new Parse.Query("Player"); lotsOfWins.greaterThan("wins", 150); var fewWins = new Parse.Query("Player"); fewWins.lessThan("wins", 5); var mainQuery = Parse.Query.or(lotsOfWins, fewWins); mainQuery.find({ success: function(results) { // たくさん勝っているひと、「あるいは」、ほとんど勝ってない人 }, error: function(error) { // There was an error. } });
  103. 103. Queryの組み合わせ ParseQuery<ParseObject> lotsOfWins = ParseQuery.getQuery("Player"); lotsOfWins.whereGreaterThan(150); ParseQuery<ParseObject> fewWins = ParseQuery.getQuery("Player"); fewWins.whereLessThan(5); List<ParseQuery<ParseObject>> queries = new ArrayList<ParseQuery<ParseObject>>(); queries.add(lotsOfWins); queries.add(fewWins); ParseQuery<ParseObject> mainQuery = ParseQuery.or(queries); mainQuery.findInBackground( new FindCallback<ParseObject>() { public void done(List<ParseObject> results, ParseException e) { // results has the list of players that win a lot or haven't won much. } });
  104. 104. Compound Queries var lotsOfWins = from player in ParseObject.GetQuery("Player") where player.Get<int>("wins") > 150 select player; var fewWins = from player in ParseObject.GetQuery("Player") where player.Get<int>("wins") < 5 select player; ParseQuery<ParseObject> query = lotsOfWins.Or(fewWins); var results = await query.FindAsync(); // results contains players with lots of wins or only a few wins.
  105. 105. Compound Queries var lotsOfWins = PFQuery(className:"Player") lotsOfWins.whereKey("wins", greaterThan:150) var fewWins = PFQuery(className:"Player") fewWins.whereKey("wins", lessThan:5) var query = PFQuery.orQueryWithSubqueries([lotsOfWins, fewWins]) query.findObjectsInBackgroundWithBlock { (results: [AnyObject]?, error: NSError?) -> Void in if error == nil { // results contains players with lots of wins or only a few wins. } }
  106. 106. Promises Promiseの利用は、Parse JavaScript SDKの大き な特徴の一つである。Parseの全ての非同期メソッド は、Promiseを返す。Promiseを利用することで、 callbackのネストのない、クリーンなコードを書くこと ができる。
  107. 107. Promiseを生成する var successful = new Parse.Promise(); successful.resolve(“The good result.”); // 結果は成功である var failed = new Parse.Promise(); failed.reject(“An error message.”); // 結果は失敗である // Promiseの生成時に、その結果が分かっているのなら // as (resolved) あるいは error (rejected)メソッドを利用できる。 var successful = Parse.Promise.as("The good result."); var failed = Parse.Promise.error("An error message.");
  108. 108. 非同期のメソッドを作る var delay = function(millis) { var promise = new Parse.Promise(); setTimeout(function() { promise.resolve(); }, millis); return promise; }; delay(100).then(function() { // 100m秒後に実行される });
  109. 109. then メソッド  全てのPromiseは、then という名前のメソッドを持つ。  then は、2つのcallback を持つ。 最初の callback は、Promise が成功した時に呼ばれ、 二つ目の callback は、Promise が失敗した時に呼ば れる。 obj.save().then(function(obj) { // the object was saved successfully. }, function(error) { // the save failed. });
  110. 110. 複数のPromiseを 一つのチェインにつなげる var query = new Parse.Query("Student"); query.descending("gpa"); query.find().then(function(students) { students[0].set("valedictorian", true); return students[0].save(); }).then(function(valedictorian) { return query.find(); }).then(function(students) { students[1].set("salutatorian", true); return students[1].save(); }).then(function(salutatorian) { // Everything is done! });
  111. 111. エラー処理  チェインの中のPromiseのどれか一つでもエラーを返せ ば、それ以降の全ての success コールバックは、error コールバックに会うまでスキップされる。  errorコールバックは、そのエラーを変形して、リジェクトさ れない新しいPromiseを返して、それをハンドルできる。  リジェクトされたPromiseは、例外を投げるのと同じように 考えることができる。errorコールバックは、エラーをハン ドルし、あるいは、再度エラーを返す catch ブロックのよ うなものである。
  112. 112. Error Handling var query = new Parse.Query("Student"); query.descending("gpa"); query.find().then(function(students) { students[0].set("valedictorian", true); // 強制的にこのコールバックを失敗させる return Parse.Promise.error("There was an error."); }).then(function(valedictorian) { // この処理はスキップされる return query.find(); }).then(function(students) { // この処理もスキップされる students[1].set("salutatorian", true); return students[1].save(); }, function(error) { // このエラーハンドラーが呼ばれることになる。 // この中で、新しいPromiseを返すことができる return Parse.Promise.as("Hello!"); }).then(function(hello) { // うまくいく }, function(error) { // エラーはすでに処理されているので、ここは呼ばれない。 });
  113. 113. シリアルなpromiseの実行 var query = new Parse.Query("Comments"); query.equalTo(“post”, 123); // post 123のcommentを検索する query.find().then(function(results) { // promiseを一つ用意する var promise = Parse.Promise.as(); _.each(results, function(result) { // それぞれのcommentに、それを削除する関数を適用する promise = promise.then(function() { // 削除が終了した時にpromiseを返す。このpromiseは、次の繰り返しで // thenに渡される。削除は、一個づつ進む。 return result.destroy(); }); }); return promise; }).then(function() { // 全てのcommentが削除された. });
  114. 114. パラレルなpromiseの実行  whenメソッドを使って、promiseを複数のタスクをパラレ ルに実行するのにも利用できる。  複数の操作を一度に開始することができる。 Parse.Promise.whenを使えば、その入力の全ての promiseが解決された時に、promiseを返すような新し いpromiseを生成できる。  この新しいpromiseは、渡されたpromiseのいずれもが 失敗しなかった時に成功する。そうでなければ、最後のエ ラーで失敗する。  パラレルに操作を実行するのは、シリアルな実行より高速 であろう。しかし、それは、多くのシステムのリソースと帯 域を消費するかもしれない。
  115. 115. パラレルなpromiseの実行 var query = new Parse.Query("Comments"); query.equalTo("post", 123); query.find().then(function(results) { // promiseの配列を用意して、一つの削除に一つのpromiseを割り当てる。 var promises = []; _.each(results, function(result) { // 削除を直ちに開始して、promiseを配列に追加する promises.push(result.destroy()); }); // whenで並列実行。全ての削除が終わったら、新しいpromiseを返す return Parse.Promise.when(promises); }).then(function() { // 全てのcommentが削除された. });
  116. 116. PUSH Android, iOSといった異なるプラットフォームに対し て、細かなターゲットの設定も行いながら、通知を一 斉送信できる ParseのPUSH機能は、とても強力で ある。
  117. 117. PUSHを送る二つの方法  push通知を送るには、二つの方法がある。 一つは、Parseの channnelを使うやり方で、 もう一つは、advanced targeting を使うやり方であ る。  channelは、pushを送るために単純で容易なモデルを提 供し、advanced targetingは、より強力で柔軟なモデル を提供する。双方ともに、お互いに完全な互換性がある。
  118. 118. PUSHでのクラウド利用 Cloud Code  通知の送出には、Parse.comのpushコンソールや REST API、あるいは、Cloud Codeからの送出がよく 利用される。  Cloud Codeでは、JavaScript SDKが使われているの で、Cloud Functionからpushを送りたいと思うのなら、 まず、ここからスタートすることになる。  しかし、 Cloud Codeの外側のJavaScript SDKや他の クライアントSDKから通知を送ることに決めるなら、Parse アプリのpush通知の中で、クライアントのpushを可能に する必要がある。
  119. 119. クライアントからの送出は、 セキュリティ上の脆弱性を持つ  しかし、クライアントからのpushは、アプリにセキュリティ 上の脆弱性をもたらしうることを、きちんと理解しておくこと 。推奨できるのは、クライアントからのpushをテスト目的 にのみ利用することである。そして、アプリが製品段階に 入る準備ができた時には、push通知のロジックをCloud Codeに移すことである。
  120. 120. PUSHの解析情報  ユーザーは、pushを作成してから30日間まで、過去の pushを見ることができる。将来に予定されているpushは 、送出が行われていない限りは、pushコンソールから削 除できる。pushの送出の後には、pushコンソールには、 pushの解析情報が表示される。
  121. 121. PUSHで利用される属性 Installations  badge  channels  timeZone  deviceType  pushType  installationId  deviceToken  channelUris  appName  appVersion  parseVersion  appIdentifier
  122. 122. Channelを使う  通知を送る最も簡単な方法は、channel を使うことであ る。channelを使えば、pushを送るのに、publisher- subscriber モデルを利用することが可能になる。  デバイスは、一つ以上のchannelにサブスクライブするこ とから始める。その後、通知は、サブスクライブしたデバイ スに送られる。  与えられたInstallationによってサブスクライブされた channelは、Installationオブジェクトのchannel フィー ルドに格納される。
  123. 123. ChannelにPUSHを送る Parse.Push.send({ // “Giants”と "Mets" という二つのchannelにPUSHを送る channels: [ “Giants”, “Mets” ], // Pushで送られるデータ data: { alert: "The Giants won against the Mets 2-3." } }, { success: function() { // Push は成功した }, error: function(error) { // エラー } }); Giantsチャンネルと Metsチャンネルに、 試合結果を配信する
  124. 124. Sending Pushes to Channels LinkedList<String> channels = new LinkedList<String>(); channels.add("Giants"); channels.add("Mets"); ParsePush push = new ParsePush(); push.setChannels(channels); // Notice we use setChannels not setChannel push.setMessage("The Giants won against the Mets 2-3."); push.sendInBackground(); // Send a notification to all devices subscribed to the "Giants" channel. var push = new ParsePush(); push.Channels = new List<string> {"Giants"}; push.Alert = "The Giants just scored!"; await push.SendAsync(); let channels = [ "Giants", "Mets" ] let push = PFPush() // Be sure to use the plural 'setChannels'. push.setChannels(channels) push.setMessage("The Giants won against the Mets 2-3.") push.sendPushInBackground()
  125. 125. Advanced Targetingを使う  channelは、多くのアプリでとても役に立つのだが、push の受け取り手をターゲットする際に、もっと細かく指定する ことが必要となることがある。  Parseでは、Query APIを使えば、Installationオブジェ クトの任意のサブセットに対するqueryを書くことができる ので、それらにpushを送出できる。  Installationオブジェクトは、他のオブジェクトと同様に、 Parseに格納されているので、どんなデータでも保存でき るし、Installationオブジェクトと他のオブジェクトとの関 係も作り出すこともできる。 こうして、ユーザーベースで、 非常にカスタマイズ化されたダイナミックなセグメントに、 pushを送ることができる。
  126. 126. Queryの結果にPushを送る var query = new Parse.Query(Parse.Installation); query.equalTo('injuryReports', true); // injuryReportsが真のもの Parse.Push.send({ where: query, // 送出先 data: { alert: "Willie Hayes injured by own pop fly." } }, { success: function() { // Push は成功した }, error: function(error) { // Handle error } }); 負傷者情報を配信する
  127. 127. QueryをChannelに使う var query = new Parse.Query(Parse.Installation); query.equalTo(‘channels’, ‘Giants’); // Set our channel query.equalTo('scores', true); Parse.Push.send({ where: query, // channelがGiantsで、scoreが真のものへ data: { alert: "Giants scored against the A's! It's now 2-2." } }, { success: function() { // Push was successful }, error: function(error) { // Handle error } }); Giantsチャンネルに得点を配信する
  128. 128. // Create our Installation query ParseQuery pushQuery = ParseInstallation.getQuery(); // Set the channel pushQuery.whereEqualTo("channels", "Giants"); pushQuery.whereEqualTo("scores", true); // Send push notification to queryParse Push push = new ParsePush(); push.setQuery(pushQuery); push.setMessage("Giants scored against the A's! It's now 2-2."); push.sendInBackground(); var push = new Parse.Push(); push.Query = from installation in ParseInstallation.Query where installation.Get<bool>("scores") == true select installation; push.Channels = new List<string> { "Giants" }; push.Alert = "Giants scored against the A's! It's now 2-2."; await push.SendAsync();
  129. 129. // Create our Installation query let pushQuery = PFInstallation.query() // Set channel pushQuery.whereKey("channels", equalTo: "Giants") pushQuery.whereKey("scores", equalTo: true) // Send push notification to query let push = PFPush() // Set our Installation query push.setQuery(pushQuery) push.setMessage("Giants scored against the A's! It's now 2-2.") push.sendPushInBackground()
  130. 130. // ユーザーがその場所に近いか調べる var userQuery = new Parse.Query(Parse.User); userQuery.withinMiles("location", stadiumLocation, 1.0); // そのユーザーのPushの送り先を調べる var pushQuery = new Parse.Query(Parse.Installation); pushQuery.matchesQuery('user', userQuery); // Send push notification to query Parse.Push.send({ where: pushQuery, data: { alert: "Free hotdogs at the Parse concession stand!" } }, { success: function() { // Push was successful }, error: function(error) { // Handle error } }); スタジアムに近いところにいるユーザーに通知を送る
  131. 131. // Find users near a given location ParseQuery userQuery = ParseUser.getQuery(); userQuery.whereWithinMiles("location", stadiumLocation, 1.0) // Find devices associated with these users ParseQuery pushQuery = ParseInstallation.getQuery(); pushQuery.whereMatchesQuery("user", userQuery); // Send push notification to query ParsePush push = new ParsePush(); push.setQuery(pushQuery); // Set our Installation query push.setMessage("Free hotdogs at the Parse concession stand!"); push.sendInBackground(); // Find users in the Seattle metro area var userQuery = ParseUser.Query.WhereWithinDistance( "location", marinersStadium, ParseGeoDistance.FromMiles(1)); var push= new ParsePush(); push.Query = from installation in ParseInstallation.Query join user in userQuery on installation["user"] equals user select installation; push.Alert = "Mariners lost? Free conciliatory hotdogs at the Parse concession stand!"; await push.SendAsync();
  132. 132. // Find users near a given location let userQuery = PFUser.query() userQuery.whereKey("location", nearGeoPoint: stadiumLocation, withinMiles: 1) // Find devices associated with these users let pushQuery = PFInstallation.query() pushQuery.whereKey("user", matchesQuery: userQuery) // Send push notification to querylet push = PFPush() push.setQuery(pushQuery) // Set our Installation query push.setMessage("Free hotdogs at the Parse concession stand!") push.sendPushInBackground()
  133. 133. 送出オプション  Push通知は、単にメッセージを送る以上のことが可能で ある。  iOSでは、pushに音や画像を含めることができるし、任意 のカスタムデータを送ることができる。  Androidでは、通知を受け取った時に起動されるIntent の指定までできる。通知が、時間に敏感な場合には、エク スパイアーする日付の設定も可能である。
  134. 134. 通知をカスタマイズする  単なるメッセージ以上のものを送ろうと思ったら、データ辞 書の他のフィールドを設定することができる。特別な意味 を持つ次のようなフィールドが用意されている。  alert: 通知のメッセージ  badge: (iOS のみ) アプリのアイコンの上右隅に示さ れる値。値を指定することも、一つづつその値を増やすこ ともできる。  sound: (iOS のみ) アプリケーション内のサウンド・ファ イルの名前
  135. 135.  content-available: (iOS のみ) Newsstandアプリ や、iOS7から導入された Remote Notification Background Mode (“Background Push”とよばれて いる)を使っている場合には、バックグラウンドでのダウン ロードを起動するために、この値に1を設定する。  category: (iOS のみ) このpush通知の UIUserNotificationCategoryの識別子  uri: (Android のみ) URIを含む、オプションのフィール ド。通知が開かれた時、このURIに関連したActivityが起 動される。  title: (Android のみ) Androidのシステム・トレーの通 知に表示される値。
  136. 136. Parse.Push.send({ channels: [ "Mets" ], data: { alert: "The Mets scored! The game is now tied 1-1.", badge: "Increment", sound: "cheering.caf", title: "Mets Score!" } }, { success: function() { // Push was successful }, error: function(error) { // Handle error } }); JSONObject data = new JSONObject("{ ¥“alert¥”: ¥“The Mets scored!¥”, ¥“badge¥”: ¥“Increment¥”, ¥"sound¥": ¥"cheering.caf¥"}"); ParsePush push = new ParsePush(); push.setChannel("Mets"); push.setData(data); push.sendPushInBackground(); Metsが得点した時に、 Metsチャンネルに通知 する。音付きで。
  137. 137. let data = [ "alert" : "The Mets scored! The game is now tied 1-1!", "badge" : "Increment", "sounds" : "cheering.caf"] let push = PFPush() push.setChannels(["Mets"]) push.setData(data) push.sendPushInBackground() var push = new ParsePush(); push.Channels = new List<string> {"Mets"}; push.Data = new Dictionary<string, object> { {"title", "Score Alert"} {"alert", "The Mets scored! The game is now tied 1-1!"}, }; await push.SendAsync();
  138. 138. var query = new Parse.Query(Parse.Installation); query.equalTo('channels', 'Indians'); query.equalTo('injuryReports', true); Parse.Push.send({ where: query, data: { action: "com.example.UPDATE_STATUS" alert: "Ricky Vaughn was injured in last night's game!", name: "Vaughn", newsItem: "Man bites dog" } }, { success: function() { // Push was successful }, error: function(error) { // Handle error } }); Indians チャンネルに 負傷者情報を流す。 アクション付きで。
  139. 139. JSONObject data = new JSONObject("{ ¥“name¥”: ¥“Vaughn¥”, ¥"newsItem¥": ¥"Man bites dog¥"}” )); ParsePush push = new ParsePush(); push.setQuery(injuryReportsQuery); push.setChannel("Indians"); push.setData(data); push.sendPushInBackground(); let data = [ "alert" : "Ricky Vaughn was injured in last night's game!", "name" : "Vaughn", "newsItem" : "Man bites dog” ] let push = PFPush() push.setQuery(injuryReportsdata) push.setChannel("Indians") push.setData(data) push.sendPushInBackground()
  140. 140. エクスパイアの設定  デバイスの電源が入っていなかったり、インターネットに接続 されていない場合、push通知は配達されない。もし、遅れて 配達されれば意味のない時間に敏感な通知の場合には、エ クスパイアーする日付を設定できる。これで、もはや重要では ない情報で、不必要にユーザーに警告するのを避ける。  通知のエクスパイアーの時間を指定するために、Parseでは 二つのパラメーターが用意されている。一つは、Parseが通 知を送るのを止めるべき日付を指定する expiration date である。  例えば、これから正確に一週間後に、通知をエクスパイアー させるには、次のようにする。
  141. 141. Parse.Push.send({ where: everyoneQuery, expiration_time: new Date(2015, 5, 10) data: { alert: "Season tickets on sale until May 10, 2015" } }, { success: function() { // Push was successful }, error: function(error) { // Handle error } }); ParsePush push = new ParsePush(); push.setExpirationTime(1430762356); push.setQuery(everyoneQuery); push.setMessage("Season tickets on sale until May 4th"); push.sendPushInBackground(); 指定の日付5/15以降は 通知を受け取れないように 設定する。
  142. 142. var push = new ParsePush(); push.Expiration = new DateTime(2015, 5, 4); push.Alert = "Season tickets on sale until May 4th"; await push.SendAsync(); // Create date object for tomorrow NSDateComponents *comps = [[NSDateComponents alloc] init]; [comps setYear:2015]; [comps setMonth:5]; [comps setDay:4]; NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; NSDate *date = [gregorian dateFromComponents:comps]; // Send push notification with expiration date PFPush *push = [[PFPush alloc] init]; [push expireAtDate:date]; [push setQuery:everyoneQuery]; [push setMessage:@"Season tickets on sale until May 4th"]; [push sendPushInBackground];
  143. 143. プラットフォームでターゲットする // Notification for Android users var queryAndroid = new Parse.Query(Parse.Installation); queryAndroid.equalTo('deviceType', 'android'); Parse.Push.send({ where: queryAndroid, data: { alert: "Your suitcase has been filled with tiny robots!" } }); // Notification for iOS users var queryIOS = new Parse.Query(Parse.Installation); queryIOS.equalTo(‘deviceType’, ‘ios'); Parse.Push.send({ where: queryIOS, data: { alert: "Your suitcase has been filled with tiny robots!" } }); Android端末のみに 通知を送る iOS端末のみに 通知を送る
  144. 144. // Notification for Windows 8 users var queryWindows = new Parse.Query(Parse.Installation); queryWindows.equalTo('deviceType', 'winrt'); Parse.Push.send({ where: queryWindows, data: { alert: "Your suitcase has been filled with tiny glass!" } }); // Notification for Windows Phone 8 users var queryWindowsPhone = new Parse.Query(Parse.Installation); queryWindowsPhone.equalTo('deviceType', 'winphone'); Parse.Push.send({ where: queryWindowsPhone, data: { alert: "Your suitcase is very hip; very metro." } }); WinRT端末のみに 通知を送る WindowsPhone端末 のみに通知を送る
  145. 145. ParseQuery query = ParseInstallation.getQuery(); query.whereEqualTo("channels", "suitcaseOwners"); // Notification for Android users query.whereEqualTo("deviceType", "android"); ParsePush androidPush = new ParsePush(); androidPush.setMessage("Your suitcase has been filled with tiny robots!"); androidPush.setQuery(query); androidPush.sendPushInBackground(); // Notification for iOS users query.whereEqualTo("deviceType", "ios"); ParsePush iOSPush = new ParsePush(); iOSPush.setMessage("Your suitcase has been filled with tiny apples!"); iOSPush.setQuery(query); iOSPush.sendPushInBackground(); Targeting by Platform
  146. 146. // Notification for Windows 8 users query.whereEqualTo("deviceType", "winrt"); ParsePush winPush = new ParsePush(); winPush.setMessage("Your suitcase has been filled with tiny glass!"); winPush.setQuery(query); winPush.sendPushInBackground(); // Notification for Windows Phone 8 usersquery.whereEqualTo("deviceType", "winphone"); ParsePush wpPush = new ParsePush(); wpPush.setMessage("Your suitcase is very hip; very metro."); wpPush.setQuery(query); wpPush.sendPushInBackground();
  147. 147. // Notification for Android users var androidPush = new ParsePush(); androidPush.Alert = "Your suitcase has been filled with tiny robots!"; androidPush.Query = from installation in ParseInstallation.Query where installation.Channels.Contains("suitcaseOwners") where installation.DeviceType == "android" select installation; await androidPush.SendAsync(); // Notification for iOS usersvar iOSPush = new ParsePush(); iosPush.Alert = "Your suitcase has been filled with tiny apples!"; iosPush.Query = from installation in ParseInstallation.Query where installation.Channels.Contains("suitcaseOwners") where installation.DeviceType == "ios" select installation; await iosPush.SendAsync(); // …….
  148. 148. PFQuery *query = [PFInstallation query]; [query whereKey:@"channels" equalTo:@"suitcaseOwners"]; // Notification for Android users [query whereKey:@"deviceType" equalTo:@"android"]; PFPush *androidPush = [[PFPush alloc] init]; [androidPush setMessage:@"Your suitcase has been filled with tiny robots!"]; [androidPush setQuery:query]; [androidPush sendPushInBackground]; // Notification for iOS users [query whereKey:@"deviceType" equalTo:@"ios"]; PFPush *iOSPush = [[PFPush alloc] init];[iOSPush setMessage:@"Your suitcase has been filled with tiny apples!"]; [iOSPush setChannel:@"suitcaseOwners"]; [iOSPush setQuery:query]; [iOSPush sendPushInBackground]; // ……
  149. 149. Pushのスケジューリング var query = new Parse.Query(Parse.Installation); query.equalTo('user_id', 'user_123'); Parse.Push.send({ where: query, data: { alert: "You previously created a reminder for the game today" }, push_time: new Date(2015, 5, 10) }, { success: function() { // Push was successful }, error: function(error) { // Handle error } }); 2015年5月10日に、 通知を送るように スケジュールする
  150. 150. Cloud Code PUSHは、クラウドからの送出が推奨されている。 Parseでは、デフォールトで利用可能なParse Objectを通じた、Parse Cloudのデータベース利用 以外にも、明示的にクラウド側のコードを設定できる。
  151. 151. Cloud Codeのセットアップ $ parse new MyCloudCode Email: ninja@gmail.com Password: 1:MyApp Select an App: 1 $ cd MyCloudCode -config/ global.json -cloud/ main.js -public/ index.html
  152. 152. Cloud Codeのdeploy Parse.Cloud.define("hello", function( request, response ) { response.success("Hello world!"); } ); $ parse deploy curl -X POST -H "X-Parse-Application-Id: …. " -H "X-Parse-REST-API-Key: …." -H "Content-Type: application/json" -d '{}' https://api.parse.com/1/functions/hello
  153. 153. Cloud上の関数に渡される 二つの引数  request  params  user  response  success  error
  154. 154. Cloud Codeの実行 run Parse.Cloud.run('hello', {}, { success: function(result) { // result is 'Hello world!' }, error: function(error) { } }); ParseCloud.callFunctionInBackground("hello", new HashMap<String, Object>(), new FunctionCallback<String>() { void done(String result, ParseException e) { if (e == null) { // result is "Hello world!” } } });
  155. 155. var result = await ParseCloud.CallFunctionAsync<IDictionary<string, object>>( "hello", new Dictionary<string, object>() ); // result is "Hello world!" Cloud Codeの実行 [PFCloud callFunctionInBackground:@"hello" withParameters:@{} block:^(NSString *result, NSError *error) { if (!error) { // result is @"Hello world!" } }];
  156. 156. Cloud Codeサンプル Parse.Cloud.define("averageStars", function( request, response ) { var query = new Parse.Query("Review"); query.equalTo("movie", request.params.movie); query.find({ success: function(results) { var sum = 0; for (var i = 0; i < results.length; ++i) { sum += results[i].get("stars"); } response.success(sum / results.length); }, error: function() { response.error("movie lookup failed"); } }); }); 映画のレビューの 平均値を計算する
  157. 157. sendPushToUser Parse.Cloud.define(“sendPushToUser”, function(request, response) { var senderUser = request.user; var recipientUserId = request.params.recipientId; var message = request.params.message; // メッセージを送っていいのかのチェックをする (友達にのみ送る) // ユーザーは、友達の情報を持っている if (senderUser.get("friendIds").indexOf(recipientUserId) === -1) { response.error( "The recipient is not the sender's friend, cannot send push.” ); } // メッセージが140文字以内かのチェックをする if (message.length > 140) { // 長すぎたら切り詰めて後ろに“...”を置く message = message.substring(0, 137) + "..."; } 友人にショート メッセージを送る
  158. 158. // 受け取り手の送り先を調べる var recipientUser = new Parse.User(); recipientUser.id = recipientUserId; var pushQuery = new Parse.Query(Parse.Installation); pushQuery.equalTo("user", recipientUser); // Push通知を行う Parse.Push.send({ where: pushQuery, data: { alert: message } }).then(function() { response.success("Push was sent successfully.") }, function(error) { response.error("Push failed to send with error: " + error.message); }); }); 友人にショート メッセージを送る
  159. 159. ショート・メッセージの送信 [PFCloud callFunctionInBackground:@"sendPushToUser" withParameters:@{@"recipientId": userObject.id, @"message": message} block:^(NSString *success, NSError *error) { if (!error) { // Push sent successfully } }]; HashMap<String, Object> params = new HashMap<String, Object>(); params.put("recipientId", userObject.getObjectId()); params.put("message", message); ParseCloud.callFunctionInBackground("sendPushToUser", params, new FunctionCallback<String>() { void done(String success, ParseException e) { if (e == null) { // Push sent successfully } } });
  160. 160. Cloud Code Trigger Cloud Codeは、Cloud Code Trigger として、デ ータの妥当性チェックや、データの再フォーマット、例 外の処理等に利用できる。AOP的な使い方。
  161. 161. beforeSave Triggers Parse.Cloud.beforeSave("Review", function(request, response) { if (request.object.get("stars") < 1) { response.error("you cannot give less than one star"); } else if (request.object.get("stars") > 5) { response.error("you cannot give more than five stars"); } else { response.success(); } }); Parse.Cloud.beforeSave(Parse.User, function(request, response) { if (!request.object.get("email")) { response.error("email is required for signup"); } else { response.success(); } }); 妥当性チェック: 星の数は、1から5まで 妥当性チェック:サインアップ には、e-mailが必要
  162. 162. 保存時にデータを変更する Parse.Cloud.beforeSave("Review", function(request, response) { var comment = request.object.get("comment"); if (comment.length > 140) { // Truncate and add a ... request.object.set("comment", comment.substring(0, 137) + "..."); } response.success(); }); コメントの長さを、140文字以内 に切り詰めてから保存する
  163. 163. afterSave Triggers Parse.Cloud.afterSave("Comment", function(request) { query = new Parse.Query("Post"); query.get(request.object.get("post").id, { success: function(post) { post.increment("comments"); post.save(); }, error: function(error) { console.error("Got an error " + error.code + " : " + error.message); } }); }); 保存後に、コメント数を、 1つ増やす。
  164. 164. beforeDelete Triggers Parse.Cloud.beforeDelete("Album", function(request, response) { query = new Parse.Query("Photo"); query.equalTo("album", request.object.id); query.count({ success: function(count) { if (count > 0) { response.error("Can't delete album if it still has photos."); } else { response.success(); } }, error: function(error) { response.error("Error " + error.code + " : " + error.message + " when getting photo count."); } }); }); 写真が含まれている アルバムは削除しない
  165. 165. afterDelete Triggers Parse.Cloud.afterDelete("Post", function(request) { query = new Parse.Query("Comment"); query.equalTo("post", request.object.id); query.find({ success: function(comments) { Parse.Object.destroyAll(comments, { success: function() {}, error: function(error) { console.error("Error deleting related comments " + error.code + ": " + error.message); } }); }, error: function(error) { console.error("Error finding related comments " + error.code + ": " + error.message); } });}); 全コメント削除 後の、処理
  166. 166. Web技術の新しい展開 Facebookのモバイル・アプリ開発技術は、ダイナミッ クに進化している。Parse + React, さらには、 ReactのReact Nativeへの進化にも目が離せない Part III
  167. 167. Part III Web技術の新しい展開  MVCとFlux  Reactとは何か?  React を、サンプルから学ぶ  Parse + React  Mutating Parse Data  React Native  Relay and GraphQL
  168. 168. MVCとFlux FluxでのMVCモデルの見直しは、Reactの前身と言 っていいもの。データの流れは、双方向ではなく、一 方向のものになる。 https://facebook.github.io/flux/docs/overview.html
  169. 169. Flux: Structure and Data Flow
  170. 170. Flux: Structure and Data Flow Flux
  171. 171. Reactとは何か? Reactは、ユーザー・インターフェースを構築するため のJavaScriptライブラリーである。
  172. 172. Reactは、UIのみにフォーカス  多くの人は、ReactをMVCのVとして利用している。 Reactは、Viewのみにフォーカスしていて、その他のテ クノロジーについては、いかなる前提も行っていないため 、既存のプロジェクトに容易に取り入れることができる。
  173. 173. Reactは、DOMを必要としない  Reactは、DOMという考え方を必要としていない。そのこ とで、プログラミング・モデルは単純になり、 パフォーマン スも向上する。  Reactは、Nodeサーバー上でのレンダリングも可能であ る。また、React Nativeを使ってネイティブ・アプリにパ ワーを与えることができる。
  174. 174. Reactでは、データは一方向に流れる  React は、一方向のreactiveなデータの流れを実装し ている。これによって、定型的なコードの繰り返しを少なく し、伝統的なデータのバインディングをわかりやすくする。
  175. 175. React を、サンプルから学ぶ Reactでは、render メソッドの返り値に、直接、 HTMLライクなタグが書ける。わざわざ、Templateを 書く必要もない。それは、とても分かりやすい。
  176. 176. Reactの Hello World! サンプル var CommentBox = React.createClass({ render: function() { return ( <div className="commentBox"> Hello, world! I am a CommentBox. </div> ); } }); React.render( <CommentBox />, document.getElementById(‘content’) ); // renderの第二引数は、この描画が行われるマウントポイントである。
  177. 177. renderメソッドとJSX記法  React コンポーネントは、入力データをとり、表示されるも のを返す render() メソッドを実装する。次の例では、 XMLに似た、JSXと呼ばれるシンタックスを利用している。 コンポーネントに渡される入力データは、renderから、 this.props.を通じてアクセスできる・ var HelloMessage = React.createClass({ render: function() { return <div>Hello {this.props.name} </div>; } }); React.render( <HelloMessage name="John" /> , mountNode);
  178. 178. var HelloMessage = React.createClass({ displayName: "HelloMessage", render: function() { return React.createElement( “div”, null, “Hello ”,this.props.name); } }); React.render(React.createElement( HelloMessage, {name: "John"}), mountNode); JSXはオプション  JSXはオプションで、Reactを使う上では必須ではない。 次のようにかける。
  179. 179. コンポーネントを組み合わせる var CommentList = React.createClass({ render: function() { return ( <div className="commentList"> Hello, world! I am a CommentList. </div> ); } }); var CommentForm = React.createClass({ render: function() { return ( <div className="commentForm"> Hello, world! I am a CommentForm. </div> ); } });
  180. 180. コンポーネントを組み合わせる var CommentList = React.createClass({ render: function() { return ( <div className="commentList"> Hello, world! I am a CommentList. </div> ); } }); var CommentForm = React.createClass({ render: function() { return ( <div className="commentForm"> Hello, world! I am a CommentForm. </div> ); } }); var CommentBox = React.createClass({ render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList /> <CommentForm /> </div> ); } }); このCommentBoxコンポーネントは、 内部で、CommentListとCommentForm というコンポーネントを利用している。 クラス名は、JSXのタグの名前として利用 できる。
  181. 181. コンポーネントのプロパティ this.propsを利用する var Comment = React.createClass({ render: function() { return ( <div className="comment"> <h2 className="commentAuthor"> {this.props.author} </h2> {this.props.children} </div> ); } }); JSX内部の、{...}に囲まれた部分は、 JavaScriptの値が展開される。 this.propを利用すると、コンポーネント のプロパティの値が利用できる。
  182. 182. コンポーネントのプロパティを利用する var CommentList = React.createClass({ render: function() { return ( <div className="commentList"> <Comment author="Pete Hunt"> This is one comment </Comment> <Comment author="Jordan Walke"> This is *another* comment </Comment> </div> ); } }); 二つのCommentコンポネントが利用 されている。最初のコンポーネントでは、 this.prop.authorは、“Pete Hunt” で二つ目のコンポーネントでは、その値 は、“Jordan Walker”になる。
  183. 183. データ・モデルと結びつける var CommentList = React.createClass({ render: function() { var commentNodes = this.props.data.map( function (comment) { return ( <Comment author= {comment.author}> {comment.text} </Comment> ); }); return ( <div className="commentList"> {commentNodes} </div> ); } }); ここでは、renderの内部で、もう一つの 関数 commentNodesが定義されている この関数では、mapで、複数個のエントリ を持つリスト・データが処理される。
  184. 184. データ・モデルと結びつける var data = [ {author: "Pete Hunt", text: "This is one comment"}, {author: "Jordan Walke", text: "This is *another* comment"} ]; var CommentBox = React.createClass({ render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data= {this.props.data} /> <CommentForm /> </div> ); } }); React.render( <CommentBox data= {data} />, document.getElementById('content') );
  185. 185. 状態を持つコンポーネント var CommentBox = React.createClass({ getInitialState: function() { return {data: []}; }, render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data= {this.state.data} /> <CommentForm /> </div> ); } }); Reactでは、propsとstateは、 区別されている。stateの変更は、 renderの再描画を引き起こす。
  186. 186. 状態を持つコンポーネント var Timer = React.createClass({ getInitialState: function() { // 状態の初期値の設定 return {secondsElapsed: 0}; }, tick: function() { // 状態が変わるとrenderが呼ばれる this.setState({secondsElapsed: this.state.secondsElapsed + 1}); }, componentDidMount: function() { this.interval = setInterval(this.tick, 1000); }, componentWillUnmount: function() { clearInterval(this.interval); }, render: function() { return ( // 状態は、this.state でアクセスできる <div>Seconds Elapsed: {this.state.secondsElapsed}</div> ); } }); React.render(<Timer />, mountNode);
  187. 187. var TodoList = React.createClass({ render: function() { var createItem = function(itemText, index) { return <li key={index + itemText}>{itemText}</li>; }; return <ul>{this.props.items.map(createItem)}</ul>; } }); var TodoApp = React.createClass({ getInitialState: function() { return {items: [], text: ''}; }, onChange: function(e) { this.setState({text: e.target.value}); }, handleSubmit: function(e) { e.preventDefault(); var nextItems = this.state.items.concat([this.state.text]); var nextText = ''; this.setState({items: nextItems, text: nextText}); }, Reactサンプル ToDo アプリ
  188. 188. render: function() { return ( <div> <h3>TODO</h3> <TodoList items={this.state.items} /> <form onSubmit={this.handleSubmit}> <input onChange={this.onChange} value={this.state.text} /> <button>{'Add #' + (this.state.items.length + 1)}</button> </form> </div> ); } }); React.render(<TodoApp />, mountNode);
  189. 189. Parse + React Parse + React は、React からParse APIへの簡 単なアクセスを提供する Parse JS SDK上のインタ ーフェース層である。 https://github.com/ParsePlatform/ParseReact
  190. 190. Parse + React とは?  Parse + React は、 Reactのコンポーネントを Parse の queryにsubscribe させて、データの変化を、Flux- styleで伝えることを可能にする。  オブジェクトの生成や変更の際に、これらのコンポーネン トが、バックグラウンドで、自動的に更新されるように、こ れらのsubscriptionは、管理される。  こうしてユーザーインターフェースは、機敏で反応良いも のになることができる。
  191. 191. Parse DataへのSubscribe  Parse dataをReactアプリケーションに持ち込む一番い いやり方は、ReactのコンポーネントをParseのqueryに subscribeすることである。  Parse Queryへのsubscribeには、observeメソッドが 利用される。  コンポーネントがマウントすると、queryは変わったり、明 示的にリフレッシュされる。その度に、新しいデータが Parseから取り出されて、コンポーネントに渡され、 renderによるコンポーネントの再描画 が引きおこされる 。
  192. 192. queryの更新  コンポーネントがマウントした時に、それぞれのqueryが 取り込まれる。それらの結果が受け取られ、コンポーナン トに付け加えられた時にはいつも、コンポーネントは更新 され、再描画される。  もし、propsやstateが変わっていれば、subscriptionは 全て再計算される。結果として変化したqueryは、すべて 、再取り込みされる。  queryは、いつでも、this.refreshQueries()を呼ぶこと で、明示的にリフレッシュされる。
  193. 193. observe メソッド  ParseReact.Mixinオブジェクトを mixinのコンポーネ ントのリストに追加することで、observe() ライフサイクル 関数のサポートが得られるようになる。  新しく提案された React のlifecycleメソッドである observe() によって、コンポーネントは queryに、 subscribe される。  この observeメソッドは、コンポーネントが更新するたび に、描画の直前に走る。  直近の propsとstateをパラメーターとして受け取って、 observeは、Parse.Queryオブジェクトを構成して、 queryに対する文字列のmapを返す。この文字列のキー は、それぞれのqueryを同定する名前として利用される。
  194. 194. observe メソッド  observe()は、コンポーネントが更新される時、描画の前 に呼ばれる。  マウント時には、初期値のpropsとstateが関数のパラメ ーターとして渡される。その後は、最新のpropsとstateが パラメーターとして渡される。それは、このメソッドが、 componentWillUpdateから呼ばれているからである。  このメソッドは、key/valueのペアからなるオブジェクトを 返す。それぞれのkeyは、subscriptionの名前を表し、 そのvalueは、Parse.QueryかLocal Subscriptionで ある。
  195. 195. subscribe sample  この例では、コンポーネントを、50票以上の投票を集めたコ メントを、生成日の順に並べた query に subscribe して いる。それは、comments という名前に関連付けられてい るので、この結果の集合は、 this.data.comments と して利用可能になる。. observe: function( props, state) { return { comments: (new Parse.Query('Comment')) .greaterThan('votes', 50) .ascending('createdAt') }; }
  196. 196. Local Subscription  コンポーネントを、もっとローカルなコンセプトに subscribe することも可能である。コンポーネントは、 ParseReact.currentUser を通じて、ログインしログアウト する現在のユーザーに subscribe することができる。次の 例では、this.data.user は、現在のユーザーのコピーに等 しくなるだろう。コンポーネントは、ユーザーが変更された時に 更新される。 observe: function() { return { user: ParseReact.currentUser }; }
  197. 197. observe sample var CommentBlock = React.createClass({ mixins: [ParseReact.Mixin], // queryへの subscriptionを可能にする observe: function() { // 生成順に並べられた全てのCommentオブジェクトにsubscribeする // その結果は、this.data.comments で利用出来る return { comments: (new Parse.Query('Comment')).ascending('createdAt') }; }, render: function() { // それぞれのcommentをリストのアイテムとして描画する return ( <ul> {this.data.comments.map(function(c) { return <li>{c.text}</li>; })} </ul> ); } });
  198. 198.  このコンポーネントがマウントされた時にはいつも、 queryが発行されて、その結果が this.data.comments に加えられる。  queryが再発行されるか、queryにマッチしたオブジェク トがローカルに変更されるたびに、それは、こうした変化を 反映するように、自分自身を更新する。
  199. 199. Mutating Parse Data Parse + React では、Parseと共有されるデータは、 Mutation を通じて変更される。Mutationは、 dispatchされると Parse APIとクライアントのアプリ の両方に、なんらかの方法でデータを更新せよと伝え るメッセージである。 https://goo.gl/EPhLPy
  200. 200. mutation.dispatch([options])  mutation.dispatchは、生成・削除・変更といったParse オブジェクトの変化を、Parse APIにリクエストを行うこと で実行する。  サーバーからのレスポンスを待つことが、明示的に要求さ れない場合、オブジェクトはローカルに楽観的に更新され る。オブジェクトが更新された場合、新しいバージョンが、 subscribeされた全てのコンポーネントにプッシュされる。  dispatch は、Parse.Promiseを返す。 このpromiseは、サーバーのリクエストが完了した時に、 成功裡に解決される。 エラーが起きた場合には、この promiseは、リジェクトされる。
  201. 201. Data mutation  データの変化 mutationはFluxのActionのやり方で dispatch される。それで、多くの異なるコンポーネント同 士が話しかけるviewを要求することなしに、更新を同期 することが可能になる。標準的なParseのデータの変化は 、全てサポートされている。 // Create a new Comment object with some initial data ParseReact.Mutation.Create('Comment', { text: 'Parse <3 React’ }).dispatch();
  202. 202.  Mutationを生成するためには、最初に適当なコンストラクタを 呼ばなければならない。これらのメソッドは、Mutations API で見ることができる。いったん Mutation が生成されれば、それ は、dispatch()を呼ぶことで実行される。 // Create a new Pizza object var creator = ParseReact.Mutation.Create('Pizza’, { toppings: [ 'sausage', 'peppers' ], crust: 'deep dish' }); // ...and execute it creator. dispatch();
  203. 203. 多くのMutationでは、変更されるべき特定のオブジェクトを自由 に選べる。それは、Query Subscriptio から受け取るオブジェ クトであるべきである。次の例は、queryから受け取ったオブ ジェクトをどのように更新すべきかを示している。 React.createClass({ mixin: [ParseReact.Mixin], observe: function() { return { counters: new Parse.Query('Counter'); }; }, // ... // _myClickHandler は、コンポーネントがクリックされた時に呼び出されると // としよう。この時、すべてのCounterオブジェクトの値を増やす。 _myClickHandler: function() { this.data.counters.map(function(counter) { ParseReact.Mutation.Increment(counter, 'value').dispatch(); }); } })
  204. 204. // objectIdが ‘c123’であるCounterを削除したい時は、 // 次のように、情報をMutationに渡せば、削除ができる var target = { className: 'Counter', objectId: 'c123' }; ParseReact.Mutation.Destroy(target).dispatch();  queryのsubscriptionから受け取ったものでも、親オブ ジェクトから受け取ったpropでも、コンポーネントが受け 取ったオブジェクトを変更するのは容易である。  直接は見ることのできないオブジェクトでも、それを変更す ることは可能である。そのオブジェクトのclassNameと objectIdを知っていれば、これらのフィールドを mutationに渡せば、それを修正することが出来る。
  205. 205. var creator = ParseReact.Mutation.Create('Pizza', { toppings: [ 'sausage', 'peppers' ], crust: 'deep dish' }); // 同じトッピングで、三つの新しいピザを作る creator. dispatch(); creator. dispatch(); creator. dispatch(); Mutationを、わざわざdispatchする一つの理由は、Mutationを独立 の操作として、後の実行のために保存したり、複数回の実行を可能にす るためである。
  206. 206. React Native https://github.com/facebook/react-native
  207. 207. React Native  React Native は、JavaScriptとReactベースの一貫し た開発者の経験を利用して、ネイティブなプラットフォーム 上で、世界クラスのアプリを構築することを可能とする。  React Natieのフォーカスは、開発者が関心を持つ全て のプラットフォームに渡って、「一度学べば、どこでも書け る」という、開発者の効率性に置かれている。  Facebookは、React Native を複数の製品版のアプリ に利用しており、これからも、React Nativeに投資を続 けていくだろう。
  208. 208. Native iOS Components  React Nativeで、iOS上での UITabBar や UINavigationControllerといった標準的なプラットフォ ームのコンポーネントを利用できる。  このことで、アプリケーションは、プラットフォームのその他 の部分とのルック・アンド・フィールは整合的になり、アプリ の品質の水準は、高いものに維持できる。  これらのコンポーネントは、それらのReactコンポーネント での対応物である TabBarIOSやNavigatorIOSを利用 することで、容易にアプリと一体化する。
  209. 209. TabBarIOS, NavigatorIOSの利用 var React = require('react-native'); var { TabBarIOS, NavigatorIOS } = React; var App = React.createClass({ render: function() { return ( <TabBarIOS> <TabBarIOS.Item title="React Native" selected={true}> <NavigatorIOS initialRoute={{ title: 'React Native' }} /> </TabBarIOS.Item> </TabBarIOS> ); }, });
  210. 210. 非同期実行  JavaScriptアプリ・コードとナイティブなプラットフォーム の間の操作は、全て、非同期で実行される。ネイティブな モジュールは、追加のスレッドも利用できる。このことは、 メインスレッドとは別のスレッドで画像のデコードをしたり、 バックグラウンドでディスクに保存したり、UIを組上げなく ても、テキストの評価やレイアウトの計算等々が可能とな ることを意味している。その結果、React Nativeアプリは 、自然になめらかに動き、反応も良いものになる。  コミュニケーションも完全にシリアライザブルなので、 JavaScriptのデバッグを、完全なアプリを、シミュレータ ーでも実機でも実際に動かしながら、 Chrome Developer Toolsで行うことが可能になる。
  211. 211. Touch Handling  iOSは、Web上では一般的な対応物がないような、複雑 なviewの階層でのタッチとやりとりする、非常に強力な Responder Chain というシステムを持っている。React Nativeは、同様のresponderシステムを実装し、一切設 定を追加することなく、スクロール・ビューやその他のエレ メントと統合される、TouchableHighlightといった高レ ベルのコンポーネントを提供している。
  212. 212. var React = require('react-native'); var { ScrollView, TouchableHighlight, Text } = React; var TouchDemo = React.createClass({ render: function() { return ( <ScrollView> <TouchableHighlight onPress={() => console.log('pressed')}> <Text>Proper Touch Handling</Text> </TouchableHighlight> </ScrollView> ); }, });
  213. 213. Flexbox and Styling  viewのレイアウトは、容易なものでなければならない。そ れが、我々がWebからReact Native に、flexbox レイ アウトを導入した理由である。flexlayoutは、マージンや パディングを持った積み重ねられ入れ子にされたboxの ような、もっとも一般的なUIのレイアウトを構築するのを簡 単にする。  React Native は、fontWeightのような、Webと共通の styleをサポートしている。StyleSheetという抽象は、ス タイルやレイアウトを、それを用いるコンポーネントと一緒 に宣言し、また、内部でそれを適用するのに最適化された メカニズムを提供している。
  214. 214. var React = require('react-native'); var { Image, StyleSheet, Text, View } = React; var ReactNative = React.createClass({ render: function() { return ( <View style={styles.row}> <Image source={{uri: 'http://facebook.github.io/react/img/logo_og.png'}} style={styles.image} /> <View style={styles.text}> <Text style={styles.title}> React Native </Text> <Text style={styles.subtitle}> Build high quality mobile apps using React </Text> </View> </View> ); }, }); var styles = StyleSheet.create({ row: { flexDirection: 'row', margin: 40 }, image: { width: 40, height: 40, marginRight: 10 }, text: { flex: 1, justifyContent: 'center'}, title: { fontSize: 11, fontWeight: 'bold' }, subtitle: { fontSize: 10 },});

×