名言 大事なことはFateから教わった
このエントリーは 名言Advent Calendar 2015の16日目のエントリです。
名言
貴様は臣下を救うばかりで導く事をしなかった 王の欲の形を示す事もなく、道を見失った臣下を捨て置き、 ただ一人ですまし顔のまま小奇麗な理想とやらを思い焦がれていただけよ
王とは、誰よりも鮮烈に生き、もろ人を見せる姿を指す言葉 全ての勇者の羨望を束ね、その道標として立つ者こそが王! 故に、王とは孤高にあらず
Androidのデザインパターンを考えてみたの続き。Kotlin対応版。
一番最新のやつはこっち
前回はこちら
- 前のプロジェクトで開発していたアプリをリリースしてからはAndroidから離れていたのですが、会社変えてまた触ることになったので思い出す意味でも再考します。
- 前回は出来るだけ純正のAPIだけで考えたのですが、今回はライブラリも使いながらもう少し手を入れてみたり。
今一度ポイント
- 基本的にModelは死なない + 通知で変更を伝える役割を持つ
- ViewやVCでAPIを呼ぶのはユーザーの操作に影響されやすいからやめれ
変えたところ
- Android Best Practiceを参考にパッケージ構成を整理
以下のライブラリ群をGradleに追加。AsyncTaskLoader周りはokHttpとRxを使い、Observer周りはottoのEventBusを使って通知のやり取りをする。
- okHttp
- otto
- rxJava
- rxAndroid
こちらも参考に。たぶん前の投稿のスライドと同じ所属の方の作。ありがたや。
- 今、Kotlin楽しい感じなので、Kotlinで書きなおしてます。考え方は変わらないのでJavaに置き換えて読むとよいかと。
パッケージ構成
パッケージ名 | 役割 |
---|---|
entities | 各ドメインで扱うデータクラスを配置 |
events | EventBusを通して発火するイベントクラスを配置 |
fragments | Fragmentを配置 |
managers | 多様なデータを扱うコントローラを配置 |
models | 各ドメインモデルを配置 |
network | 外部との通信が発生するモジュールを配置 |
utils | ユーティリティー群 |
views | Viewに関連するモジュールを配置 |
コード
package me.rei_m.kotlinmvcsample.managers import java.util.HashMap /** * 各Modelのインスタンスを管理する */ public class ModelLocator private constructor() { companion object { private val showcase = HashMap<Tag, Any>(); public enum class Tag { ATND, } public fun register(tag: Tag, model: Any): Unit { showcase.put(tag, model) } public fun get(tag: Tag): Any = showcase[tag]!! } }
- アプリ起動時にAtndApiのモデルをセットしておく
// ModelLocatorにModelの参照を登録
ModelLocator.register(ModelLocator.Companion.Tag.ATND, AtndModel());
- entities/AtndEntity
- Kotlinのデータクラスを使って作成
package me.rei_m.kotlinmvcsample.entities import java.io.Serializable /** * Atndのイベント情報を保持するデータクラス. */ public data class AtndEventEntity(val id: Int, val title: String) : Serializable
- network/AtndApi
- APIとの通信部分を作成。さんぷるなのでキーワードは固定。
package me.rei_m.kotlinmvcsample.network import com.squareup.okhttp.CacheControl import com.squareup.okhttp.HttpUrl import com.squareup.okhttp.OkHttpClient import com.squareup.okhttp.Request import me.rei_m.kotlinmvcsample.entities.AtndEventEntity import org.json.JSONObject import rx.Observable import java.net.HttpURLConnection /** * Atnd Apiへの接続部分を定義する. */ public final class AtndApi private constructor() { companion object { /** * Atnd APIへのリクエストを行いAtndEventのEntityを返すObservableを作成する. */ public fun request(): Observable<AtndEventEntity> { return Observable.create({ t -> // リクエストのURLを作成. val url = HttpUrl.Builder() .scheme("https") .host("api.atnd.org") .addPathSegment("events") .addQueryParameter("keyword_or", "google,cloud") .addQueryParameter("format", "json") .build() val request = Request.Builder() .url(url) .cacheControl(CacheControl.FORCE_NETWORK) .build() // リクエスト開始. val response = OkHttpClient().newCall(request).execute() if (response.code() == HttpURLConnection.HTTP_OK) { // 正常に取得できたら、パースしてonNextでObserverに流す. val responseJson = JSONObject(response.body().string()); val eventCount = responseJson.getInt("results_returned") if (0 < eventCount) { val events = responseJson.getJSONArray("events") for (i in 0..eventCount - 1) { val e = events.getJSONObject(i).getJSONObject("event"); t.onNext(AtndEventEntity(e.getInt("event_id"), e.getString("title"))) } } } else { // エラーの場合はレスポンスコードをThrowableにつめて投げる t.onError(Throwable(response.code().toString())) } t.onCompleted() }) } } }
- models/AtndModel
package me.rei_m.kotlinmvcsample.models import me.rei_m.kotlinmvcsample.entities.AtndEventEntity import me.rei_m.kotlinmvcsample.events.AtndLoadedEvent import me.rei_m.kotlinmvcsample.events.EventBusHolder import me.rei_m.kotlinmvcsample.network.AtndApi import rx.Observer import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import java.util.* import me.rei_m.kotlinmvcsample.events.AtndLoadedEvent.Companion.Type as EventType /** * Atndに関する情報を管理するドメインモデル. */ public class AtndModel { /** API読込中など処理中か判定するフラグ */ public var isBusy = false private set /** アテンドのイベント情報を保持するリスト */ public val atndEventList = ArrayList<AtndEventEntity>() /** * AtndのAPIからイベント情報を取得し、内部に保持する. */ public fun fetch() { // ビジー状態の場合は処理終了. if (isBusy) { return } // ビジー状態にする isBusy = true // APIリクエストのオブザーバーを作成 val observer = object : Observer<AtndEventEntity> { override fun onNext(t: AtndEventEntity?) { // リストに追加する. atndEventList.add(t!!) } override fun onCompleted() { // onCompletedを受け取ったら完了のLoadedEventを発火する EventBusHolder.EVENT_BUS.post(AtndLoadedEvent(EventType.COMPLETE)) } override fun onError(e: Throwable?) { // onErrorを受け取ったらエラーのLoadedEventを発火する EventBusHolder.EVENT_BUS.post(AtndLoadedEvent(EventType.ERROR)) } } // AtndのAPIにリクエストを飛ばしてModelへの配信を開始する AtndApi.request() .onBackpressureBuffer() .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .finallyDo({ // 必ずビジー状態を解除する. isBusy = false }) .subscribe(observer) } }
- fragments/AtndEventListFragment
package me.rei_m.kotlinmvcsample.fragments import android.os.Bundle import android.support.v4.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.ListView import com.squareup.otto.Subscribe import me.rei_m.kotlinmvcsample.R import me.rei_m.kotlinmvcsample.entities.AtndEventEntity import me.rei_m.kotlinmvcsample.events.AtndEventClickEvent import me.rei_m.kotlinmvcsample.events.AtndLoadedEvent import me.rei_m.kotlinmvcsample.events.EventBusHolder import me.rei_m.kotlinmvcsample.managers.ModelLocator import me.rei_m.kotlinmvcsample.models.AtndModel import me.rei_m.kotlinmvcsample.views.adaptors.AtndEventListAdapter import me.rei_m.kotlinmvcsample.events.AtndLoadedEvent.Companion.Type as EventType import me.rei_m.kotlinmvcsample.managers.ModelLocator.Companion.Tag as ModelTag /** * AtndEventをリスト表示するFragment. */ public class AtndEventListFragment : Fragment() { /** ListViewのアダプター */ private var mListAdapter: AtndEventListAdapter? = null companion object { /** * ファクトリ. */ fun newInstance(): AtndEventListFragment { return AtndEventListFragment() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mListAdapter = AtndEventListAdapter(activity, R.layout.list_item_atnd_event) } override fun onDestroy() { super.onDestroy() mListAdapter = null } override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater!!.inflate(R.layout.fragment_atnd_event_list, container, false) val listView = view.findViewById(R.id.list_atnd_event) as ListView listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id -> // リスト内の項目がクリックされたらクリックイベントを発火. val atndEventEntity = parent?.adapter?.getItem(position) as AtndEventEntity EventBusHolder.EVENT_BUS.post(AtndEventClickEvent(atndEventEntity)) } // リストビューにアダプターをセット listView.adapter = mListAdapter return view } override fun onResume() { super.onResume() // EventBus登録 EventBusHolder.EVENT_BUS.register(this); // AtndModelとリストビューに表示中のイベント数を取得 val atndModel = ModelLocator.get(ModelTag.ATND) as AtndModel val displayedCount = mListAdapter?.count!! if (displayedCount != atndModel.atndEventList.size) { // 表示済の件数とModel内で保持している件数をチェックし、 // 差分があれば未表示のイベントがあるのでリストに表示する mListAdapter?.clear() mListAdapter?.addAll(atndModel.atndEventList) mListAdapter?.notifyDataSetChanged() } else if (displayedCount === 0) { // 1件も表示していなければイベント情報を取得する atndModel.fetch() } } override fun onPause() { super.onPause() // EventBus登録解除 EventBusHolder.EVENT_BUS.unregister(this); } @Subscribe @SuppressWarnings("unused") public fun onAtndEventLoaded(event: AtndLoadedEvent) { // ModelからLoad完了のイベントを受け取った時の処理 when (event.type) { EventType.COMPLETE -> { // 読み込みが正常に完了したらModel内のイベント情報をアダプターにセットして再描画する val atndModel = ModelLocator.get(ModelTag.ATND) as AtndModel mListAdapter?.clear() mListAdapter?.addAll(atndModel.atndEventList) mListAdapter?.notifyDataSetChanged() } EventType.ERROR -> { // エラー表示する } } } }
動かしてみる
- 今回のコードはこちらに
GitHub - rei-m/Kotlin_mvc_sample
- Retrofitとか使うともっと短く書けるのかな。まだまだAndroid歴浅いのでなんとも修行足りない感。
- ちゃんとテストを書かないとというところがまだ考えきれてないのでその辺もやらないとですね。Stateパターンとかも取り入れてみたら規模がでかくなったら幸せになる気も。しばらく触りそうなので色々試してみます。
自分用 MacBookセットアップメモ
転職して新たなMacBookが支給されたけど、セットアップするときにあれ何入れてたっけ?となったので自分用のメモ。(USキーボード向け)
英数/かな切り替え
- Google IMEを入れる
- Karabiner-Elementsを入れる
- Complex ModificationsからJP向けのruleを入れてコマンドキーで切り替えるルールを有効にする
Homebrew入れる
shell
パッケージ管理
- brewコマンドで必要なものを入れるようにしておくためにとりあえずぶっこむ。
- homebrew
- home-brew-cask
iTerm2
- リッチなターミナルエミュレータ
brew cask install iterm2
- iTerm2でプロフィールを新しく作成してデフォルトにしておく。
- プロフィールのWindowタブを開いて、styleを
Top of Screen
にRowsを40
にしておく。iTerm2を開くとモニター上段が表示領域になる
tmux
- ターミナルを多重化するツール
brew install tmux
- tmux plugin用にtpmを入れる
- iTerm2のプロフィールのKeysタブを開いて追加する。Send Hex CodeでiTerm2からtmuxにキーを送れるようにする。
ショートカットキー | HexCode | 動作 | tmux のコマンド |
---|---|---|---|
Ctrl Shift ] | 0×2 0x6e | 次のWindowに移動 | Ctrl b n |
Ctrl Shift [ | 0x2 0x70 | 前のWindowに移動 | Ctrl b p |
Ctrl N | 0x2 0x63 | 新しいWindowを開く | Ctrl b c |
Ctrl Shift % | 0x2 0x25 | Windowを左右分割 | Ctrl b % |
Ctrl ] | 0×2 0x6f | 次のPaneに移動 | Ctrl b o |
ほかいろいろ
brew install go brew install peco brew install ctags brew install nodebrew brew install rbenv brew install elixir brew cask install java なんかほかあれば
エディタ
- Atom入れる
brew cask install atom
- 設定は自分の記事参考
- sync-setting使ったけど職場のAtomとの共有は失敗した。。。なぜだ。
dot files
- 自分のを落としておく
- GitHub - rei-m/dotfiles: dotfilesたち
- 全部 homeディレクトリにコピーしておく
- tmuxを起動して
ctrl+b , I
でプラグインをインストールする
だいたいおっけーなはず
- 何かあったらこの日記に追加していく。
飛騨の古民家を借りて開発合宿に行ってきた
目的
- 非日常な空間にこもって新しい技術に挑戦したり、作ろうと思っていたけど普段はなかなか時間が・・・というようなものを集中して作る
- もともと身内でプライベートでも何か作ったりするのが好きな人達が集まってわいわい開発するイベントだったり。今回は2回目で前職のつながりで技術顧問のNaoyaさん含む6人で参戦。
場所
- 飛騨古川駅より徒歩8分で駅からのアクセス抜群。まあ飛騨までが遠いんですけど。
- ちなみに1回目は日光でした。場所はこちら はじめのいっぽ
- 宿泊代は1軒あたりの金額なので参加人数が多いほど一人あたりの金額が安くなります。なお、布団代は別で一人3000円かかりました。
スケジュール
## 11/7(土) 9:00 東京駅集合 9:32 東京駅発 | かがやき507 11:46 富山駅着 昼食取る 13:02 富山駅発 14:17 飛騨古川着 古民家チェックイン 15:00 飲食物買い出し。 15:30 スケジュール確認して開発開始 19:00頃 出前で夕飯 23:00頃 初日は軽く宴。聖地で氷菓観るw ## 11/8(日) 食事以外は基本開発 ## 11/9(月) 朝食後開発追い込みと発表準備 10:50頃 成果発表LT。5分から10分 11:30頃 LT終了。チェックアウト。開発はここまで。 12:00 〜 高山の方までいって軽く観光して名古屋回りで帰還
やったこと
- 最近Kotlinをちょいちょいつまんで触っているので、KotlinでHBFavのAndroid版を再現してみるみたいなことやってました。
- 完成まではまだ遠いけど進捗 rei-m/HBFav_kotlin · GitHub
行ってみて
- ちょっと遠いかなと思ったけど、風情ある1軒屋を貸しきって開発するというのはとてもよい時間でした。周りも静かで超集中できます。他人が気になるなら余ってる部屋に行けば一人になれたり。
- 開発合宿は最後にLTやるの大事です。限られた時間でデモできるものを作ってLTのスライドも作って発表すると達成感がすごい。
- 場所遠かったのでもう一日くらい使っても良かった感。また来年も行きたいですね!
一休を退職しました
ご報告
- 10月いっぱいで退職しますという話をし、本日が最終出社でした。社内外の方にはお世話になりました。ありがとうございました。気がつけば入社してから6年。。。6年前だとガラケサイトの開発からはじまり、最近だとネイティブアプリの開発とかいろいろやったなあという感じです。
- 最近の一休は技術顧問としてNaoyaさんがジョインしてから組織改善プロジェクトがはじまり、すごい勢いでいろいろなことが変わっていきました。といっても技術顧問は銀の弾丸ではないので大事なのは自分たちが課題を認識して自分たちで変わろうとする意思を持つことかなと思います。最初の頃から携わってた身としてはよい経験ができました。ありがとうございました。
参考
【前編】CTO不在で、開発組織改善に着手! 一休のエンジニアが語る苦悩の1年 / 飲み会で探るエンジニアのホンネ #naoya_sushi 編
【後編】CTO不在で、開発組織改善に着手! 一休のエンジニアが語る苦悩の1年 / 飲み会で探るエンジニアのホンネ #naoya_sushi 編
今後
雑兵MeetUp #1で「REVEAL.jsとMilkcocoaで双方向LTする」というLTをしてきました
- Twitterのタイムライン上でイベントをみかけて、あまり見ないコンセプトの勉強会で面白いなーと思ったので盛り上げる側で参加してきました。主催の @yodatomato さん、会場を貸してくれた 21cafeさんありがとうございました。ほかの参加者の方々もお疲れさまでした。
イベント
まとめ
自分のLT資料
- スライド兼デモ
- スライドにコメント投稿するコントローラー
- Github
コメント
- 外でLTするのは超久しぶりだったので案の定、時間内には微妙に収まりきらず、この辺は場数が必要だなという印象ですね。継続して何かネタを投下していきたいと思います。
- 発表してるときに気づいたのですが、流れるコメントにz-indexつけ忘れてたっぽくてスライドの画像の後ろにコメントが回ってしまっていました。Github上のコードは直しています。
- 今回はゆるふわなネタで発表しましたが、スライドで使ったMilkcocoaは国産のBaasとしてはめっちゃ簡単に導入できてアイデアも広がるので知らない方はぜひ触ってみてほしいです。npmでNode.js用のパッケージも提供されてるので、IOTにNodeが動かせる環境が乗っていれば簡単にIOTで集めたデータをPublishすることもできます。IOTの入門としてはハードルは低めではないでしょうか。
- LT後にMilkcocoaの中の人からTシャツをもらいました! @bakuonboogie さんありがとうございました!!
Android StudioとKotlinでAndroidアプリを作る
Kotlinとは
- InteliJ IDEAでおなじみ、JetBrains社が主導して開発している言語で、JVM上で動く静的型付けのオブジェクト指向プログラミング言語。Java 言語よりも簡潔に書けることを目指しているらしい。これを使ってAndroidアプリを書いてみます。
- 公式はこちら。Kotlin
導入
- Android Studioは同じJetBrains製品のInteliJベースで作られているだけあって、プラグインを入れるだけで簡単に導入可能。Preferences -> pluginから以下の2つをインストールして再起動するだけです。なお自分の環境はAndroid Studio 1.4、compileSdkVersionは23、minSdkVersionは16にしてます。
- Kotlin
- Kotlin Extensions For Android
プロジェクトを作ってJavaのコードをKotlinに変換する
- 適当にBlankActivityだけ追加したプロジェクトを作ります。そうするとこんなActivityが出来ますね。
package me.rei_m.kotlinsample; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
- プロジェクトツリーにappがフォーカスされている状態でメニューバーからcodeを選ぶと一番下に
Convert Java File to Kotlin File
が追加されていているのでこれを選択します。すると、Kotlinへのコンバートが開始されます。 - コンバートが完了するとMainActivityはこのように生まれ変わり、拡張子も.ktになります。ハイライト効かないのでみづらいですね。。。早速、valが見えているあたり、Scalaに近い雰囲気を感じます。
package me.rei_m.kotlinsample import android.os.Bundle import android.support.design.widget.FloatingActionButton import android.support.design.widget.Snackbar import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import android.view.View import android.view.Menu import android.view.MenuItem class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById(R.id.toolbar) as Toolbar setSupportActionBar(toolbar) val fab = findViewById(R.id.fab) as FloatingActionButton fab.setOnClickListener(object : View.OnClickListener { override fun onClick(view: View) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG).setAction("Action", null).show() } }) } override fun onCreateOptionsMenu(menu: Menu): Boolean { // Inflate the menu; this adds items to the action bar if it is present. menuInflater.inflate(R.menu.menu_main, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. val id = item.itemId //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true } return super.onOptionsItemSelected(item) } }
- 次にメニューのTools -> Kotlin -> Configure Kotlin Projectを選択してそのままOKを押します。
- すると
build.gradle
にKotlinでビルドするための設定が追加されるので、この状態でビルドをかけて実行します。
- 動きました!
- 次にKotlin Extensionを使うための設定を追加します。
build.gradle
を開いてdependentciesにextensionsを追加します。
buildscript { ext.kotlin_version = '0.14.451' repositories { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" <- これを追加 } }
- そして
MainActivity.kt
を開いてimportを追加します。syntheticから後ろはxmlの名前です。
import kotlinx.android.synthetic.activity_main.* import kotlinx.android.synthetic.content_main.*
- これで何ができるようになるかというと
findViewById
を使わなくてもViewのコンポーネントを参照できるようになります。content_main.xml
のTextView
のテキストをActivity
から書き換えてみましょう。参照するためにはIDが必要なのでxmlを開いて適当にTextView
にidを追加します。ここではhello
としています。 MainActivity.kt
に戻り、findViewByIdを使っているところを書き換えてみましょう。
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // val toolbar = findViewById(R.id.toolbar) as Toolbar // setSupportActionBar(toolbar) setSupportActionBar(this.toolbar) // val fab = findViewById(R.id.fab) as FloatingActionButton // fab.setOnClickListener(object : View.OnClickListener { this.fab.setOnClickListener(object : View.OnClickListener { override fun onClick(view: View) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG).setAction("Action", null).show() } }) // TextViewを書き換える処理を追加 this.hello.text = "Hello Kotlin !!" }
- 今まで
findViewById
を使っていたところはimport
したことでthis
から参照できるようになりました。わかりやすくthis
を書いてますが、もちろん無くても動きます。これでビルドして動かしてみましょう。TextView
の表示が変わったはずです。
- まだReferenceを読んでいる途中ですが、Javaで書くよりだいぶ簡潔に書ける印象です。しばらく触ってみて既存の資産の使い方など確かめながら何か作ろうかと思います。
追記
- 早速書き始めたらいきなりExtensionが原因でこけた。。。下のようにViewに追加するとクラスが見つからないと言われて死にます。
<AppCompatButton android:id="@+id/open_hogehoge" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="なんか開く" />
- AppCompatButtonをフルパスで指定したらビルド通りました。Support library系のコンポーネント使うときは注意しないといけないですねー
<android.support.v7.widget.AppCompatButton android:id="@+id/open_list" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Open sample of listView" />