ボクココ

熱海で開発するブログ

Android の ViewPager で使う Fragment の注意点

Android アプリ開発で便利な ViewPager . 画面のスワイプが簡単に実装できるのでどんなアプリを作るにも役立つ。

これはFragmentを使っていて、今回そのFragment周りを中心にどう実装するのかをまとめる。

ViewPager に FragmentStatePagerAdapter を setAdapter

これがまず基本。 FragmentStatePagerAdapter を継承したクラスを作り、

   @Override
    public Fragment getItem(int i) {
    }

ここに作った各Fragmentを返すように作ってあげる。 詳細は他に任せるとして、今回はこのFragment のライフサイクル的なのをまとめたい。

ライフサイクルの使い分け

まずは典型? と思われるコードを書く。

public class BoardFragment extends Fragment {
    private static BoardFragment boardFragment;

    public static BoardFragment getInstance() {
        if (boardFragment == null) {
            boardFragment = new BoardFragment();
        }
        return boardFragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_board, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // 1
    }

    @Override
    public void setMenuVisibility(final boolean visible) {
        super.setMenuVisibility(visible);
        if (visible && isAdded()) {
            // 2
        }
    }
}

getInstance()

シングルトンにするケースが多いみたい。

onCreateView

ここで使うレイアウトを指定する。

onViewCreated

ここで view.findViewById で各要素が取って来れる。 この処理内での呼び出しであれば、 getActivity() が問題なく取って来れる。 自分はActivityでいうonCreate に該当する処理をここで書いている。

setMenuVisibility

今回の落とし穴。 自分はここで onResume() 的な処理を書いていた。例えば2つのFragmentA, FragmentBがあったとして、FragmentAの値を更新した後にそのままFragmentBに移ったときに FragmentAの更新した内容をFragmentBでも反映させる必要があった場合など使える。 注意点としてisAdded() は、 Activityが対象Fragment をアタッチしたかどうかを返すので、falseだったら setMenuVisibility内の getActivity() の呼び出しは null が返ってくる。ここのifは重要。

んで、 View の処理を setMenuVisibilityだけに書くと、このisAdded()がfalseを返したときに処理が何も走らない、ということが起きる場合がある。

これを回避するためにも、Viewの処理、特にgetActivity()でアクティビティやコンテキストが欲しい場合はonViewCreated にその処理を書かないといけない。

所感

Fragment は難しい。。 Fragment に関する本とかが出始めているようだ。 これからのAndroidアプリ開発にFragmentの知識は切っても切れないので頑張って学んでいくしかない。

rails-api は デフォルトの middleware に要注意

いや〜ハマったハマった。 rails-api っていうフルスタックのRailsからAPIに特化した構成に削ぎ落としてくれるGemがあるんだけど、これで「何が削られているのか」をちゃんと把握してないとハマる。

基本的なRailsアプリとrails-api の Middleware 比較

rake タスクを実行すれば見れる。

フルスタック

$ bundle exec rake middleware

use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fe29f9cc6e0>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use BetterErrors::Middleware
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run MyApp::Application.routes

rails-api

use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fac42102e08>
use Rack::Runtime
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run MyApp::Application.routes

今回ハマったのは use Rack::MethodOverride。これは putとかpatchとかのリクエスト送るときにformのmethodは"post”にしておいて、hiddenパラメータで_method="patch"ってのを指定することで擬似的にPATCHリクエストを実現するための仕組み。 なんと、rails-apiではデフォルトではこのRack::MethodOverrideが搭載されていないのだ。

てことで config/application.rb に

    config.middleware.use Rack::MethodOverride

を追加して解決した。

今回自分がやりたかったのは、作ったAPIを実験するためのhtml集を作っていて(もちろんテストコードも書いてます)、そこで _methodをpatchにしても全部 post として解釈されてしまって3時間くらいハマった。

ソースコードで _method でgrepしてrails コアの該当箇所を見つけて原因を特定して頑張った。 一件落着。

Rails の bundler でなぜ --path vendor/bundle を付けるのか

今までずっと謎だった。

bundle install

これだけでいいじゃないか、なんでわざわざ --path を付ける必要があるのか、と。

何が嬉しいか

gem をいじっても他のRailsアプリを汚さない
ctagによる検索が可能になる

ctagの存在を知らなかった。。今まで自分をvimmer だと思っていたのが恥ずかしくなるくらい素晴らしい機能。これはいわばEclipseでいうCtrl + クリックでの移動。
Ruby だししょうがないか、と思って今までメソッドとかGrepで検索してましたw

これをRailsアプリ内に置くことで、Gem内のクラスにも移動できるようになる!

 手順

brew install ctags
vim ~/.zshrc
  alias ctags="`brew --prefix`/bin/ctags”
source ~/.zshrc
ctags --langmap=RUBY:.rb --exclude="*.js"  --exclude=".git*" -R .
bundle install --path vendor/bundle

これで
<CTRL-]> 定義元にジャンプ
<CTRL-T> 戻る
ができるようになる。

しかも、 --path vendor/bundle しているおかげで、Gemの方にも移動ができちゃう!素晴らしい・・・。もしかしてこれってかなり当たり前なことだったのかも。

alpaca-tc/alpaca_tagsってので自動でctagを付けてくれるのがあって、それをやろうと思ったんだけど、

Error detected while processing function alpaca_tags#create_tags#update..alpaca_tags#util#system:
line   10:
E117: Unknown function: vimproc#popen2
E116: Invalid arguments for function 10
E15: Invalid expression: s:Watch.new(vimproc#popen2(command), a:message)

こんなエラーが出たから諦めたw とりあえずは bundle install とか何らかのタイミングで ctags コマンド打つようにすればいいや。

Railsのテスト,デプロイ,ドキュメント生成をBitbucket, Jenkins で行う

今回はJenkinsとBitbucket の連携をします。
Bitbucket はプライベートリポジトリを何個でも作れて、5人までなら無料で使えるという優れもの。少人数開発ならこれを使わない手はないです。 Github Enterprise だとお金かかる部分が浮きます。 さらに! Wiki 機能もあり、今回はここに自動生成したドキュメントを反映できるようにします。

またJenkinsはどっかのリモートに置くとそれだけでお金がかかるし、無料のJenkins ホスティングサービスは柔軟性が無いので使いません。その代わりにしばらくはMacのローカルでJenkinsサーバを立てて運用していきます。

ローカルでJenkinsを立てると、Bitbucketへのフックができなくなるので、git push した瞬間にJenkinsを走らせる、みたいなことはできないのでご注意を。

やりたいこと

Git push したら Jenkins で以下の手順を実施する

  • テストの実行
  • 全て通ったらHerokuにアプリケーションをデプロイ
  • 成功したらそのドキュメントをBitbucket に更新

これにより、毎回デプロイやドキュメント更新をコマンドで打つ必要が無くなり、手間が減る。そして開発により専念することができるようになる。

主要ポイント

細かい設定とかは他のサイトに色々情報が載ってるので、大まかな部分だけ解説。

RSpec によるテスト

基本的にShellでゴリゴリ書いていきます。 Jenkins に rake plugin とかあるけど、ローカルで既に入れてあるし、わざわざまたそのプラグインから入れるのも大げさな気がしたので使わないことにした。

シェルの実行で以下のコマンドを叩きます。

COVERAGE=true AUTODOC=1 bundle exec rake spec

COVERAGEとAUTODOCについてちょっと解説。

カバレッジの出力

Jenkins でテストがどのくらい通ってるのかを一覧で見れます。これは Rails で Jenknsを回すなら是非入れたい所。

  gem 'simplecov', '~> 0.7.1'
  gem 'simplecov-rcov'

spec_helper.rb に色々追加して、Jenkinsにrcov pluginを入れてビルド後の処理でcoverage/rcovを指定すればOK.

自動ドキュメント生成

これは結構すごい。

  gem "autodoc"

spec_helper に設定をちょちょっと書いて、RSpecの itに autodoc: true を指定する。そして specを流すだけで APIのサンプルrequest と responseの .md ファイルができあがる。

doc/api というディレクトリが出来るので、後でこれをBitbucketのwikiに載せる。 toc.md を作ったほうがいい。これは目次を作ってくれるんだけど,autodocはデフォルトでは作らないようになってる。詳細はautodocのドキュメント参照。

Heroku にプッシュ

ここはHerokuのドキュメント見ながら git push heroku master までこぎつける。
自分のアプリではHeroku用の設定(gemfileにrails_12factorを追加したり)する必要があったけど、それもシェルでゴリゴリ書いた。

Wiki にアップ

Bitbucket にはWiki 機能があって、これも一つのGitリポジトリとなっている。そのため、その Wiki を git clone して自動生成した .md ファイルを追加して push するとなんと Bitbucket 上でリンク付きのAPIドキュメントができちゃう!

シェルはこんな感じ

git clone https://{{your account}}@bitbucket.org/hogehoge.git/wiki

# 古いwikiを削除
rm -rf wiki/api

# 新しく作ったwiki をコピー
cp -r doc/* wiki/

cd wiki

git add -A
git commit -a -m "added wiki" || pwd
git push origin master || pwd

cd ..
rm -rf wiki

|| pwd してるのは、そこでexit code が0以外になっちゃうとJenkinsが失敗したと見なして以降の処理をしてくれなっくなってしまうので、それの対応。 もっといいシェルは書けると思うので、参考程度で。。

Bitbucket のWiki のホームを作って、そこに [API Rquest Response](./api/toc.md) のようなリンクを作れば完了。

Doorkeeper を使ったRailsのテストをRequest Spec で作る

今回はちょいとマニアック。

問題

APIのテストはController Specではなく、Request Specに書くべきなのだが、DoorkeeperのサンプルコードはController Specで書かれている。こんな感じのコード

describe Api::V1::ProfilesController do
  describe 'GET #index' do
    let(:token) { double :accessible? => true }

    before do
      controller.stub(:doorkeeper_token) { token }
    end

    it 'responds with 200' do
      get :index, :format => :json
      response.status.should eq(200)
    end
  end
end

何も気にせず、そのまま書くとRequest Specの中でcontrollerを呼んじゃっているので、ちゃんとスタブ化されずに401が返ってきてしまう。 てことでこのスタブを違う方法で利用して動作するようにしないといけない。

解決

指定したコントローラのインスタンスのスタブを作るようにする。

  describe 'show user' do
    let(:path) { '/v1/users/' }
 
   it 'should show user' do
      user = FactoryGirl.create(:user, :valid_mail, :same_pass)
      token = double(accessible?: true, resource_owner_id: user._id.to_s)
      V1::UsersController.any_instance.stub(:doorkeeper_token) { token }

      get path
      res = JSON.parse(response.body)
      response.status.should eq(200)
      expect(res["item"]["email"]).to eq("test1@gmail.com")
    end
  end

ポイントはV1::UsersController.any_instance.stub(:doorkeeper_token)の部分。ここでRequest SpecでもControllerをスタブ化することが出来るようになる。

users/show ではcurrent_user の情報を出すだけのコードになっている。 上記のテストはそのcurrent_user を直前でFactoryGirt.createしたものとしてちゃんと取って来れるかっていうテストになる。

ひとこと

FactoryGirl をちゃんとまともに使うようになったけど、これ便利ね。 ちなみに自分のRails API プロジェクトでは versionist ってgemも使ってる。これのジェネレータがなかなか良い。

ユーザ登録・API 認証の仕組みを Rails で実現する

スマホアプリから会員の新規登録、ログインが両方できるようにAPIを作成中。ようやく自前でアクセストークンを作ってOAuth認証が出来たのでまとめておく。

まず何がしたいか?

  • スマホアプリでAPI認証ができるように、OAuthを自前で作成したい。

-> スマホアプリ側ではユーザ名とパスワードを入力すればトークンが取って来れて、そのトークンで各APIにアクセスすればユーザ固有の情報が取って来れるようになる仕組みを作る。

  • スマホアプリ側でユーザ作成も出来るようにしたい。

-> APIでユーザが作れるようにする。もちろんhttps前提。

環境

gemfile

ruby '2.0.0'
gem 'rails', '4.0.0'

gem 'rails-api'
gem 'active_model_serializers'

gem 'mongoid', '4.0.0.alpha1'
gem "moped", '2.0.0.beta6'

gem 'sorcery'
gem 'rack-cors', :require => 'rack/cors'

gem 'doorkeeper'

認証ではDeviseではなく、Sorcery を。理由は Rails の認証で Devise ではなく Sorcery という選択 - ボクココ

ORM は ActiveRecord ではなく Mongoid を利用。

Sorcery と Doorkeeper のセットアップ

ここら辺はもうマニュアル通りで。前章のGemfile構成なら特に問題なくいけるはず。

rails g mongoid:config
rails g sorcery:install
rails g doorkeeper:install

config/initializers/doorkeeper.rb

doorkeeper で使用するORM と認証方法を指定。今回はdoorkeeperのpassword token で認証するので、以下のような感じでセット。

  orm :mongoid4
  resource_owner_from_credentials do |routes|
    User.authenticate(params[:username], params[:password])
  end

config/application.rb

Moped::BSONが undefined だよっていちいち言ってくるので、以下のように修正

Bundler.require(*Rails.groups)

# @see https://github.com/mongoid/mongoid/issues/3455
Moped::BSON = BSON

API でユーザ作成

通常はHTMLでユーザを作るけど、SorceryならAPIで簡単にユーザ作成できる! app/controllers/users_controller.rb

class UsersController < ApplicationController
  doorkeeper_for :show

  def create
    @user = User.new(user_params)
    if @user.save
      head :created
    else
      head :bad_request
    end
  end

  def show
    render json: current_user
  end

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

これで localhost:9000/users にpost で users[email], users[password], users[password_confirmation] を送れば作れる。

Doorkeeper の関門

ここが最大の難所。 まずちょっと解説する。 Doorkeeper で OAuth 認証をする訳だが、Doorkeeper には Application って概念があって、これは例えば MyAppWeb, MyAppAndroid, MyAppiPhone みたいに分けたり、サードパーティAPIを提供したりで Application を作る分ける必要がある。それの id と secret とユーザのid, password をセットで送ることでアクセストークンが取得できる。

ここで問題は、DoorkeeperはこのApplicationの作成をHTMLベースで作ること前提でしか作られていない、という点だ。 doorkeeper のソースを見ると、 app/controllers/doorkeeper/applications_controller.rb あたりに書いてある。同様にViewも提供されているので、普通なら問題なく作れる。

ただ、今回はrails-apirailsアプリを作っているため、レンダリングの仕組みはrequireしていない。もちろんそこで妥協するのも手ではあるが、それではかっこわるすぎる。てことでなんとかAPIでapplicationを作れるようにしなきゃならない。

Doorkeeper 内の applications_controller.rb を再オープン

問題となっているこいつを再定義してやる。具体的には app/controllers/doorkeeper/applications_controller.rb を作成。

module Doorkeeper
  class ApplicationsController < Doorkeeper::ApplicationController
    respond_to :json

    before_filter :authenticate_admin!
    before_filter :set_application, :only => [:show, :edit, :update, :destroy]

    def index
      @applications = Application.all
      render json: @applications
    end

    def create
      @application = Application.new(application_params)
      if @application.save
        render json: {status: "created"}
      else
        render json: {status: "failed"}
      end
    end

 ~~~~~~
  end
end

これで、http://localhost:3000/oauth/applications あてにpost でapplication[name]、application[redirect_uri] を含めて送れば登録できる!

アクセストークン取得

これさえクリアすればゴールは近い。

http://localhost:3000/oauth/token.json へ post で grant_type=password, client_id, client_secret, username, password を入れてやればAccessTokenが取得できる。

取得したアクセストークンで、 http://localhost:3000/users/ にGET で access_tokenパラメータを付けてやれば、 doorkeeper_for を通り抜け、current_user が取って来れるようになる。

あ、ちなみに app/controllers/application_controller.rb はこうする。

class ApplicationController < ActionController::API
  def current_user
    @current_user ||= User.find_by_id(doorkeeper_token.resource_owner_id) if doorkeeper_token
  end
end

この先

まずテストコード書くか。そしてJenkins環境を作る。そんでもってJenkinsからHerokuへ自動デプロイするようにして、 vagrantで作ったvmに cap deploy できるようにして本番デプロイ(AWS or さくらVPS)をできるようにする。

そしたらアプリ固有の実装に突入!!

熱海へ引っ越し。これからは田舎で開発すべき

今日、東京のど真ん中から熱海に引っ越した。 ここで、自分の事業を始めていく。毎回「なんで熱海なの」って聞かれるので、今回はその経緯と理由について書いておく。

熱海でスタートアップをすると決めた経緯

まずは親がここにリゾートを持っていたから、ってのがある。これを言った時点で、ああ金持ちか、って思われるかもしれないけど、今の熱海のリゾートマンションって激安で、150万くらいから買える。車買ってる人の方がよっぽど金持ちだ。 そして月にかかるのは電気代と管理費合わせても3万ちょいくらい。生活するのにめちゃめちゃお得。
後は後述するけど、東京はうるさいしもっとリラックスできる所でプログラミングしたかったってのがある。そういった意味でここでスタートアップをしようと決意した。

熱海にして良いと思った理由

1、温泉に入り放題 リラックスという意味でこれはかなり自分にとって良い点だ。肩までゆっくり毎日温泉に入れる。足や腕をのばしてストレッチが出来る。こんなことを東京でできるだろうか?さらにガス代はかからないし水道代も浮くし、節約にもなる。

2、海に近い これも大きい。自分はよく走るのだけど、朝日を浴びながら海辺を走るのが最高に気持ちいい。その後に温泉はいってからのプログラミングをすると頭が冴えて効率が上がる。

3、余計な予定が入らない 東京にいないというだけで、なかなか会うことができないので、それが逆に集中できる環境を生む。東京ならどっかの勉強会いこっかな、とかどっかのミーティング参加しないとな、とかそんな余計な思いが生まれて本業のプログラミングができない理由がたくさんできてしまう。それらを完全に遮断するにはそこから離れるのがベスト。

東京で始めなくて大丈夫なのか

まぁこの点はプログラミングで食ってる人ならわかると思うけど、同じ場所で働く必要なんてほとんどなくなってきている。会いたければSkype使って話せば良いし、仕事上の成果はGithubで管理すれば良いし、進捗もプロジェクト管理ツールを使えば良いだけだ。その他会社でやらなきゃいけないことなんてもはやない。 むしろ余計なMTGや会話が発生する分、行くだけ無駄。

外部の人など、本当に会わないといけない時はさすがに東京にいないと不便だけど、そういった状況にはまだなっていない。

それでも一緒の場所で働かないのは無理だって思ってる人はこの本を読めば考えが変わるかもしれない。

そんなこんなでこれから生活していきまっす〜。