DroidKaigi 2017に参加しました [Day2]
DroidKaigi 2017の2日目(3/10)に(途中から)参加しました。 (1日目の記録はこちら)
ここでは私が聴いた講演のメモやら感想やらを載せます。
位置情報を正確にトラッキングする技術
※ 資料が公開されたらリンク貼ります。
コードから位置情報取得設定が可能。基地局情報を使用しないトラッキング方法だと精度が落ちるのでなるべく使うべき。
位置情報トラッキング中に障害物の遮蔽により精度が出なかったりAndroid OSでキャッシュした古い位置情報が返却されたりする。そのようなノイズを抑えるためにある閾値を超えた返却値は無視したり、KalmanFilterというFilteringを通すと上手く位置情報がトラッキングできる。品質もよく使われているアプリと遜色ない。
Error Handling in RxJava
アプリ開発時によくあるユースケースをRxJavaのエラーハンドリングでどのように扱うかの紹介。講演の大半がエラーハンドリング系のAPIの説明に費やされてしまってたので"うーん。。。"と思ったのが正直なところ。
Rxは鉄道思考プログラミングのような表現が可能だと考えている。このように表現できるとユースケース単位で成功Pathと失敗Pathに分離され、それぞれのPathに集中できるので例外設計がやりやすくなったりするのかなーと思っている。そのあたりのヒントがあればと期待していたので少し残念。
テスト0から目指すクラッシュフリー率99%
テストのない状況からソフトウェアを安定動作させるためにCrashlyticsやFirebase Crash Reportingを導入。クラッシュの原因を探ることができるようになる。Firebase Crash Reportingはクラッシュのコンテキスト(どの画面から遷移したか、どのような操作をしたか)を追うことも可能になる。
テストを書けない典型的なケースはFat Activity。責務を分離していってActivityを薄く持っていく。
DIするぞ->Dagger使うぞ!みたいな感じだと慣れていない人にとっては抵抗を感じる。まずはコンストラクタから注入する形から始める。
ContractorにPresenterとViewの責務を表現。Activity/Fragment/CustomViewでViewを実装、PresenterでAPIアクセスやDBアクセスの結果をViewに伝えるような仕組みにしてFat Activityを避ける。
非同期処理のコールバックには注意。FragmentのonResume ~ onPause以外でFragmentTransaction#commitするとクラッシュする。DialogFragment#show内部でもFragmentTransaction#commitを呼んでいるので要注意。
感想
契約による設計やDIなど特別新しいこともないノウハウでも丁寧にやっていけば良いソフトウェアになる。昨日の講演にあったDDDや単一責任原則などもそう。
今回DroidKaigiに参加してもっと古きを温めたい気持ちになった。
発表者の方々の培ってきたノウハウや苦労話など聴けてとても刺激を受けました。また来年も参加したい!
DroidKaigi 2017に参加しました [Day1]
DroidKaigi 2017の1日目(3/9)に参加しました。
ここでは私が聴いた講演のメモやら感想やらを載せます。
各講演の記録
How to Apply DDD to Android Application Development
DDDは難しいけど怖くない 行き当たりばったりの設計に、とりあえず動けばいいやで作られたアプリを長年メンテナンスし機能追加するほうがよっぽど恐ろしいです。
この言葉はいいなと思った。
質問コーナーでバリデーションロジックはどこに置くべきかとあり、ユーザ操作の補助的なものであればUI層・ドメインモデルとしての存在可否を確認するものであればドメイン層とのこと。
一口にバリデーションで片付けず、詳細を分析する必要があるということは新しい発見だった。
Android Security最前線
※ 資料が公開されたらリンク貼ります。
Android Nougatではsecurity周りが多数アップデートされた。その紹介。
Using Scoped Directory Access
Android 7.0から特定のディレクトリのみにアクセスを許可することが可能になった。
ユーザ側としてはディレクトリ単位でアクセス許可できるのは安心かもしれない。
Direct Boot
Android 6.x以前は端末が暗号化されている場合、ユーザがアンロックしていないと既存の保存先にデータ保存できなかった。
Android7.0からはロック解除されていない状態でダイレクトブートモードで動作し、端末暗号化ストレージにアクセスできる。
端末暗号化ストレージを使う際はセキュリティレベルが下がるので保存するデータは要精査。
Network Security Config
Android 7.0から、カスタムCA証明書を信頼するための安全で簡単なAPIが提供されるようになった。
デフォルトではユーザが追加したCAは信頼しないようになっている。
Android 6.x以前だと設定アプリからCA証明書を入れる操作をしてもらっていたが、その必要がなくなりユーザの負担が軽くなる。
インスペクションとAndroid Lint Custom Ruleによる、単一責任実装の実践
www.slideshare.net
自前でLintルールを追加して単一責任原則を満たしているか確認する手法の紹介。
クラス内のメンバ変数を元に単一責任原則を満たしているかの検査を行う。
チェック対象がプリミティブ値のみなので、"ThoughtWorksアンソロジー オブジェクト指向に近づく9つのルール"の"全てのプリミティブ型と文字列型をラップする"を実践していたら恩恵を受けられないw
でも設計原則をLintに組み込むチャレンジはとても面白い。
大規模アプリのリノベーション
※ 資料が公開されたらリンク貼ります。
2011年から開発されているはてなブックマークアプリのリファクタリングもといリノベーションの記録。
画面遷移をNavigatorとして表現しViewで画面遷移の実装を意識しない仕組みは、画面遷移をコードで表現できメンテナンス性も保たれてとても良さそう。積極的に取り入れたいと思った。
解剖 Kotlin ~バイトコードを読み解く~
Kotlinプラグインを使えばKotlinコードをJavaにデコンパイル可能。Kotlinのコードを理解するためにJavaへデコンパイルしてどんなことをしているのか解読する。
null許容型は変数がnullになる領域の示している。nullの領域を狭めて非null領域を広くとることでnull安全なソフトウェアになる。
拡張関数を使って既存のクラスでもそのクラスが持っておいたほうが良い責務を後付で持たせることができる。
変更に強いEspressoテストコードを効率良く書こう
Espresso Test RecorderはGUI操作で効率的にテストコードを記述できる。
ただし複数のテストパターンをGUI操作だけで作るのは非効率なので、元となるテストコードをGUI操作で生成しAndroid Studioのリファクタリング機能を駆使して共通部分を抽出することで効率的に複数のテストパターンを作成できる。
共通部分が抽出されているので、UIの変更に対しても局所的な変更で済む可能性が高くなる。
実演でAndroid Studioのリファクタリング機能を使ってコードを整理していく様子がとても素晴らしかった。テストコードを書く場合だけでなくプロダクトコードでも活かせそうなのでリファクタリング機能はしっかり習得したい。
Viewを動的に変化させるアプローチ
※ 資料が公開されたらリンク貼ります。
ユーザ操作にあわせてViewが動的に変化する処理を実現するために、スクロール量などを0.0~1.0などの値に正規化して他のviewではその正規化した値を元にalpha値やviewの大きさなどを変化させる。
その際にDataBindingを使いxml内に正規化された値を渡し、諸々の属性値にはその値を元に算出する処理を記述する。
DataBindingはviewにロジックが書けるのでドメイン知識が漏れてしまったり、可読性を損ねてしまうのではと心配しているが、このような使い方であればアリなのかなと思った。
Android Bikeを作ろう
https://speakerdeck.com/tnj/lets-make-android-bike
スマホホルダーが袋式で気圧センサーが外気圧を取得できなかったという話、センサー系を使う場合は環境の影響を受けやすいということを頭に置いておいた方がいいなと思った。
1日目が終わって
RxJavaやDataBindingに言及する講演が多かった。多すぎて少しお腹いっぱい…。やっぱりそれだけ流行っているんだろうなという印象。
講演時間が重なって参加できなかった講演もいくつかあるので、あとで資料読みたいー。
AndroidでJavaslangを動かす
Javaslangとは
Javaの関数型Libraryです。 Option, Future, EitherやCurrying, Compositionなど関数型プログラミングを実現するAPIをいくつか揃えています。
詳細は以下のページで
http://www.javaslang.io/javaslang-docs/
前提
JavaslangはJava8以上が必要なので、Android N(sdk version 24)以上の環境が必須です。
Androidで使ってみる
build.gradleに以下を追加するとJavaslangが使えるようになります。
... android { ... defaultConfig { ... jackOptions { enabled true } ... } ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } dependencies { ... compile "io.javaslang:javaslang:2.0.1" } }
例えばこんな感じで。
List<Integer> list = List.of(1, 2, 3, 4, 5); TextView textView = (TextView) findViewById(R.id.text_view); textView.setText(list.sum().toString());
Android + Kotlinなプロジェクトでjava.lang.NoSuchMethodError
com.android.tools.build:gradle:2.2.0-alpha1
とKotlinを使用する場合、dependenciesの記述順によってはerrorになるみたいです。
NG
dependencies { classpath 'com.android.tools.build:gradle:2.2.0-alpha1' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version' }
OK
dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.android.tools.build:gradle:2.2.0-alpha1' }
KituraでサーバサイドSwift
IBMのサーバサイドフレームワークKituraを使ってSwift製のWebアプリケーションを作ってみます
環境
Mac OSX El Capitan 10.11.4 Swift 2.2 XCode 7.3
Swiftのインストール
自分の環境のインストーラをダウンロードしてインストールします。 Swift.org - Download Swift
※$ swift build
を実行して「swift-build
が見つからないよ」というようなエラーが出た場合はSnapshotsの"Trunk Development"をインストールして下さい。
XCodeのToolchainを変更(XCode7.3の場合)
- XCodeを開き
Preferences
>Components
>Toolchains
でXcode Swift 2.2 Snapshot XXXX
を選択 - パスを通す
$ export PATH=/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:"${PATH}"
Kituraアプリケーションの作成
適当なところに新しいディレクトリを作成
$ mkdir myFirstProject
Swift projectのinitialize
$ cd myFirstProject $ swift build --init
- 以下のようなディレクトリ構成になっているかと思います
myFirstProject ├── Package.swift ├── Sources │ └── main.swift └── Tests └── empty
Package.swiftにKaturaの依存関係を追加
Package.swiftを以下のように編集します
import PackageDescription let package = Package( name: "myFirstProject", dependencies: [ .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 0, minor: 7) ])
main.swiftを編集
Sources/main.swiftを以下のように編集します
import Kitura import KituraNet import KituraSys let router = Router() router.get("/") { request, response, next in response.send("Hello, World!") next() } let server = HttpServer.listen(8090, delegate: router) Server.run()
Compile
Mac OSの場合は以下のコマンドを実行
$ swift build -Xcc -fblocks -Xswiftc -I/usr/local/include -Xlinker -L/usr/local/lib
サーバ起動
$ .build/debug/myFirstProject
ブラウザからhttp://localhost:8090/にアクセスするとWebページが表示されます
次回(あるかわからないけど)はBluemixにKituraアプリをデプロイしてみようかと思っています。
Scalajs + React + Electronでhello world
scala, sbt未インストール状態からScalaJS, Reactを使ってElectronでHello worldするまで
ScalaJSとは
AltJS Scalaで書いたコードがJSに変換される
http://www.scala-js.org/doc/tutorial.html
sbtとは
Scala向けのpackage managerみたいなやつ
Scala, sbtをインストール
homebrew などでインストール
$ brew install scala $ brew install sbt
Project作成
$ mkdir sample $ cd sample $ touch build.sbt $ mkdir project $ touch project/plugins.sbt $ touch project/build.properties $ sbt
ScalaJSのTutorialにそってHelloworldする
http://www.scala-js.org/doc/tutorial.html
このTutorialの'Integrating with HTML'まで
これをベースにこの後scalajs-reactを導入し、Electronで動くようにする
scalajs-reactの導入
こいつを導入します
build.sbtを編集
build.sbtにscalajs-reactの依存関係を追加
enablePlugins(ScalaJSPlugin) name := "Scala.js Tutorial" scalaVersion := "2.11.6" libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.8.0" libraryDependencies += "com.github.japgolly.scalajs-react" %%% "core" % "0.9.0" libraryDependencies += "com.github.japgolly.scalajs-react" %%% "extra" % "0.9.0" jsDependencies += "org.webjars" % "react" % "0.12.2" / "react-with-addons.js" commonJSName "React"
target/index.htmlを編集
reactを導入することでJSライブラリにも依存関係が発生するのでindex.htmlも変更します
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>ScalaJS Tutorial</title> </head> <body> <script type="text/javascript" src="./scala-2.11/scala-js-tutorial-jsdeps.js"></script> <script type="text/javascript" src="./scala-2.11/scala-js-tutorial-fastopt.js"></script> <script type="text/javascript"> tutorial.webapp.TutorialApp().main(); </script> </body> </html>
TutorialApp.scalaを編集
reactを使って'hello world'の出力を行います
package tutorial.webapp import scala.scalajs.js.JSApp import org.scalajs.dom import dom.document import japgolly.scalajs.react.{React, ReactComponentB} import japgolly.scalajs.react.vdom.prefix_<^._ object TutorialApp extends JSApp { def main(): Unit = { val hello = ReactComponentB[Unit]("Hello world") .render(_ => <.div("Hello world")) .buildU React.render(hello(), document.body) } }
一旦この状態でブラウザで確認してみて'Hello world'が表示されていたら次に進む
Electronアプリ化
基本的にはElectronのquick-startで作成しているファイルをtarget/ 配下に作ります
electron/quick-start.md at master · atom/electron · GitHub
npm init npm install --save electron-prebuilt
target/ 配下にmain.jsを作成
$ touch target/main.js
var app = require('app'); // Module to control application life. var BrowserWindow = require('browser-window'); // Module to create native browser window. // Report crashes to our server. require('crash-reporter').start(); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the javascript object is GCed. var mainWindow = null; // Quit when all windows are closed. app.on('window-all-closed', function() { if (process.platform != 'darwin') { app.quit(); } }); // This method will be called when Electron has done everything // initialization and ready for creating browser windows. app.on('ready', function() { // Create the browser window. mainWindow = new BrowserWindow({width: 800, height: 600}); // and load the index.html of the app. mainWindow.loadUrl('file://' + __dirname + '/index.html'); // Open the devtools. mainWindow.openDevTools(); // Emitted when the window is closed. mainWindow.on('closed', function() { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWindow = null; }); });
この状態で
$ electron target/main.js
Electronアプリが起動して'Hello world'が表示されたらOK
Neo4jで一意なpropertyを持つNodeを作る
アプリケーション内でuserのmailアドレスなど、あるLabelを持つnodeのpropertyに一意制約をつけるにはConstraintsを使う
http://neo4j.com/docs/stable/query-constraints.html
Cypherはこのような感じ
CREATE CONSTRAINT ON (user:User) ASSERT user.email IS UNIQUE
以下のような同じemail propertyを持つuserを複数回登録するとエラーになります
CREATE (user:User {name: "Taro", email: "test@example.com"}) CREATE (user:User {name: "Taro", email: "test@example.com"}) # => Node 3 already exists with label User and property "email"=[test@example.com]
また、この方法は'User' labelの付いているNodeのみ制限がかかるようになる。
Leiningenで作成したアプリケーションをHerokuにDeployするとエラー
Leiningenで作成したRingを使ったアプリケーションをHerokuにDeployした際に、 このようなエラーが出てアプリケーションが起動しなくなってしまった場合の対処法です。
Exception in thread "main" java.io.FileNotFoundException: Could not locate leiningen/core/classpath__init.class or leiningen/core/classpath.clj on classpath: (server.clj:1)
HerokuにClojure用のbuildpackを登録
$ heroku buildpack:set https://github.com/heroku/heroku-buildpack-clojure
project.cljでLeiningenのminimum versionを設定
project.cljに以下を追加
:min-lein-version "2.0.0"
Herokuにpush
この状態でherokuにpushするとエラーが解消され、問題なくアプリケーションが動くようになっているはず。
参考
ActiveRecordでBigqueryを使ってみる
目的
メインDB + Bigqueryの複数DBなRailsアプリケーションでActiveRecordからBigqueryにあるテーブルを参照するまでの手順。
前提として、Bigquery上のtable, recordの作成などは行っていることとします。
ここではローカル環境や外部サーバ上のRailsアプリケーションからそのテーブルを参照できるようにしていきます。
GCP Projectの作成
ここからGoogle Cloud Platformのプロジェクトを作成する
https://console.developers.google.com/project
クライアントIDの作成
- 作成したprojectを選択し、"APIと認証" > "認証情報"を選択する
- "新しいクライアントIDを作成"を押すとクライアントID作成用のポップアップが表示される
- "アプリケーションの種類"に"サービスアカウント"を選択
- "キーのタイプ"に"P12 key"を選択
- この状態でIDを作成
- IDが作成されたら秘密キーのパスワードが表示されるので控えておく(これはあとで使う)
- p12ファイルがダウンロードされる(これもあとで使う)
- サービスアカウントに新しいクライアントIDが作成されていることを確認
- 同時にメールアドレスも作成されていることも確認(これもあとで使う)
Gemのインストール
ActiveRecordでBigqueryを使うためのGemにBigBrodaというものがあるので、それを使う
Gemfileにgem "bigbroda"
を追加して$ bundle install
RailsからBigqueryに接続する
設定ファイルの生成
Gemをインストール後、bigqueryの設定ファイルをインストールします
rails g google_bigquery:install
これで config/initializers 配下にbigqueryに接続するための設定ファイル(bigquery.rb)が追加されます
p12ファイルを配置
先ほど作成したp12ファイルをRailsプロジェクト内の適当な場所(config内など)に移動させておいてください
bigquery.rbを編集
bigquery.rbを以下のように編集してください
GoogleBigquery::Config.setup do |config| config.pass_phrase = ["pass_phrase"] # クライアントIDを作成した際に表示された秘密キーのパスワード config.key_file = ["key_file"] # p12ファイルのpath config.scope = "https://www.googleapis.com/auth/bigquery" config.email = ["email"] # クライアントIDを作成した際に一緒に作成されたEmailアドレス config.retries = [retries] # 任意のretryカウント end
config/apprication.rbの編集
RailsアプリケーションがBigBrodaを認識するようapprication.rbに以下を追加します
Bundler.require(*Rails.groups) require "google_bigquery" # これを追加
database.ymlの編集
database.ymlにbigqueryへの接続情報を追加します
bigquery: database: 'DATABASE_NAME' adapter: 'bigquery' project: API_PROJECT_ID
Modelの作成
bigquery用のmodelを作成します
ここではbigquery上に'users'というテーブルがある想定でmodelを作成します
class User < ActiveRecord::Base establish_bq_connection "bigquery" end
これで準備OK
確認
pry-railsが使えるという前提で、REPLで確認してみます
$ rails c
[12] pry(main)> User.all.pluck(:id) (3176.0ms) SELECT testdata.users.id FROM testdata.users => ["1", "2"]
ちゃんと結果が取得できたらOKです!
Papeclipで画像ファイル向け汎用メソッドを定義するサンプル[Ruby]
paperclipでは画像ファイル以外も添付できる。 気をつけないと画像ファイル以外のObjectからも呼ばれる危険性がある。
画像ファイルに限定したUtilityメソッドを安全に定義するための例として以下の様な実装を考えてみた。
module ImageFileDecorator extend ActiveSupport::Concern module ClassMethods # Public # # Paperclipによって添付されたimageファイル向けのUtilityメソッドを追加するメソッド # # returns/raises section def activate_image_file_utils *images images.each{|image| # 画像ファイルの横幅、縦幅の配列を取得するメソッドを定義 define_method("#{image}_dimensions") { tmp_file = send(image).queued_for_write[:original] if tmp_file.present? geometry = Paperclip::Geometry.from_file(tmp_file) [geometry.width.to_i, geometry.height.to_i] end } # ... } end end end class SomeModle < ActiveRecord::Base # attributes # - iamge_file_name # - image_content_type # - image_file_size # - image_updated_at # - pdf_file_name # - pdf_content_type # - pdf_file_size # - pdf_updated_at has_attached_file :image, :pdf activate_image_file_utils :image end some_model = SomeModel.find_by(id: 1) some_model.respond_to?(:image_dimensions) # => true some_model.respond_to?(:pdf_dimensions) # => false
うーん、他に良い解法がありそうな気もする