AWSとGAEを比較してみた。

やりたいこと

アプリのバックエンドとしてAPIを提供するサーバーを構築したい。

ウェブアプリケーションへの拡張の可能性も高いと思うので、Scala & Play Frameworkで構築しようと思う。
データベースにApache等HTTPサーバー、Javaコンテナにローテーションバックアップ、動作監視等のスクリプトあたりが必要?
VPSに環境構築するのが安く済みそうではあるが、スタートアップなのでどのくらいの負荷がかかるかは分からない。
理想的には少しずつユーザーが増えて増えてスケールアウトしていく、、、、というイメージなので、VPSでは環境の載せ替えや、サーバーウェアのチューニングなどの業務にかなり手間を割かれそうな予感がする。
ということで少なくとも最初はPaaSでの運用をしたほうが地雷が少なさそう。
候補としてはAWSとGoogleCloudPlatform(GCP)が使いたいなーーということで比較してみた。

 

それぞれ必要な構成

AWS: ElasticBeanstalk(EB) & Amazon Relational Database Service (RDS) & S3
GCP: AppEngine(GAE) & Google Cloud SQL & Cloud Storage

AWSではEBを使ってJavaのアプリケーションをホストして、staticなファイルはS3においておく感じ。
GCPではGAEにホスト、StaticファイルはCloudStorage。
データベースはどっちでも機能的に不足はなさそう。

他の様々な”便利”なサービスを利用しないんであれば、どちらでもできることはほとんどかわらなさそう。

 

PaaSの比較(AWS EB vs GCP GAE)

EBはEC2の上で動くサービスなのでVM上にマネジメントされた環境を構築して、スケールするときはVMを新しく起動する形。
EBはEC2を使ったマネジメントシステムということなので、EC2でできることなら柔軟に色々できそう。
GAEではVMはつかわずアプリケーションコンテナのプロセスを起動する形なので、スケールアウトの時間がミリ秒レベルの速さになるよう。
ただし、VMがないので、SSH接続もファイルの書き込みもできないなどの制限がある。(ファイルはCloudStorageに保存すれば良さそうだけど)

料金は
GAE:0ドル (1インスタンス) : 30ドル(2インスタンス) : 322ドル(10インスタンス)
AWS:20ドル弱 (1インスタンス) : 38ドル(2インスタンス) : 190ドル(10インスタンス)

GAEのほうは無料枠がついてるのでインスタンス一つで設定すると無料になる!
その場合GAE送信転送量のみに課金される

インスタンスが少ない場合はGAEのほうが安いがインスタンスが多くなるとAWSのほうが安くなるようだ
AWSでインスタンスを10にするような場合はインスタンスの種類を買えるほうが良いかもしれないが、、、、

 

DBの比較(AWS RDS vs GCP Cloud SQL)

レスポンスはそんなに大きな差はなさそう。使い勝手に差も対してないだろうと思う。
つかえるRDBMSもそれぞれ色々使えるので問題はなさそう
料金は最小構成と容量10GBで見積もってみたところ
RDS: 20ドル強
CloudSQL10ドル弱

こちらもお値段ではGCPの方に軍配。

 

Storageの比較(AWS S3 vs GCP Cloud Storage)

https://thinkit.co.jp/story/2015/03/30/5801?page=0%2C1
速度計測をしたレビューがありました。日本にデータセンターが無いにも関わらずCloudStorageがダントツに早いようです。
料金は1TB確保して、割りと大きなファルを配信する設定にしました。
S3:34ドル程度
CloudStorage: 27ドル程度

データ転送量に対する課金の比較

AWSではAWS全体からの転送量
GCPではGAEの転送量とCloudStorageの転送量にたいしてそれぞれレートで料金がかかるようです。
GAEの転送量に関してはGAEの料金に含めてしまったのでここでは

どちらも受信に関しては料金が発生しない。

それぞれS3とCloudStorageに関しては1TB/月で
GAEの転送量に関しては重いデータはStorageに逃がすと考え50GB/月

AWS:150ドル(S3-1TB)
GCP: 61ドル(GCS-1TB) + 5.9ドル(GAE-50GB)

 

無料枠について

AWSではこのプランの場合、新規契約から12ヶ月は月44.8ドル割引が適用されるらしい。
GAEでは1日毎にそれぞれ無料枠があるので、アクセスが少ない場合などは0円運用なども視野に入ってくる。

 

まとめと特筆事項

この構成で、ほとんど両者に機能や性能の差はなさそう。
特にそれぞれのPaaSにロックインされてしまうのが困るので特殊な機能はあまり使いたくない気持ちもある。
上記の設定、それほど負荷がかからないがstaticなデータ転送が多い想定での構成で料金はそれぞれ以下のようになった。

AWS:初年度の月額182.06ドル 次年度から 226.86
GCP: 月額103.31ドル

あとは料金の比較になるが、スタートアップや小さいサービスであればGoogleに完全に軍配があがる。

リスクとしては、AWSが規模で大きくリードしているため、GCPがサービスを終了させる可能性などもすこし頭をよぎる。
またGCPに関して今までは国内にデータセンターがなかったため、米国のデーターセンターへのアクセスとなり、ターンアラウンドタイムが少し遅いというデメリットがあったが、2016年中には日本にGoogle Cloud Platformのデータセンターが作られるようだ。国内でイマイチ盛り上がりにかけるところもあるので、これも期待したい。米国内でもデーターセンターの追加もあるようなのでGCP全体で勢いがつけばいいなというところ。

とりあえず、なるべくサービスにロックインされないような仕様のシステムを構成し、GCPを選択しようかなと思っています。
実際の運用データがでたらそれをもとにまた比較をしてもいいかも。

 



この記事のトラックバック用URL - http://mashi.exciton.jp/archives/241/trackback


洗濯表示アプリがR25スマホ情報局に紹介されました!

Android用のアプリ「洗濯表示」がR25スマホ情報局に紹介されました!

 

洗濯表示アプリ

 

衣類を傷めず自分で洗濯をする際にチェックしておく必要がある、タグに記載されたJISの「洗濯表示」。よく調べずに洗濯してしまい、衣服が縮んだり伸びたりしてしまった経験がある人も多いでしょう。そんな悲劇を繰り返さないために役立ててほしいのが、この「洗濯表示 -新洗濯マーク対応-」です。

 

2016年12月から採用される、新しい洗濯表示全41種類に対応している点が大きな特徴。アイコンをタップすると画面下段に意味が表示されるので、洗濯方法を間違えてしまうリスクが回避できます。特に、新しい洗濯表示は見慣れないアイコンが多いので、このアプリが頼りになるはずです。ちなみに、新しい洗濯表示は国際基準に則ったものなので、海外製衣類の洗濯法もわかるようになりますよ。

 

もちろん現行の洗濯表示(全22種)にも対応しているので、これまでに購入した衣類の洗濯法を調べることも可能。機能自体はシンプルですが、実用度はかなり高いこのアプリ。大切な衣類を洗濯する際は、ぜひ活用してください。

アプリでわかる!衣類の正しい洗濯法 | R25スマホ情報局
http://smartphone.r25.jp/app-use/141752

 

 

 

ハウツーアプリの方にも紹介いただきました。

R25スマホ情報局

冬の衣類や毛布などを自宅で洗濯しようと思った時、気になるのが洗濯方法。もしタグにわからない「洗濯表示」のアイコンがあったら「洗濯表示 -新洗濯マーク対応-」でチェックしてみましょう。アイコンをタップするだけで洗濯法を教えてくれるので、衣類を傷めてしまう心配ともおさらば。2016年から採用される新しい洗濯表示にも対応しています。

小さな疑問を解決!暮らしハウツーアプリ | R25スマホ情報局
http://smartphone.r25.jp/app-use/theme-141754

 

どうもご紹介ありがとうございます!

 

 

【洗濯表示 -新洗濯マーク対応-】アプリのダウンロードはGoogle Play Storeからもどぞーー


この記事のトラックバック用URL - http://mashi.exciton.jp/archives/225/trackback


Slick3のSQLクエリを複数結合する

Slickにて、クエリを連続的に結合する方法についてのまとめ
忘備録的なものなので、用語に関してなど、間違っているところがあるかもしれないです。
ツッコミしていただけたらありがたいです。

環境

  • play2.4
  • Slick3.0
  • mysql


Slickでクエリを発行する際に、同一のWeb Requestの中でdb.runを何回も発行するとトランザクションが別になってしまうので、片方のトランザクションだけが失敗してしまって原子性が保たれないなどの場合がある。
同一トランザクションにするためにはすべてのクエリ(DBIOAction)を一つにまとめる必要がある。


データベースに対する操作はDBIOActionと呼ばれる

  • accountTable.schema.create
  • accountTable.map(_.userId === 1).result
  • accountTable += Account(1,”name”,”email)
  • (users returning users.map(_.id)) += User(None, “Stefan”, “Zeiger”)


これらはすべてDBIOActionであり、db.run( DBIOAction ) と実行できる類のものの事になります。

これらを同一のトランザクションとして処理したい場合、単純な方法としてはマニュアルに乗っているようにすればよい。
DBIO.seqの中に全部放り込めば良いので、上記のDBIOActionを例にすると以下のようになる。

Slick3.0.3 Manual
http://slick.typesafe.com/doc/3.0.0/gettingstarted.html#populating-the-database

val setup = DBIO.seq(
  	accountTable.schema.create,
	accountTable.map(_.id == 1).result,
	accountTable += Account(1,"name","email),
	(accountTable returning accountTable .map(_.id)) += Account(1,"name","email)
)
val setupFuture = db.run(setup.transactionally)

これですべてのクエリが実行されるわけだが、クエリの結果は取得することができない。ひとつとして。

さらに実際はSelectした結果をInsertしたい場合もあるだろうし、AutoincrementされたIdを元に他のレコードをUpdateしたい場合など、
クエリの結果を取得したうえで次のクエリを行うには次のように考えれば良い。


参考にしたいのがマニュアルにあるこちらのサンプル
for yield 文を使ってうまくクエリの結合をしている。

Slick3.0.3 Manual
http://slick.typesafe.com/doc/3.0.0/dbio.html#transactions-and-pinned-sessions

val a = (for {
  ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result
  _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally

val f: Future[Unit] = db.run(a)

もう少し単純でわかりやすい形のサンプルにしてみる(これは実際に役に立つ例ではないが)

val coffees = TableQuery[CoffeeTable]
val a = (
  for {
    gotIds <- coffees.filter(_.type === "ESPRESSO").map(_.id).result
    gotCoffees <- coffees.filter(_.id === gotId.head).result
} yield (gotCoffees))

val f: Future[Seq[Coffee]] = db.run(a.transactionally)

このサンプルではまず3行目でtypeが“ESPRESSO”のレコードのIDをすべて取得する。そして取得されたすべてのidが左に示したgotIdsにSeqとして代入される。
次4行目でgotIdsと一致するレコードをCoffeeテーブルから取得する。それが左に示したgotCoffeesに代入される。(ここではheadを使い取得したgotIdの最初の1つと一致するものを取得している)
そして最後 yield に指定されたgotCoffeesが最終的にFutureから取得できる結果となり、Seq[Coffee]である。
もしyield(godIds)とすれば最後に取得できる値はSeq[Int] になる。
両方欲しければ yield((gotCoffese, gotIds)) とすれば両方の値が(Seq[Coffee], Seq[Int])とタプルで取得できるようになる。


まとめると
forの中の各行にクエリを記述し、取得される値を左辺の変数に代入、
次の行では同様にクエリを記述でき、前の行で取得した変数のリストを利用することもできる。
これをいくつも繰り返すことができるような形になっている。
そして、取得したい結果を左辺の変数より、yield に記述する。

そして最後にdb.runするまえにtransactionallyを指定しておけばコミット処理やロールバックを自動で行ってくれるため、原子性が守られることになる。


もう一つ少し実用的な応用のサンプルも一つ挙げてきます。
ESPRESSOの種類のCoffeのIDを取得し、時間とともにオーダーに加えるというサンプルです。
取得したIDを元にOrderインスタンスを作成しinsertするようなことも一つのトランザクションで可能です。

val coffees = TableQuery[CoffeeTable]
val orders= TableQuery[OrdersTable]
val a = (
  for {
    gotIds <- coffees.filter(_.type=== "ESPRESSO").map(_.id).result
    _ <- orders += Order(gotIds.head, "order time")
} yield ())

val f: Future[Seq[Unit]] = db.run(a.transactionally)

ところで、なぜこれらのクエリーがfor yield文で記述されるのか気になった方もいると思います。
for yieldはscalaではflatMap関数に変換されて処理されるところがポイントになります。

このfor yield文をflatMap関数で表していくと次のように表すことができます。

coffees.filter(_.type=== "ESPRESSO").map(_.id).result.flatMap( gotIds:Seq[Int] =>
    orders += Order(gotIds.head, "order time").map( _ => ... )

いままでのクエリがどのように組み立てられているか、なんとなくわかったのではないでしょうか。


DBIOAction.flatMap(result1:Any =>
  DBIOAction.flatMap(result2:Any => 
    DBIOAction.flatMap(result3:Any =>
      DBIOAction.flatMap(result4:Any => 

こんなかんじでクエリが作られていくようです。
この辺の仕組みがわかると、ようやくSlick3と仲良くなれたような気がしました。



この記事のトラックバック用URL - http://mashi.exciton.jp/archives/189/trackback


SlickにてAutoincrementされたIDを取得して別テーブルにInsert

SlickでAutoincrementされたIDを追加時に取得し、そのIDを使用して他のテーブルにもレコードを追加する方法
例えばAccountにユーザーを追加、IDはautoincrementされるが、そのIDを利用して他のテーブルにもIDに結びついたレコードを追加したいような場合。
の方法。


環境は
  • play2.4
  • Slick3.0
  • mysql


普通にレコード追加して、そのIDを取得する方法としてはオフィシャルドキュメントに載ってるとおり

Slick3.0.3 Manual
http://slick.typesafe.com/doc/3.0.3/queries.html#inserting

val userWithId =
  (users returning users.map(_.id)
         into ((user,id) => user.copy(id=Some(id)))
  ) += User(None, "Stefan", "Zeiger")

db.action(userWithId.transactionally)

問題ははこの取得したIDを使って他のレコードを追加すること。
トランザクションが別になっても良いのであれば難しいことは何もないのだが、
同一のトランザクションで行うには次のようにすればよい。

def(account: models.Account, another: models.Another){
  val action = (
    for {
      newId <- (accountTable returning accountTable.map(_.userId) )+=account
      _ <- anotherTable += another.copy(userId = newId)
  } yield newId )
  db.action(action.transactionally)
}

4行目でレコードを追加して、autoincrementされたnewIdを確保
5行目でanotherモデルのuserIdプロパティにnewIdをセット(関数型プログラミングならコピーで。)してInsert

これで同一トランザクションで処理できる
ほかの処理を追加したい場合はforの中に追加していく形でできる



この記事のトラックバック用URL - http://mashi.exciton.jp/archives/182/trackback


Slickで削除済みテーブルを使った削除を実装

Slickにて、レコード削除の際、削除したレコードを削除済みテーブルに追加する方法と
それを復活させる方法、
また管理の際に、通常テーブルと削除済みデーブルを結合して取得する方法について。

環境は

  • play2.4
  • Slick3.0
  • mysql

SQLのレコードを削除したいけど、削除済みのデータも残しておきたい。
そんなことも度々あると思うんですが、論理削除として削除フラグをテーブルに作るのも後々管理がしにくい、、ということで
通常テーブルと削除済みテーブルを2つ作ってしまえばいいじゃない!
そして、通常の処理では通常テーブルだけを参照して、
管理画面なんかで見る時だ通常テーブルと削除済みテーブルをUnionするという方法をとっています。
SQLで書くなら

通常の処理

  SELECT * From Account;

管理画面での処理

 (select *, false as Exist from Account) 
   union
 ( select *, true as Exist from AccountDelete)
   order by userId Desc;

こんな感じにしたいわけです。


ここから本題。
まず通常テーブルと削除済みテーブルの定義は以下のとおりで、通常テーブルについているO.AutoInc以外はほとんど同じです。

//通常テーブル
  val table = TableQuery[AccountTable]
  class AccountTable(tag: Tag) extends Table[Account](tag, "Account") {
    def userId = column[Int]("userId", O.PrimaryKey, O.AutoInc)
    def email = column[String]("email")
    def password = column[String]("password")

    def * = (userId.?, email, password) <>(Account.tupled, Account.unapply _)
  }

//削除済みテーブル
  val deleteTable = TableQuery[AccountDeleteTable]
  class AccountDeleteTable(tag: Tag) extends Table[Account](tag, "AccountDelete") {
    def userId = column[Int]("userId", O.PrimaryKey)
    def email = column[String]("email")
    def password = column[String]("password")

    def * = (userId.?, username, email, password) <>(Account.tupled, Account.unapply _)
  }

まず削除処理のSlick3での書き方

  def delete(userId: Int): Future[Unit] = {
    val act = {
      for {
        rows <- table.filter(_.userId === userId).take(1).result
        _ <- DBIO.seq(deleteTable += rows.head,
                      table.filter(_.userId === userId).delete)
      } yield()
    }
    db.run(act.transactionally)
  }

userIdに相当するレコードを探してきて、それを削除済みテーブルに追加して、通常テーブルから削除しています。
単純に3つのクリエをそれぞれに実行してもできますが、
一連の処理を一つのトランザクションで行いたいのでこういう形になります。


レコード復活の場合もほぼ同様ですが、UserIdをそのまま戻したいので、insertOrUpdateを利用しています。

  def restore(userId: Int): Future[Unit] = {
    val act = {
      for {
        rows <- deleteTable.filter(_.userId === userId).take(1).result
        _ <-DBIO.seq( table.insertOrUpdate(rows.head),
                         deleteTable.filter(_.userId === userId).delete)
      } yield ()
    }
    db.run(act.transactionally)
  }

通常の処理ではAccountTableのみを見ればよいですが、管理画面などでは削除されたものとそうでないものをまとめて、しかもID順に並び替えて閲覧したかったので、次のようにしました。
まず二つのテーブルを存在フラグのカラムを追加したうえで結合して、userId順に並び替えています。

  def selectAllWithDelete(): Future[Seq[AccountWithDeleteFrag]] ={
    val tbl = table.map(x => (x.userId.?, x.username, x.email, x.password, LiteralColumn[Boolean](true)) )
    val tblDelete = deleteTable.map(x => (x.userId.?, x.username, x.email, x.password, LiteralColumn[Boolean](false)))
    val union = (tbl union tblDelete).sortBy(_._1.desc)
    db.run(union.result).map(_.map(AccountWithDeleteFrag.tupled(_)))
  }
LiteralColumn[Boolean](true))

を使って、テーブルの定義に対し値を設定したカラムを1つ追加したレコードを取得しています。
それぞれののテーブルからの取得クエリを union して sortby しています。名前がないので、1番目を指定。
取得できるレコードは元のAccountレコードとは構造が変わってきますのでタプルのまま取得してもいいですし、
上記のように新しくbooleanのプロパティを増やしたcase class を作っても良いと思います



この記事のトラックバック用URL - http://mashi.exciton.jp/archives/177/trackback


Play framework2.4 のプロジェクトがintellij IDEAにインポートされない場合

Play Framework2.4のプロジェクトをActivator new で作成し、intellij IDEAでうまくインポートできない場合

mydocuments> activator new myproject play-scala
mydocuments> cd myproject
mydocuments/myproject> activator run

http://localhost:9000/
この時点で問題なく動作。アクセスできる。

次にこのプロジェクトをIDEAにインポートする。
Welcomeウィンドウより、[import project]
作成した[myproject]を選択
「import project from external model] => [SBT] を選択
必要に応じて各種設定を変更。デフォルトでもOK
[Finish]
すると

Resolve Error

Error while importing SBT project: ... at
scala.collection.mutable.WrappedArray.foreach(WrappedArray.scala:34) at
scala.collection.TraversableLike$class.map(TraversableLike.scala:244) at
scala.collection.AbstractTraversable.map(Traversable.scala:105) at

・・・・

とかなんとかエラーが出てインポートできない様子。
アップデートなんかで治るかもしれませんが、とりあえず、現状の対策として
ホームディレクトリにある、
~/.stb ディレクトリを削除してから、上記の作業をすればうまくいきます。



この記事のトラックバック用URL - http://mashi.exciton.jp/archives/165/trackback


Intellij community(無料版)でPlayFramework2.4を使う方法

Intellij使ってPlayつかいたいけど、予算がないので無料版で何とかするメモ!

お金のある方はultimateをお買い上げするのが一番良さそうですが・・・

新しいバージョンのPlayFrameworkではTypesafe Activatorを使うようになったので
以前のやり方
[Playframework][Scala][IntelliJ IDEA]Intellij IDEA/Scala/Playframework2.0 での環境構築に関するメモ #play_ja
とは少し変わりました。


公式にもTypeSafe Activatorを使えと書いてあります。
https://www.playframework.com/download


Typesafe Activatorをインストール

まずは公式でダウンロードしてくる
https://www.typesafe.com/get-started

適当な場所に解凍しておいてください。program filesとかスペースが入ってるとこはうまく動かない可能性があるので、cドライブの直下とか、何処かにおいてください。
そしたら、Activatorを設置した場所へパスを通しておいてください。
PATHに加えるとか、環境変数のpathに追記するとかのあれです。
macもlinuxもwindowsもやり方は違えど基本同じだとおもいます。
これで[activator]コマンド使えるようになったはずです。
コマンドラインから

>activator help

でヘルプが出ればOK!


playプロジェクト作成

もういきなりプロジェクト作成です。
プロジェクトを作る場所を決めたら、そこにディレクトリを移動してください。
コンソールや、コマンドプロンプトから

mydocuments> activator new my-play-app play-scala

これで“play-scala”テンプレートで“my-play-app”を作成します。
Javaで作る場合は“play-java”にしてください。

次に

mydocuments> cd my-play-app
mydocuments/my-play-app> activator run

暫し待つ。
newもrunも依存ライブラリなんかをダウンロードするので結構時間かかりました。

(Server started, use Ctrl+D to stop and go back to the console...)
----
[info] - play.api.Play$ - Application started (Dev)

がでたら起動成功。
ブラウザでhttp://localhost:9000/にアクセスしてみましょう。

こんなかんじになってたらplayのインストールと起動は成功!
終了はCtrl+D


intellijのインストールとプラグイン

intellijは公式ページから無料のCommunity Editionをダウンロードしてきましょう。https://www.jetbrains.com/idea/download/
(ultimateもお試し期間があるようですが、、、)

インストーラーで入るので難しいことはないと思います。
普通に立ち上げて、次はプラグインをインストールしましょう。

必要なのは
・Scala
・SBT

です。あとは
・Batch Scripts Support
を入れておくと起動ボタンから起動するようにできます。
プラグインのインストールの仕方は難しく無いとは思いますが、色々なサイトにやり方が書いてあるとおもいます。
プラグインを入れたら、intellij再起動。


playプロジェクト取り込み

intellijから
[import project]
先ほどのプロジェクトフォルダを選択
[SBT]としてインポートを選択

必要なものにチェックをつけて、Finishボタン
またちょっと時間かかります。結構色々待たされます。


intellijから起動

インポートが終わったら、intellijのターミナルから

mydocuments/my-play-app> activator run

してみてください。

先ほどと同じようにplay framework が立ち上がると思います。
ブラウザでhttp://localhost:9000/にアクセスしてみましょう。
終了はCtrl+D


起動設定をしてRunボタンから立ち上げできるようにする

Batch Scripts Supportプラグインを入れていれば、Runボタンから立ち上げの設定ができます。
ここから

+ ボタンから Batchを選択して
名前とScript,ScriptParameter WorkingDirectryを設定してください。

こんな感じ。

これで、起動ボタンから起動出来て簡単!?


デバッグできるようにする。

まずデバッグモードでサーバーの起動ボタンを作成します
デバッグモードでのサーバー起動はポート9999で起動する場合

mydocuments/my-play-app> activator -jvm-debug 9999 run

という形になります。
これを先ほどと同じように

+ ボタンから Batchを選択して
以下のように入力!

つぎに、デバッガーをつなげるために
+ ボタンから Remoteを選択して
portを9999に設定

これで設定はOK


デバッグする

まずデバッグモードでサーバー(batchで設定した方)を起動させます。
起動したら、次にデバッガを起動させます。(Remoteで設定した方)
これで適当にブレイクポイント設定して、http://localhost:9000/にアクセスすれば、ブレイクポイントでとまると思います。

雑な説明なのでどこか抜けてるかもしれませんが以上


参考にしたサイト
http://d.hatena.ne.jp/kamekoopa/20130427/1367042044
http://d.hatena.ne.jp/yuheiomori0718/20120623/1340464997



この記事のトラックバック用URL - http://mashi.exciton.jp/archives/144/trackback


ゴミ収集日

アプリ、リリースしました!

ゴミ収集日

https://play.google.com/store/apps/details?id=jp.exciton.gomiapp

今回もマテリアルデザイン意識してみました。
意識しただけなので細かいところは許してください!

これ作りながら、ちょっと気になったものを記事にしてたりしたので、
AndroidのUIスレッドTimerとか
PendingIntentとAlermManagerとか
あと今回ユニットテストもやったので、AndroidをUnitTestする
の内容も!

とくに、アラート系のアプリなので、開発の中でAlertManagerの特性についてはわかったことも多いです。
AlertManagerの説明に関しては
TechBoosterさんのところにわかりやすくまとまってます。

AlertManagerで時刻を予約してから、時計の設定を先に進めると一気にアラート来るんですね、
知らなかった。

このアプリではいくつかの予約(2,3個)をしているので、デバッグのために時計進めてみると、
いっきにIntentが飛んでくるんです。
なので、ほんとに今実行すべきか(アラートを出すか)をIntent受け取ってから判断する必要があります。
細かいけど、こういう細かいことをやってるアプリとそうじゃないアプリがあるってことが、時刻ずらしてみるとわかるのがおもしろい。

あとCrashlytics入れました。なので、クラッシュでてもご報告不要です!!!
ただしUIとか動きがおかしいのはCrashlyticsしてくれないのでご報告いただけたら嬉しいです!
Crashlyticsの方もそのうちレポートしたいなーとおもってます。



この記事のトラックバック用URL - http://mashi.exciton.jp/archives/124/trackback


カスタムビューとレイアウトエディタ

アンドロイドでカスタムビューを作るときのお話

カスタムビューを作った時にレイアウトエディタでの表示がカッコよくならなくて
諦めたりしてませんかー?

isInEditMode()

http://developer.android.com/reference/android/view/View.html

これが便利!


Viewを継承してなんかのCustomViewを作るんですが、
ActivityのonCreateなんかで

public void onCreate() {
   new MyCustomView(this, param1, param2);
}

とか

myview = findViewById(R.id.my_custom_view);
myview.setMyParameter("hoge");

とかって処理をして、表示内容を変えたりすると思うんです。
何も渡さない時のデフォルトをコンストラクタで用意してもいいんですが、
その処理がそれなりに重いものだったりすると無駄がおおい、、、、

isInEditMode()

そんなときisInEditMode()で判断して、レイアウトエディタで表示するときにだけの処理なんかが書けるようになります!

myview = findViewById(R.id.my_custom_view);
if( isInEditMode() ){
   myview.setMyParameter("レイアウトエディタ表示");
}else{
   myview.setMyParameter(message);
}

みたいな?
こういうのちゃんとやっとくとプログラマとUIデザイナーとの分業もやりやすいです!?

参考:
http://www.united-bears.co.jp/blog/archives/402
http://to40.blog55.fc2.com/tb.php/29-4af5d637


この記事のトラックバック用URL - http://mashi.exciton.jp/archives/117/trackback


AndroidをUnitTestする

Androidのテスト環境について

Unitテストをやりたい場合。
JUnitテストを行うやり方についてのメモ。
AndroidStudioを使ってることを前提とします。

まず、build.gradleのdependenciesですが、
これまでだとEspressoなどを使う必要があったのですが、
androidのsupport packageに追加されているtesting-support-libに統合されたようです。

Testing Support Library

support packageに追加されたtesting-support-libを使ってAndroidのテストをJUnit4で書く

ということなのでdependenciesには
androidTestCompile ‘com.android.support.test:testing-support-lib:0.+’
のみ追加。

それとdefaultConfig
testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner”
を追加してください。
これはJUnit4を動かすには必要なようです。
もしJUnit3だけであればなくても動くのですが、今回はJUnit4を使ってみるので入れておきます。

 

この状態で実行してみると、つぎのようなエラーが出るかもしれません。

Error:duplicate files during packaging of APK /Users/…/build/apk/app-debug-androidTest-unaligned.apk
Path in archive: LICENSE.txt

下記のようにbuild.gradleにのandroid{ のなかに追加してください
エラーに出てるファイル、もしくはパス名を下記のように付け加えればOK

android {
    ~~  
    packagingOptions {
        exclude 'LICENSE.txt'
    }

環境によっては’META-INF/LICENSE.txt’になるかも。

そうするとこんなかんじになります。↓

 android {
    ~~

    defaultConfig {
           ~~

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }


    packagingOptions {
        exclude 'LICENSE.txt'
    }
}

dependencies {
    ~~

    androidTestCompile 'com.android.support.test:testing-support-lib:0.+'
}

 

UnitTestの書き方

AndroidStudioがいろいろと手伝ってくれます。
テストしたいクラスにカーソルをあわせた状態で[ALT+Enter](デフォルト設定の場合)
ここではScheduleListItemという適当なクラスをテストしてみることにします。ただのプレインオブジェクトです。
キャプチャ
そのままCreate Test

 

ダイアログが出て、ここで好きなテストフレームワークをとオプションが選べます。
testing-support-libで、JUnit4が使えるようになったということでJUnit4にしてみましょう。
JUnit4の場合、クラスの継承はしないのでSuperClassが空になりますが、ご心配なく。
setUptearDownはテストの最初と最後に実行される関数です。
とりあえず作ってもらったら楽なのでチェック。
下のメンバー関数は特にチェックしなくてもよいかも。
キャプチャ2

保存先を聞かれるけどそのままでOKでしょう。
src/androidTest/java/~~/**Test.javaが作られます。

public class ScheduleListItemTest {
	@Before
	public void setUp() throws Exception {

	}

	@After
	public void tearDown() throws Exception {

	}
}

テストを書く

テストを追加したい場合はメンバ関数を以下のように追加して
assert文を書けばOK

	@Test
	public void foo() throws Exception {
             assertEquals("31", day);
	}

JUnit4の使い方についてはここでは詳しく説明しませんが他のサイトで勉強してみてください。
参考:http://d.hatena.ne.jp/megascus/20130622/1371879646

 

assert文を書く場合、static importで
import static junit.framework.Assert.assertEquals;
を追加するといいです。これがないとAssert.assertEquals()のように書かないといけないので。

 

 

かんたんなテストのサンプルを書いてみます。
あんまり良いサンプルではないですが許してください。

import static junit.framework.Assert.assertEquals;

public class ScheduleListItemTest {

	private ScheduleListItem item;
	@Before
	public void setUp() throws Exception {
		item = new ScheduleListItem();
	}

	@After
	public void tearDown() throws Exception {
		//最後になんかする場合はここで。
	}

	@Test
	public void foo(){
		item.set(5, 8, "5/8日のすけじゅーる");
		
		assertEquals(5, item.getMonth());
		assertEquals(8, item.getDay());
		assertEquals("5/8日のすけじゅーる", item.getTitle());
	}
}

最初にsetUp()が実行され、itemがnewされます。
次にfoo()が実行され、各assert文が実行されます。
itemにセットした値が正しく入ってるかチェックしてるって感じです。

 

実行の設定

テストの実行に当たっての設定です。
まず実行ボタンの左のメニューよりEditConfigurationsを選んでください。
キャプチャ3

 

次に+ボタンからAndroid Testsを選択!
キャプチャ4

 

新しいテストの実行の設定が作られました。
キャプチャ5
名前がUnnamedになっているので、好きな名前に変えてOKです。
Moduleになってるので、テストするモジュールを選択してください。

テスト範囲が選べます
[All in Module]AndroidTestに入ってるモジュール全部。実行
[All in Package]パッケージを指定して、実行
[Class]クラスをひとつ指定して実行
[Method]メソッドを指定して実行

ここでは全部ということでAll in Moduleを選択しました。

 

TargetDevice
テストを実行するのにも、Androidデバイスが必要なので、
それぞれ実機を使うか、エミュレーターを使うか、ダイアログでその都度選ぶかするか、
などの選択肢です。
いつも実行するときに選択しているものにしておけばよいでしょう。

設定が終わったらOK

テスト実行!

今作成した実行設定を選んで、RUN!!!
キャプチャ6

こんなかんじでテストが実行されます。
オールグリーン!
キャプチャ7

コンテキストが必要な場合は・・・?

例えばActivityをテストしたい場合や、ファイルの読み書きのテストをしたい場合、
androidのAPIにアクセスするためにcontextを渡さなければいけないので、今回のようにはできません。
ActivityInstrumentationTestCase2というクラスなどを継承して作らなければいけないのですが、
それはまた次回に・・・・



この記事のトラックバック用URL - http://mashi.exciton.jp/archives/69/trackback