のぶLab.

流しのソフトウェアエンジニアの雑記帳. Android, Scala, Clojure, Ruby on Railsなど

DroidKaigi 2017に参加しました [Day2]

DroidKaigi 2017の2日目(3/10)に(途中から)参加しました。 (1日目の記録はこちら)

ここでは私が聴いた講演のメモやら感想やらを載せます。

位置情報を正確にトラッキングする技術

※ 資料が公開されたらリンク貼ります。

位置情報トラッキングするノウハウの紹介。

コードから位置情報取得設定が可能。基地局情報を使用しないトラッキング方法だと精度が落ちるのでなるべく使うべき。

位置情報トラッキング中に障害物の遮蔽により精度が出なかったりAndroid OSでキャッシュした古い位置情報が返却されたりする。そのようなノイズを抑えるためにある閾値を超えた返却値は無視したり、KalmanFilterというFilteringを通すと上手く位置情報がトラッキングできる。品質もよく使われているアプリと遜色ない。

Error Handling in RxJava

speakerdeck.com

アプリ開発時によくあるユースケースをRxJavaのエラーハンドリングでどのように扱うかの紹介。講演の大半がエラーハンドリング系のAPIの説明に費やされてしまってたので"うーん。。。"と思ったのが正直なところ。

Rxは鉄道思考プログラミングのような表現が可能だと考えている。このように表現できるとユースケース単位で成功Pathと失敗Pathに分離され、それぞれのPathに集中できるので例外設計がやりやすくなったりするのかなーと思っている。そのあたりのヒントがあればと期待していたので少し残念。

テスト0から目指すクラッシュフリー率99%

speakerdeck.com

テストのない状況からソフトウェアを安定動作させるために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

y-anz-m.blogspot.jp

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 ~バイトコードを読み解く~

speakerdeck.com

Kotlinプラグインを使えばKotlinコードをJavaデコンパイル可能。Kotlinのコードを理解するためにJavaデコンパイルしてどんなことをしているのか解読する。

null許容型は変数がnullになる領域の示している。nullの領域を狭めて非null領域を広くとることでnull安全なソフトウェアになる。

拡張関数を使って既存のクラスでもそのクラスが持っておいたほうが良い責務を後付で持たせることができる。

変更に強いEspressoテストコードを効率良く書こう

speakerdeck.com

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とは

www.javaslang.io

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になるみたいです。

stackoverflow.com

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の場合)

  1. XCodeを開き Preferences > Components > ToolchainsXcode Swift 2.2 Snapshot XXXXを選択
  2. パスを通す
    $ export PATH=/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:"${PATH}"

Kituraアプリケーションの作成

  1. 適当なところに新しいディレクトリを作成

    $ mkdir myFirstProject

  2. Swift projectのinitialize

    $ cd myFirstProject
    $ swift build --init
    

  3. 以下のようなディレクトリ構成になっているかと思います
    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するまで

環境はMacOS X Yosemite

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の導入

github.com

こいつを導入します

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するとエラーが解消され、問題なくアプリケーションが動くようになっているはず。

参考

clojure - Heroku Deployment Error w/ Compojure: "Could not locate leiningen/core/classpath__init.class" - Stack Overflow

ActiveRecordでBigqueryを使ってみる

目的

メインDB + Bigqueryの複数DBなRailsアプリケーションでActiveRecordからBigqueryにあるテーブルを参照するまでの手順。

前提として、Bigquery上のtable, recordの作成などは行っていることとします。

ここではローカル環境や外部サーバ上のRailsアプリケーションからそのテーブルを参照できるようにしていきます。

GCP Projectの作成

ここからGoogle Cloud Platformのプロジェクトを作成する

https://console.developers.google.com/project

クライアントIDの作成

  1. 作成したprojectを選択し、"APIと認証" > "認証情報"を選択する
  2. "新しいクライアントIDを作成"を押すとクライアントID作成用のポップアップが表示される
  3. "アプリケーションの種類"に"サービスアカウント"を選択
  4. "キーのタイプ"に"P12 key"を選択
  5. この状態でIDを作成
  6. IDが作成されたら秘密キーのパスワードが表示されるので控えておく(これはあとで使う)
  7. p12ファイルがダウンロードされる(これもあとで使う)
  8. サービスアカウントに新しいクライアントIDが作成されていることを確認
  9. 同時にメールアドレスも作成されていることも確認(これもあとで使う)

Gemのインストール

ActiveRecordでBigqueryを使うためのGemにBigBrodaというものがあるので、それを使う

michelson/BigBroda · GitHub

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

うーん、他に良い解法がありそうな気もする