yo_waka's blog

418 I'm a teapot

volley(サブプロジェクト)のbuildToolsVersionをafterEvaluateで上書く

Android StudioがBetaになったので、0.8.2に上げようとしたらモジュールのビルドでハマった。

Android Studioのバージョンを上げるときは、build.gradleを弄る時でもある。 Betaに上げるからには最新版のGradleプラグインAndroid SDKコンパイル&ビルドできるようにしたい。

// project/gradle/gradle-wrapper.properties
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip

// project/build.gradle
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:0.12.+'
  }
}

// project/app/build.gradle
android {
  compileSdkVersion 20
  buildToolsVersion '20.0.0'
}

しかしビルドエラー。
ルートプロジェクトのGradleプラグインのバージョンを0.12に上げると、buildToolsVersionが"19.1"以上でないとビルドできない。
僕の環境ではvolleyをモジュール(submodule)として組み込んでいて、compileSdkVersionとbuildToolsVersionがこのように指定されている。

// modules/volley/build.gradle
android {
  compileSdkVersion 19
  buildToolsVersion = 19
}

volleyのソースを見る限りbuildToolsVersionを20に上げても特に問題なさそうなので、何とかしてビルドが実行される前にandroid()の中身を上書きしたい。
と思って、Gradle User Guideを眺めていたら、Project.afterEvaluate()というものを見つけた。 Project.afterEvaluate()は、そのプロジェクトのビルドスクリプトが評価された後に実行されるらしい。まさにやりたいことと一致!

Gradleで分からないことがあれば、Gradle User Guideを見るのがオススメ。 Gradleのバージョンごとに用意されているので、使っているものに合わせて見るとよさげ(ちょくちょく変わったりするので)。

subprojects { subproject ->
  afterEvaluate {
    if (subproject.plugins.hasPlugin('android-library')) {
      android {
        compileSdkVersion 20
        buildToolsVersion '20.0.0'
      }
    }
  }
}

これで、モジュールとして組み込んでいるライブラリプロジェクト全てのcompileSdkVersionとbuildToolsVersionをアプリのそれと合わせることができる。
もし特定のプロジェクトだけどうしても"19.1"でビルドしたければ、プロジェクトごとに指定すればおk。

project(':modules:volley') {
  afterEvaluate {
    android {
      compileSdkVersion 20
      buildToolsVersion '19.1'
    }
  }
}

Android meets RxJava

少し、いやかなり前に渋谷Javaで「Android meets RxJava」というタイトルでLTしてきました。 スライド上げるのが遅くなってすいません。。。

freeeのAndroidアプリの開発前にチーム内で考えていたのが、テストの書きやすさを考慮するとどうしてもFragmentとAPIのやりとり含むビジネスロジックを切り分けたいというところで、 ViewController/ViewModel/Modelを上手く疎に分けられる仕組みが必要でした。

先行して開発していたiPhone版では、ReactiveCocoaを導入して上手くいったこともあり、FRPが出来るJavaのいいライブラリはないか探していたところ、上手くマッチしそうだったのがRxJavaでした。
RxJavaのObservable、Subscriber、Func/Actionを使うことで、API呼び出し/モデルへの変換/画面への表示を上手く切り分けることが可能になります。 また、ViewModelのプロパティをFragmentからバインディングすることにより、データの状態をFragment側で管理する必要がなくなります。

Fragment側でデータの状態を持ってしまうと、いざそのテストを書く際にUIが必要になるので非常にめんどくさい。ViewModelまでで完結できればユニットテストだけでOK。 とはいえ、スライドにも書いてますが、ビジネスロジックがそこまで複雑でなければEventBusなどでやり取りするのもアリだと思います。

こういうFRPなライブラリをクライアントアプリで使うと、コアな部分で使うためどうしてもロックインを防げないのがデメリットです。 Reactive Streamsによる標準化に期待。

Hubotを導入したらレビューの敷居が下がった話

ウチの会社ではHipchatとGitHubを開発のコミュニケーションの中心にしている。
だんだん人も増えてくると、以前よりプルリクの数がそれだけ増えて、レビューで1日終わってしまう人がでてきた。

昔から仕様を知っている人にレビューが投げられがちで集中しやすいとかは他の会社でもよくある話しだと思う。
レビューは自分のタスクと同様に大事だけど、それで自分のタスクが全くできなくなったり、新しく入ってきた人がレビューする機会を失うのはあまりよくない。

というのもあって、Hubotを立ててみてプルリクのレビュアーをランダムで振れるようにしてみた。
Hubotというのはご存知Hipchatのbotとして動くプログラムで、botにコマンドを指定してリモート実行させたり、特定の文字列に反応させたりということがHipchat上でできる。
CoffeeScriptスクリプト書けるのでとてもお手軽。

sushiという名前のHubotを立てて、GitHubのプルリクのURLをbotに投げるとランダムでレビューお願いします!とメンションを飛ばすようにしてみた。

xxx   | @sushi https://github.com/AAA/BBB/pull/999
sushi | @yyy オナシャス! https://github.com/AAA/BBB/pull/999

GitHubから流れてくるログから自動でメンションを飛ばさないのは、大きな機能追加などは特定の人にレビューしてもらいたいことがあるので、そこは自分で打つ。assignまで自動でできる美しいんだけどそこまではまだ出来てない。
誰にレビューしてもらおうかなと考える必要もないし、レビューの偏りも結構解消されていい感じになった。
スタートアップはどんどん人が増えるので、新しく入った人にもレビューが振られることで早くプロジェクトになじめるとかそういうのは大きいと思う。

何より、みんなが楽しんでレビューを振れるようになったのがよかった。
pushされたブランチを見て、「俺に来るな、来るなよー・・来たーマジかorz・・」とか振られる側もドキドキできて楽しい。

ランダムレビュー以外にも、みんなの名前打ったりするとその人のおもしろ発言が流れたり楽しい感じになってる。

これまで自分のローカルで動かしてたのでw ちょっと旅行に行ったときは動いてなくて寂しかったと言ってもらえたのは嬉しかった。
普段の開発フローに定着できたので、ちゃんと運用していこうと思う。

方針を決めて守ったり対面のミーティングでなんとかしていけることもあるけど、動く仕組みを作ることで一段上の解決ができることもあるよなーと1つ勉強になりました。

WEB+DB PRESS Vol.79にAngularJSの記事を書きました

2/22発売のWEB+DB PRESS Vol.79JavaScript連載で「AngularJS」をテーマに記事を書きました。

こんな感じのことを書いてます。

  • AngularJSについて
  • AngularJSの主な機能
  • AngularJSアプリケーションのファイル構成

AngularJSを使って本格的にアプリケーションを作っていくのに最低限必要な要素について解説しています。
AngularJS触ってみようかなーとか、とりあえずHelloWorldしてみたけどどういう機能があるのかいまいち分からないなー。
といった人のお役に立てれば幸いです。

以下、短いですけど執筆後記です。

2年間続いた「JavaScript活用最前線 -大規模開発の現場から-」は今号で最終回となります。
ちょうど2年ほど前に、現場で使えるJavaScriptネタで連載を書かないかという話をいただいて、最初は僕とid:ama-chの2人で書く予定だったのですが、僕が転職することになったため、急遽同期のid:teppeisにお願いすることになったという経緯があります。
記事のやり取りをしていたサイボウズLiveを見ていて、申し訳ないなという気持ちもあったり。
そんな中、連載が2年目に入ったのと、ちょうど今の会社に転職して個人で執筆できるようになったのもあって、2年目の連載は3人でローテーションして書いてきました。
業務をこなしながら8ページ分の記事を書くのは、業務のタイミング次第では本当に大変でしたが、非常にいい経験ができたと思っています。
id:teppeisid:ama-ch、2年間お疲れさまでした!

iOSアプリの全てのビューコントローラーにGoogleAnalyticsを一括で設定する

今作っているアプリで、改善のためにどれくらい画面が使われているか知りたかったので、GoogleAnalyticsを入れたときのメモ。

GoogleAnalyticsはご存知みんな知っているアクセス解析ツール
iOS用にもSDKが公開されていて、CocoaPodsを使っていればpod installで簡単に入れられる。

pod 'GoogleAnalytics-iOS-SDK', '~> 3.0'

画面の閲覧回数を取るためには、2つやり方がある。

1つは、GAITrackedViewControllerクラスを継承したUIViewControllerを作る。
viewDidLoadなどでscreenNameに画面名をセットしておくと、viewDidAppearで自動でトラッキングリクエストが送信される。

@interface SampleViewController : GAITrackedViewController
@end

@implementation SampleViewController

- (void)viewDidLoad
{
    self.screenName = @"画面名";
}

end

もう1つは、ビューコントローラーのviewDidLoadかviewDidAppearで、GAITrackerクラスを使って画面名を送ってやるやり方。
こっちはWebブラウザ版の使い方に近い。
UITableViewControllerなどUIViewControllerのサブクラスを使っている場合は、こっちでやるしかない。

- (void)viewDidAppear
{
    [super viewDidAppear];

    id tracker = [[GAI sharedInstance] defaultTracker];
    [tracker set:kGAIScreenName value:@"画面名"];
    [tracker send:[[GAIDictionaryBuilder createAppView] build]];
}

つまり、UITableViewControllerをふんだんに使っていたり、UIViewControllerを継承したベースクラスを作っていると、1つ1つのビューに同じ処理を書かないといけない。
これは絶対入れるの忘れそうなのでなんとかしたい。。。と思って調べてみた。

Objective-CにはMethod Swizzlingという、すでに実装されているクラスのメソッドを自前のメソッドに入れ替えるやり方が用意されているらしい。
"objc/runtime.h"が提供している、method_exchangeImplementations関数を使えばクラスメソッドの入れ替えが可能になる。

これを使ってUIViewControllerのメソッドを入れ替えれば各画面ごとにアナリティクス処理を書かずに済みそう。
つまり、UIViewControllerのカテゴリ拡張を作って、viewDidAppearをGATrackerの処理を追加してものに入れ替える関数を用意する。
画面名には「NSStringFromClass([self class])」でクラス名を自動でセットしてやる。

#import <objc/runtime.h>

@implementation UIViewController (GAInject)

- (void)replacedViewDidAppear:(BOOL)animated
{
    // 元のメソッド(名前は既に置き換わっているので注意)を呼び出す
    [self replacedViewDidAppear:animated];
              
    [[GAI sharedInstance].defaultTracker set:kGAIScreenName value:NSStringFromClass([self class])];
    [[GAI sharedInstance].defaultTracker send:[[GAIDictionaryBuilder createAppView] build]];
}

+ (void)exchangeMethod
{
    [self exchangeInstanceMethodFrom:@selector(viewDidAppear:) to:@selector(replacedViewDidAppear:)];
}

/**
 メソッドの入れ替え
  */
+ (void)exchangeInstanceMethodFrom:(SEL)from to:(SEL)to
{
    Method fromMethod = class_getInstanceMethod(self, from);
    Method toMethod   = class_getInstanceMethod(self, to);
    method_exchangeImplementations(fromMethod, toMethod);
}

@end

AppDelegateでこいつを呼び出してUIViewControllerのviewDidAppear関数を入れ替える。

#import "UIViewController+GAInject.h"

@implementation SampleAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // UIViewControllerのメソッド差し替え
    [UIViewController exchangeMethod];
}

@end

これで、UIViewControllerを継承しているUITableViewControllerや自作ビューコントローラーでも、自動でトラッキング処理が走るようになります。
method_exchangeimplementations、Rubyのalias_method感覚で使えるヒッジョーに面白い仕組みですが、そのクラスと子クラスすべての挙動が変わるので使いどころには要注意ですね。