もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

最近の個人的なAndroidの設計とかテスト周りとかまとめ

  • 最近、Androidの設計やらテストの書き方やらを試行錯誤していて、ちょっと情報が散らばってきたので個人的なまとめです。これが絶対的にイケてる!とかじゃなくて単にいろんな人のスライド読んだり、自分で試したりして今こんな感じになったというレベルのものです。
  • コードは↓で作ったアプリをベースにいじってます。。Kotlin製のアプリなのでJavaに適時読み替えてください。

rei19.hatenablog.com

設計の話

  • 設計の目標はモジュールの責務を分割して将来の変更に強くするという感じです。Androidの場合は特に適当に作るとActivityとFragmentが膨れ上がってメンテつらい作りになりがちです。で、去年に書いた記事(Androidのデザインパターンを考えてみたの続き。Kotlin対応版。 - もやもやエンジニア)とかを経て、いまのところはMVPな構成にしてModel層をDDDライクに作るようにしてます。先日のAndroidオールスターズ2でもid:kgmyshin 氏が同様の話をしてましたが、自分も近いものを目標に作ってます。

  • 雑な絵だとこんな感じです。Activity/FragmentはViewの操作に集中して、ドメインロジックはModel層に、Presenterが両者を仲介するというふいんきですね。

f:id:Rei19:20160822002625p:plain

実際に作った例

  • 起動時にIDを設定する画面(InitializeFragment)を例にとってみます。この画面は入力されたIDが有効か問い合わせて有効なら端末に保存して次の画面に行くという仕様です。

f:id:Rei19:20160821214641p:plain:w300

  • まずはPresenterとViewをつなぐInterfaceです。ViewはFragmentに実装してActionsはPresenterに実装させます。
interface InitializeContact {

    interface View {
        fun showNetworkErrorMessage()
        fun showProgress()
        fun hideProgress()
        fun displayInvalidUserIdMessage()
        fun navigateToMain()
    }

    interface Actions {
        fun onCreate(view: InitializeContact.View)
        fun onResume()
        fun onPause()
        fun onClickButtonSetId(userId: String)
    }
}
  • 次にFragmentです。ライフサイクルやユーザーの操作で発生したイベントをPresenterに伝えるのと、上で定義したInterfaceを実装してViewの操作をするメソッドを生やしてます。
  • Presenterはテスト時に差し替えできるようにDagger経由で注入できるようにしておきます。Model層の操作はここでは一切出てきません。
class InitializeFragment() : BaseFragment(),
        InitializeContact.View,
        ProgressDialogController {

    companion object {
        fun newInstance(): InitializeFragment = InitializeFragment()
    }

    @Inject
    lateinit var navigator: ActivityNavigator

    @Inject
    lateinit var presenter: InitializeContact.Actions

    override var progressDialog: ProgressDialog? = null

    private var subscription: CompositeSubscription? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        component.inject(this)
        presenter.onCreate(this)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {

        subscription = CompositeSubscription()

        val view = inflater.inflate(R.layout.fragment_initialize, container, false)

        val editId = view.findViewById(R.id.fragment_initialize_edit_hatena_id) as AppCompatEditText

        val buttonSetId = view.findViewById(R.id.fragment_initialize_button_set_hatena_id) as AppCompatButton
        buttonSetId.setOnClickListener {
            presenter.onClickButtonSetId(editId.editableText.toString())
        }

        subscription?.add(RxTextView.textChanges(editId)
                .map { v -> 0 < v.length }
                .subscribe { isEnabled -> buttonSetId.isEnabled = isEnabled })

        return view
    }

    override fun onResume() {
        super.onResume()
        presenter.onResume()
    }

    override fun onPause() {
        super.onPause()
        presenter.onPause()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        subscription?.unsubscribe()
        subscription = null
    }

    override fun showNetworkErrorMessage() {
        with(activity as AppCompatActivity) {
            val rootView = findViewById(R.id.fragment_initialize_layout_root)
            hideKeyBoard(rootView)
            showSnackbarNetworkError(rootView)
        }
    }

    override fun showProgress() {
        showProgressDialog(activity)
    }

    override fun hideProgress() {
        closeProgressDialog()
    }

    override fun displayInvalidUserIdMessage() {
        view?.findViewById(R.id.fragment_initialize_layout_hatena_id)?.let {
            it as TextInputLayout
            it.error = getString(R.string.message_error_input_user_id)
        }
    }

    override fun navigateToMain() {
        navigator.navigateToMain(activity)
        activity.finish()
    }
}
  • 最後にView層から受け取ったイベントとModel層の操作を仲介するPresenterです。Presenterが依存するModel層のモジュールはコンストラクタで受け取るようにしています。テスト時にはモックにするなりして差し替えます。
class InitializePresenter(private val userRepository: UserRepository,
                          private val userService: UserService) : InitializeContact.Actions {

    private lateinit var view: InitializeContact.View

    private var subscription: CompositeSubscription? = null

    private var isLoading = false

    override fun onCreate(view: InitializeContact.View) {

        this.view = view

        val userEntity = userRepository.resolve()
        if (userEntity.isCompleteSetting) {
            view.navigateToMain()
        }
    }

    override fun onResume() {
        subscription = CompositeSubscription()
    }

    override fun onPause() {
        subscription?.unsubscribe()
        subscription = null
    }

    override fun onClickButtonSetId(userId: String) {

        if (isLoading) return

        isLoading = true
        view.showProgress()

        subscription?.add(userService.confirmExistingUserId(userId)
                .doOnUnsubscribe {
                    isLoading = false
                    view.hideProgress()
                }
                .onBackpressureBuffer()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    onConfirmExistingUserIdSuccess(it, userId)
                }, {
                    onConfirmExistingUserIdFailure(it)
                }))
    }

    private fun onConfirmExistingUserIdSuccess(isValid: Boolean, userId: String) {
        if (isValid) {
            userRepository.store(UserEntity(userId))
            view.navigateToMain()
        } else {
            view.displayInvalidUserIdMessage()
        }
    }

    private fun onConfirmExistingUserIdFailure(e: Throwable) {
        if (e is HttpException) {
            if (e.code() == HttpURLConnection.HTTP_NOT_FOUND) {
                view.displayInvalidUserIdMessage()
                return
            }
        }
        view.showNetworkErrorMessage()
    }
}
  • これでFragmentはViewの操作のにのみ集中出来るようになって見通しがよくなりました。ViewはDatabindingを使うとよりスッキリするかと思います。

テストの話

  • ここでは主にUnitTestレベルの話をします。Androidにおけるテストは単純なJUnitのテストとエミュレーターや実機を使ったテストで分かれますが、前者を指します。

Viewのテスト

  • Viewは本来はEspressoを使ってエミュレーターなり実機なりの上でテストを動かす必要がありますが、Robolectric を使うと端末を使わずにテストができます。
  • 事前にテスト用のアプリケーションクラスを作成してViewが依存するPresenterを差し替えておきます。全体のコードはGitHubを見た方が早いと思うので差し替えの部分だけ紹介。Test用のComponetを作ってファクトリメソッドをオーバーライドして差し替えます。
open class TestApp : App() {
    override fun createApplicationComponent(): ApplicationComponent {
        return DaggerTestApplicationComponent.builder()
                .applicationModule(ApplicationModule(this))
                .infraLayerModule(InfraLayerModule())
                .build()
    }
}
  • こちらが実際のRobolectricを使ったテストケースです。Configで上で作ったテスト用のアプリケーションを指定することで、テスト対象のFragmentが依存するPresenterを差し替えて、Model層の操作を行わないようにして、実装したViewのメソッドが正しく実装されているかのみを検証します。
  • 余談ですがKotlinはMockitoのwhenやらhamcrestのisやらが予約語になってるのでバッククォートで囲む必要があってちょっとめんどくさい。。。
@RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class,
        application = TestApp::class,
        sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP))
class InitializeFragmentTest {

    lateinit var fragment: InitializeFragment

    private val view: View by lazy {
        fragment.view ?: throw IllegalStateException("fragment's view is Null")
    }

    private val editHatenaId: EditText
        get() = view.findViewById(R.id.fragment_initialize_edit_hatena_id) as EditText

    private val buttonSetHatenaId: Button
        get() = view.findViewById(R.id.fragment_initialize_button_set_hatena_id) as Button

    private val textInputLayoutHatenaId: TextInputLayout
        get() = view.findViewById(R.id.fragment_initialize_layout_hatena_id) as TextInputLayout

    private val snackbarTextView: TextView
        get() = fragment.activity.findViewById(android.support.design.R.id.snackbar_text) as TextView

    private fun getString(resId: Int): String {
        return fragment.getString(resId)
    }

    @Before
    fun setUp() {
        fragment = InitializeFragment.newInstance()
        SupportFragmentTestUtil.startFragment(fragment, SplashActivity::class.java)
    }

    @Test
    fun initialize() {
        assertThat(editHatenaId.visibility, `is`(View.VISIBLE))
        assertThat(buttonSetHatenaId.visibility, `is`(View.VISIBLE))
        assertThat(buttonSetHatenaId.isEnabled, `is`(false))
    }

    @Test
    fun testButtonSetHatenaIdStatus_input_id() {
        editHatenaId.setText("a")
        assertThat(buttonSetHatenaId.isEnabled, `is`(true))
    }

    @Test
    fun testButtonSetHatenaIdStatus_not_input_id() {
        editHatenaId.setText("")
        assertThat(buttonSetHatenaId.isEnabled, `is`(false))
    }

    @Test
    fun testButtonSetHatenaIdClick() {
        val presenter = mock(InitializeContact.Actions::class.java)
        doAnswer { Unit }.`when`(presenter).onClickButtonSetId("valid")
        fragment.presenter = presenter
        editHatenaId.setText("valid")
        buttonSetHatenaId.performClick()
        verify(presenter).onClickButtonSetId("valid")
    }

    @Test
    fun testShowNetworkErrorMessage() {
        fragment.showNetworkErrorMessage()
        assertThat(snackbarTextView.visibility, `is`(View.VISIBLE))
        assertThat(snackbarTextView.text.toString(), `is`(getString(R.string.message_error_network)))
    }

    @Test
    fun testDisplayInvalidUserIdMessage() {
        fragment.displayInvalidUserIdMessage()
        assertThat(textInputLayoutHatenaId.error.toString(), `is`(getString(R.string.message_error_input_user_id)))
    }

    @Test
    fun testShowHideProgress() {
        fragment.showProgress()
        assertThat(fragment.progressDialog?.isShowing, `is`(true))
        fragment.hideProgress()
        assertNull(fragment.progressDialog)
    }

    @Test
    fun testNavigateToMain() {
        val navigator = spy(fragment.navigator)
        doAnswer { Unit }.`when`(navigator).navigateToMain(fragment.activity)
        fragment.navigator = navigator
        fragment.navigateToMain()
        verify(navigator).navigateToMain(fragment.activity)
    }
}

Presenterのテスト

  • PresenterはAndroidの実装に依存しない形になっているのでピュアなJUnitのテストになります。Contextが必要な場合などはMockitoを使ってContextのMockを作ってあげればいいかなと思います。
  • setUpでモックのViewのメソッドを空実装するのと、RxAndroidのスケジューラーを使った処理がすぐ返るように設定しています。テストの検証はMockのViewのメソッドが呼ばれたかどうかで判定するようにして、その先で何が起きるかはPresenterは関心を持ちません。
  • モデル層はMockitoを使ってテスト時に必要な処理を実装しています。
@RunWith(MockitoJUnitRunner::class)
class InitializePresenterTest {

    @Mock
    lateinit var userRepository: UserRepository

    @Mock
    lateinit var userService: UserService

    @Mock
    lateinit var view: InitializeContact.View

    @Before
    fun setUp() {

        doAnswer { Unit }.`when`(view).navigateToMain()
        doAnswer { Unit }.`when`(view).showProgress()
        doAnswer { Unit }.`when`(view).hideProgress()
        doAnswer { Unit }.`when`(view).displayInvalidUserIdMessage()
        doAnswer { Unit }.`when`(view).showNetworkErrorMessage()

        RxAndroidPlugins.getInstance().reset()
        RxAndroidPlugins.getInstance().registerSchedulersHook(object : RxAndroidSchedulersHook() {
            override fun getMainThreadScheduler(): Scheduler? {
                return Schedulers.immediate()
            }
        })
    }

    @After
    fun tearDown() {
        RxAndroidPlugins.getInstance().reset()
    }

    @Test
    fun testOnCreate_initialize_not_complete_register_user() {

        `when`(userRepository.resolve()).thenReturn(UserEntity(""))

        val presenter = InitializePresenter(userRepository, userService)

        presenter.onCreate(view)
        verify(view, never()).navigateToMain()
    }

    @Test
    fun testOnCreate_initialize_complete_register_user() {

        `when`(userRepository.resolve()).thenReturn(UserEntity("test"))

        val presenter = InitializePresenter(userRepository, userService)

        presenter.onCreate(view)
        verify(view).navigateToMain()
    }

    @Test
    fun testOnClickButtonSetId_success_check_id() {

        `when`(userRepository.resolve()).thenReturn(UserEntity(""))

        doAnswer { Unit }.`when`(userRepository).store(UserEntity("success"))

        `when`(userService.confirmExistingUserId("success")).thenReturn(Observable.just(true))

        val presenter = InitializePresenter(userRepository, userService)
        presenter.onCreate(view)
        presenter.onResume()
        presenter.onClickButtonSetId("success")

        verify(userRepository, timeout(TimeUnit.SECONDS.toMillis(1))).store(UserEntity("success"))
        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).showProgress()
        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).hideProgress()
        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).navigateToMain()
    }

    @Test
    fun testOnClickButtonSetId_fail_check_id() {

        `when`(userRepository.resolve()).thenReturn(UserEntity(""))

        `when`(userService.confirmExistingUserId("fail")).thenReturn(Observable.just(false))

        val presenter = InitializePresenter(userRepository, userService)
        presenter.onCreate(view)
        presenter.onResume()
        presenter.onClickButtonSetId("fail")

        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).showProgress()
        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).hideProgress()
        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).displayInvalidUserIdMessage()
    }

    @Test
    fun testOnClickButtonSetId_fail_check_id_404() {

        `when`(userRepository.resolve()).thenReturn(UserEntity(""))

        `when`(userService.confirmExistingUserId("fail"))
                .thenReturn(Observable.error(HttpException(Response.error<HttpException>(HttpURLConnection.HTTP_NOT_FOUND, ResponseBody.create(MediaType.parse("text/html"), "")))))

        val presenter = InitializePresenter(userRepository, userService)
        presenter.onCreate(view)
        presenter.onResume()
        presenter.onClickButtonSetId("fail")

        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).showProgress()
        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).hideProgress()
        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).displayInvalidUserIdMessage()
    }

    @Test
    fun testOnClickButtonSetId_fail_check_id_network_error() {

        `when`(userRepository.resolve()).thenReturn(UserEntity(""))

        `when`(userService.confirmExistingUserId("fail"))
                .thenReturn(Observable.error(HttpException(Response.error<HttpException>(HttpURLConnection.HTTP_INTERNAL_ERROR, ResponseBody.create(MediaType.parse("text/html"), "")))))

        val presenter = InitializePresenter(userRepository, userService)
        presenter.onCreate(view)
        presenter.onResume()
        presenter.onClickButtonSetId("fail")

        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).showProgress()
        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).hideProgress()
        verify(view, timeout(TimeUnit.SECONDS.toMillis(1))).showNetworkErrorMessage()
    }
}
  • これでgradleのtestタスクでUnitTestが回るようになるのでCircleCIなどの上で動かすようにしてあげれば継続的にテストを回せる仕組みができます。

おわり

  • Androidのテストまわりがよくわかってなかったので、一旦立ち止まって調べたり試したりしたことを整理するためにメモを残してみました。
  • サンプルコードをだらだらと貼ってしまいましたが、見返してみるとレイヤーを区切ったことでテストが書きやすくなったなという印象です。まだAndroid歴浅いので、間違ってるところがあるかもしれませんが、もっといいコードを書いてスピード感ある改修ができる作りを目指したいですね。

京都の一軒家借りて開発合宿に行ってきた

  • 2泊3日で京都の一軒家借りて一休.comのエンジニア勢と開発合宿してきました。この開発合宿は東京から離れて泊まりでもくもくして最終日にアニメの聖地巡礼をして帰るという趣のイベントです。今回はちはやふるがテーマということで開発した後にかるたの聖地の近江神宮をぶらりとしてきました。
  • 今年やったことは、2年前くらいからAndroidも開発する人になったものの、テストをちゃんと書けてなかったので去年の合宿で作ったアプリの手直しとEspressoによるUIテストのやり方とか調べながらもくもくしてました。

  • 成果発表したスライドはこれ

github.com

雑感

  • 仲間内の開発合宿は日光、飛騨ときて今回は京都で開催されたのですが、前2回に比べて京都は適当に歩いていても美味そうな飯屋がごろごろあるので誘惑が多いですwとはいえ、東京から離れて非日常に身をおいて集中して開発するという目的は達成できたのでよかったです。
  • 個人の目標はCircleCIの上でだけconnectedAndroidTestがこけるという状態が解消できなかったので微妙に未達でくやしい。。。こいつを解消して合宿で作ったPRをマージしたら僕の合宿は完了という感じです。
  • 飯の写真とか泊まったところとかは他の参加メンバーの記事を見るとよいですね。お好み焼き最高に美味かったです。

rfp.hatenablog.com

blog.shibayan.jp

ちなみに前回

rei19.hatenablog.com

もくもく会でFluxを学んだ with Babel + webpack

仲間内でGWもくもく会やるか!という話になったので、僕のテーマはFluxの考え方を学んで実装できるようにしようということで久しぶりにJS書きました。

やったこと

資料は Flux でぐぐると一番上に出てくる @azu_re さんのこちらのスライドを理解しながら写経 + @ するという感じ。

10分で実装するFlux

Fluxの雑な理解

  • データの流れを一方通行にして表現するアーキテクチャ
  • 登場する要素はComponent、Action、Store、EventEmitter
    • ComponentはActionをCallする + Storeを監視していてStoreの状態が変わったイベントを受け取って自分 = Viewに反映させる
    • ActionはComponentからCallされたActionをEventとして発行する
    • StoreはStateを持つ。Actionを監視していてActionのEventを受け取って自分のStateを更新して状態が変わったEventを発行する
    • EventEmitterはBus的な役割を持っていてObserverパターンの中核を担い、データの流れを繋げる。
  • よく聞くReduxはこの辺をライブラリ化したもの。

書いてみる

書いてみて思ったこと

  • データの流れに制約を持たせることで煩雑になりがちな状態の管理をしやすくできるのかなという印象を持ちました。
  • 仕組み的には新しい物はなく考え方が大事。

次の展開

  • アーキテクチャはざっくり理解したので本業のアプリ開発に展開できるようなAndroidのサンプルを書いてみる予定。クライアントサイドの開発は状態の管理をいかに制御するかがキモですね。

SwiftでiOSアプリ開発の勉強始めた 1

今の会社の仕事はAndroidアプリ書いてるけどiOSアプリも書けるようになりたいということでSwiftの勉強を始めました。ブログにはやったこととか覚えたこととかを適当にメモを残していこうかなーと思います。夏くらいまでにはしょぼくてもいいのでアプリを1本ストアに載せるのが目標。iOSアプリ自体はTitaniumで書いたやつは昔リリースしたけど純粋なネイティブアプリは初めてですね。

教科書

  • 特にこれだ!という理由はないけどSwiftは開発スピード早いイメージなので新し目の参考書を選択。2月の中くらいから読み始める。

やったこと

  • PlayGround上でSwiftの文法の確認。評判通り割とモダンな感じで、書いていけば慣れるかなという印象。
  • あとは写経。コードは一応上げている。

GitHub - rei-m/HelloSwift: Swift素振り用

今週までやったこと / 覚えたこと

  • StoryBoard ・・・ これにコンポーネントを配置していく。StoryBoardのコンポーネントをViewControllerのコード上にドラッグするとそのコンポーネントの参照が取れたり、イベントを設定できたりする。AndroidfindViewByIdのようなことをGUIでやっているぽい。
  • Outlet / Action ・・・ コンポーネントの参照はOutlet、イベントはActionと定義されているみたい。作成した後にOutletの名前を変えたりするとビルドエラーになるのでその前に接続を解除する必要がある。コードからStroyBoardへの接続もできる。
  • UIViewController ・・・ 名前の通りViewController。これが画面単位にあるのかな。viewDidLoad は画面の起動時に必ず呼ばれるみたいなので、Viewのイベントや値の設定などはここで行う。Androidと同じでVCに色々仕事をつめ込まないことが作る時のコツになりそう。
  • UIButton、UILabel etc etc ・・・ UIResponder - UIView が親クラスになっていて、UIViewが表示に関する機能を提供している。
  • AutoLayout ・・・ 多様なデバイスサイズと画面の向きにレイアウトをいい感じに表示させるための仕組み。コンポーネントに対して表示位置の制約(Constraints)をつける。コツをつかむまでちょっと時間がかかりそう。
  • segue ・・・ VC間の移動の仕方が定義される。StoryBoard上でVCとVCを繋げることで作成できる。移動のアニメーションの仕方はSegueで定義する。
  • UINavigationController・・・ 複数のシーンをドリルダウンするように移動する。既存のViewと組み合わせて使う。基本的にこれを使うのかなーという印象。
  • ここまでで 「Section 13-4 ナビゲーションコントローラで遷移する」まで。

DroidKaigi 2016に行ってきた 2日目

一日目

rei19.hatenablog.com

ということで2日目も行ってきました。2日目も知見にあふれていて最高でしたね!各セッションの資料はこちらのブログであらかたまとめられていたので、今回は参加したセッションと感想を少しだけ残しておこうかなっと思います。

unsolublesugar.com

unsolublesugar.com

参加セッション

第1部「Support Libraryノススメ」 + 第2部「Support Library Internal」 @yuichi_araki さん

  • Googleのエンジニア @yuichi_araki さんによるSupport Libraryの解説でした。Androidに触り始めのころに思った疑問がかなりすっきり。基本的にSupportLibraryを使えば良いということがわかったのは収穫でした。

生まれ変わったUI Automatorを使いこなす @sumio_tym さん

  • UIまわりのテストの知見が少なかったのでこちらを選びました。UIのテストは基本はEspressoを使ってそれでカバーできないところはUI Automatorを使うとよいとのこと。自分のアプリでもテスト書かないとなあ。

Advanced Kotlin for Android @ngsw_taro さん

  • 最近、個人的にホットなKotlinのセッション。Kotlinでアプリを一本書いたけど、後半のExtensionを使ったハックはそんな書き方あるんだ!という感じで参考になりました。Kotlinは書いてて気持ちいいんですよね。

  • ぽろっとTwitterで聞きたかったこと流したら速攻で反応あってびっくりしたw

Android実プロダクトへのKotlinの導入事例 @boohbah さん

  • 連続でKotlinのセッション。こちらは実際にJavaからKotlinに書きなおした事例ということでした。移行の中でハマったことなどが紹介されていて、前のセッションとは違った知見が得られましたね。個人的にはすでにJavaで最適化されているアプリはあえて書きなおさなくてもいいかなーと思っている派ですけど、でもKotlinで書きたい。。。

Androidエンジニアになって2年の学び @ryugoo_ さん

  • 僕もAndroidさわって1年ちょいで割と同じような環境だったので、わかるわ~と思いながら聞いてました。一人プルリクからのセルフレビュー、マージをえんえんと繰り返す感じですね。わかります。規約類は充実させる前にそのプロジェクトは抜けてしまったけど、きっと同じようなことをしてたんだろうな。

Fireside chat @公式アプリのリリースにかかわったみなさん

  • 今回のDroidKaigiの公式アプリに関わった方々でディスカッション。とりあえずIssueたてたらPRが速攻来たみたいなエピソードを聞いてただただOSSすごいって感じました。公式アプリ自体、腕の立つエンジニアたちの知見が集まっていて超勉強になりました。来年は自分もPR送りたいなあ。あとKotlinで書きなおしてみようとも思ったりしました。

まとめ

  • 控えめにいって最高でした。去年は開発が佳境だったこともあり参加できなかったのですが、今年は参加できて本当に良かったと思いました。セッションを聞いて、へー、なるほどすごい!で終わらせるのではなく、実際の開発に活かしたり自分の中の知識として使えるようになるまでがDroidKaigiかなという認識なので、インプットからのアウトプットを心がけるようにします。

  • とりあえず帰ってから書いたもの

qiita.com

  • こっちはKotlinのセッションで聞いた内容をちょっとだけ反映

github.com

DroidKaigi 2016に行ってきた 1日目

Androidの開発者向けのお祭り DroidKaigi に参加してきたレポ。去年は行けなかったけど、今年は参加できた!写真撮ろうと思ったけどスマホの充電器を忘れてしまってご臨終な感じだったのでほとんどテキスト。。。

参加セッション

基調講演 : OSSの動向を捉えた実装方針 @wasabeef さん

  • CyberAgentさんで作っているAndroidアプリで使っているライブラリの話を中心に、昨今よく使われるライブラリを紹介。Rxでリスト操作してるところはStreamに置き換えた方がいいかな。Androidはこの辺のライブラリを知っているか知らないかで作りが変わるので聞けてよかったです。

Master of Canvas @amyu_san さん

  • Canvasを使った面白いアニメーションの作り方の話。Androidはアニメーション苦手なイメージあったけど、いろいろ出来るんだなということがわかってよかったです。ちょうど仕事で必要そうな感じでタイムリー。

Android Lintで正しさを学ぼう @Nkzn さん

  • 割と初学者向けの内容で、あー同じことやったわwという内容でした。./gradle lint でlintかけられるの知らなかったので自分のGradle力低いな。。。と反省。仕事で作ってるやつにとりあえず流してみようかなと思います。ちょっとコワイ。

Android Dev Tools Knowledge @operandoOS さん

スライド上がってなかったので雑なメモ

# 便利なコマンド
- 簡単にadbの環境を用意する
    - brew install android-sdk (caskかも?)
- adb-peco
    - 複数のAndroid端末を同時に繋いでるときに便利
- input text
    - adb shell input text droidkaigi
    - adb shellでどのエミュに繋ぐか選べるので↑のコマンドで選択した端末にテキストを送りこめる
- dumpsys
    - system serviceをdumpできる
    - adb shell dumpsys | grep "DUMP OF SERVICEIS"
    - adb shell dumpsys activity top でViewの構造やら見える
    - adb shell dumpsys activity activities | grep apk
- settings
    - 設定にどんな値が入っているか確認できる
- screenrecord
    - 画面録画機能
- systrace
    - パフォーマンスを見るためのツール
    - HTMLでレポートを出力できる
- atrace

- Android-Command-Note
    - いろいろのっけてる
- AndroidSHell
    - もうちょっと細かく載っている
- dexcount-gradle-plugin
- gradle-version-plugin
    - 使っているライブラリの更新をチェックしてくれる
    - gradle dependencyUpdates
- build-time-tracker-plugin
- gradle-android-command-plugin

# Android Studio Plugin
- Android wifi adb
- ADB Idea
    - studio内からadbコマンド打てる
- adb commander for android
- eventbus-intellij-plugin
- android-postfix-plugin
- Google Developers color scheme

# その他のツール
- androidtool-mac
- tonkotsu
- vysor
    - 端末のミラーリングしてくれる
- Android SDK Search
- DPI Calculator
- Android Resource Navigator
- Material Terminal
    - アプリの中でshell使える
- materialdoc.com
    - 公式ページのサンプルをアプリにした
- DesignOverlay
    - 神ツールっぽい
- View Debug

# どこでこの情報を集めるか
- コード読む
- Google+を覗く
- Twitter
- GitHub
  • adb系のコマンドやgradleのコマンドをひたすら紹介する内容でしたがこの辺が勉強不足な僕的にはすごいよかったです。少しずつ使いこなしていきたいですね。

Dagger2とRealmを利用したモダンな開発 @experopero さん

  • RealmとDagger2で実際の運用での使い方を聞けたのはよかったです。Dagger2はいまいち正解がわからない。。。とりあえずSubComponent使ってみようかと思いました。

Androidの省電力について考える 中西 良明さん

スライド上がってなかったので雑なメモ

# 電力使用量削減の歴史
- Android 6.0で大変革が起こった
    - 5系までは開発者側に省電力の仕組みの実装が委ねられたが6系から標準な仕様として実装された
- 以前は通信キャリアごとで対策が取られていた
    - 端末メーカーレベルでもあった
- Job Scheduler(5.0〜)
- Fused Location Provider

# 6.0からの話
- 強制的に電力消費を抑える仕組み
    - Doze
        - 一定の条件を満たすとDozeモードに入る。たまに起きる
        - setAlermClockはDozeを突破できる。が、推奨されなさそう
        - GCMはNWが閉じるので受け取れないが受け取れるようにする指定もある
    - App Standby
        - アプリが一定期間使われていないとアプリはスタンバイ状態に置かれる
        - スタンバイになったアプリは1日1回しかネットーワークに繋がらなくなる
- 電力的に行儀が悪いとStoreから落とされる可能性がある
- 5.0以上はJobSchedulerを以下はAlarmManagerを使うとよいのでは。めんどくさかったらAlarmManagerだけでも。
- adbを使うことで強制的にDozeモードにすることができる
  • この辺、あまり消費電力を意識したことないので聞けてよかったです。とりあえずAlarmManagerのAPIから調べてみよう

実践!Android Studioプラグイン開発 @konifar さん

スライド上がってなかったので雑なメモ

# プラグインの話
- IntelijでJava6開発
- pluginのプロジェクトを作成
- Eventに応じたActionクラスを作成
- 基本的な導入はQiitaに投稿したぞい
    - 簡単なことはすぐできる

# 実践的な話
## 覚えておくべきクラスと役割
- AnActionEvent
    - プラグインのActionに関連するさまざまな情報を扱うクラス
- Project
    - プロジェクトの情報がとれる。例えばSDKのバージョンによって生成するコードを変える。。。ということなど
- Editor
    - Editorの情報を取ることができる。選択中のテキストを取得するなど
- Document
    - EditorからDocumentを取得する。リスナーなど仕込めて、入力に応じてなんかさせる、などできる
- PsFile

### COmponent
- Dialog
    - DialogWrapperを継承して使う
- Editor Hint
    - HintManager
- Notification
    - Notification.Busを使ってNotifyする。これは更新確認で使ったりするのでデファクトで入れる

## 悩んだ時の調べ方
- 公開されているコードを読むのが早い
- indellij-sdk-docs
    - 基本的にはこれを見ると大丈夫そう
- android-parcelable-intellij-plugin
    - Parcelableを自動生成するぷらぎん
- android-drawable-importer-intellij-plugin
    - 実践的な内容は全て詰まっている
- adb-idea
    - adbコマンドをAndroid Studio上から簡単に実行できる
- Android Studio Code
    - 最終兵器。GitHubには上がっていないが別の場所にある

# まとめ
- 基本的なクラスは覚えておく
- わからないときは似たコードを読んでみる
  • アプリ作るとき、Koniferさんのプラグインめっちゃお世話になってるので、自分でも何か作れるようになりたいなーと思って聞いたけど、事前知識を仕入れることができてありがたかったです。おそらく知らないで突撃したらすごい時間かかりそうな感じ。。。

  • 余談

2日目に続く。。。

  • スタッフの方々お疲れ様でした。明日もよろしくお願いいたします。

ストレングスファインダー(Now, Discover your strength)をやってみた

会社の開発チームでみんなでやってみようという話になったのでテストしてみました。

元ネタはこちら

さあ、才能(じぶん)に目覚めよう―あなたの5つの強みを見出し、活かす

さあ、才能(じぶん)に目覚めよう―あなたの5つの強みを見出し、活かす

  • 初版は2001年となっているので割と古典ぽい感じですね。簡単に紹介すると、才能知識技術が組み合わさることで強みが生まれ充実したライフを送ることが出来るよ!という話で、ストレングスファインダーはそのうちの才能を明らかにするためのツールという感じです。簡単なテストに答えていくいことで34に分類された資質の中から自分にあてはまる上位5個がわかります。なお、テストを受験するためには本の末尾についている受験コードが必要になるので中古で買っても本は読めるけどテストは受けられない可能性が高いです。よくできている。

やってみた結果

最上志向

優秀であること、平均ではなく。これがあなたの基準です。平均以下の何かを平均より少し上に引き上げるには大変な努力を要し、あなたはそこに全く意味を見出しません。平均以上の何かを最高のものに高めるのも、同じように多大な努力を必要としますが、はるかに胸躍ります。自分自身のものか他の人のものかに関わらず、強みはあなたを魅了します。真珠を追い求めるダイバーのように、あなたは強みを示す明らかな徴候を探し求めます。生まれついての優秀さ、飲み込みの速さ、一気に上達した技能――これらがわずかでも見えることは、強みがあるかもしれないことを示す手がかりになります。そして一旦強みを発見すると、あなたはそれを伸ばし、磨きをかけ、優秀さへ高めずにはいられません。あなたは真珠を光り輝くまで磨くのです。このように、この自然に長所を見分ける力は、他の人から人を区別していると見られるかもしれません。あなたはあなたの強みを高く評価してくれる人たちと一緒に過ごすことを選びます。同じように、自分の強みを発見しそれを伸ばしてきたと思われる人たちに惹かれます。あなたは、あなたを型にはめて、弱点を克服させようとする人々を避ける傾向があります。あなたは自分の弱みを嘆きながら人生を送りたくありません。それよりも、持って生まれた天賦の才能を最大限に利用したいと考えます。その方が楽しく、実りも多いのです。そして意外なことに、その方がもっと大変なのです。

学習欲

あなたは学ぶことが大好きです。あなたが最も関心を持つテーマは、あなたの他の資質や経験によって決まりますが、それが何であれ、あなたはいつも学ぶ「プロセス」に心を惹かれます。内容や結果よりもプロセスこそが、あなたにとっては刺激的なのです。あなたは何も知らない状態から能力を備えた状態に、着実で計画的なプロセスを経て移行することで活気づけられます。最初にいくつかの事実に接することでぞくぞくし、早い段階で学んだことを復誦し練習する努力をし、スキルを習得するにつれ自信が強まる――これがあなたの心を惹きつける学習プロセスです。あなたの意欲の高まりは、あなたに社会人学習――外国語、ヨガ、大学院など――への参加を促すようになります。それは、短期プロジェクトへの取組みを依頼されて、短期間で沢山の新しいことを学ぶことが求められ、そしてすぐにまた次の新しいプロジェクトへに取組んでいく必要のあるような、活気に溢れた職場環境の中で力を発揮します。この「学習欲」という資質は、必ずしもあなたがその分野の専門家になろうとしているとか、専門的あるいは学術的な資格に伴う尊敬の念を求めていることを意味するわけではありません。学習の成果は、「学習のプロセス」ほど重要ではないのです。

内省

あなたは考えることが好きです。あなたは頭脳活動を好みます。あなたは脳を刺激し、縦横無尽に頭を働かせることが好きです。あなたが頭を働かせている方向は、例えば問題を解こうとしているのかもしれないし、アイデアを考え出そうとしているのかもしれないし、あるいはほかの人の感情を理解しようとしているのかもしれません。何に集中しているかは、あなたのほかの強みによるでしょう。一方では、頭を働かせている方向は一点に定まっていない可能性もあります。「内省」の資質は、あなたが何を考えているかというところまで影響するわけではありません。単に、あなたは考えることが好きだということを意味しているだけです。あなたは独りの時間を楽しむ類の人です。なぜなら、独りでいる時間は、黙想し内省するための時間だからです。あなたは内省的です。ある意味で、あなたは自分自身の最良の伴侶です。あなたは自分自身にいろいろな質問を投げ掛け、自分でそれぞれの回答がどうであるかを検討します。この内省作業により、あなたは実際に行っていることと頭の中で考えて検討したことと比べた時、若干不満を覚えるかもしれません。あるいはこの内省作業は、その日の出来事や、予定している人との会話などといったような、より現実的な事柄に向かうかもしれません。それがどの方向にあなたを導くにしても、この頭の中でのやりとりはあなたの人生で変わらぬもののひとつです。

目標志向

「私はどこに向かっているのか?」とあなたは自問します。毎日、この質問を繰り返します。目標志向という資質のために、あなたは明確な行き先を必要とします。行き先がないと、あなたの生活や仕事はたちまち苛立たしいものになる可能性があります。ですから毎年、毎月、さらに毎週でさえ、あなたは目標を設定します。この目標はあなたの羅針盤となり、優先順位を決定したり、行き先に向かうコースに戻るために必要な修正をする上で、あなたを助けてくれます。あなたの目標志向は素晴らしい力を持っています。何故ならそれはあなたの行動をふるいにかけさせるからです。――すなわち、特定の行動が目標へ近づくために役に立つかどうかを本能的に評価し、役に立たない行動を無視します。そして最終的に、あなたの目標志向はあなたを効率的にさせるのです。当然ながらこの裏返しとして、あなたは遅れや障害や、例えそれがどんなに興味深く見えようとも本筋から外れることにいらいらするようになります。このことは、あなたを集団の一員として非常に貴重な存在にしています。他の人が脇道にそれ始めると、あなたは彼らを本筋へ連れ戻します。あなたの目標志向は、目標に向かって進むために役に立っていないものは重要ではないということを、あらゆる人に気付かせます。そしてもし重要でないなら、それは時間を割く価値がないということです。あなたは、あらゆる人を進路から外れさせません。

戦略性

戦略性という資質によって、あなたはいろいろなものが乱雑にある中から、最終の目的に合った最善の道筋を発見することができます。これは学習できるスキルではありません。これは特異な考え方であり、物事に対する特殊な見方です。他の人には単に複雑さとしか見えない時でも、あなたにはこの資質によってパターンが見えます。これらを意識して、あなたはあらゆる選択肢のシナリオの最後まで想像し、常に「こうなったらどうなる? では、こうなったらどうなる?」と自問します。このような繰り返しによって、先を読むことができるのです。そして、あなたは起こる可能性のある障害の危険性を正確に予測することができます。それぞれの道筋の先にある状況が解かることで、あなたは道筋を選び始めます。行き止まりの道をあなたは切り捨てます。まともに抵抗を受ける道を排除します。混乱に巻き込まれる道を捨て去ります。そして、選ばれた道――すなわちあなたの戦略――にたどり着くまで、あなたは選択と切り捨てを繰り返します。そしてこの戦略を武器として先へ進みます。これが、あなたの戦略性という資質の役割です:問いかけ、選抜し、行動するのです。

結果を見て

  • チーム内で発表したら、わかるわ~という評価が多かったです。自分でも上位3つは、なるほど確かになと思う点が多々あったり。家でぼっちでもくもくコード書いてたりするのは好きなので、今の仕事というか生き方はある程度は自分の資質ともマッチしてるんだなあという印象です。朝活でコード書いたり本読んだりしてますよーとか話するとストイックですねって言われることあるけど、自分ではそうなの??という感じなので、そういう生活は自分の資質とマッチしているから苦に感じていないという話なのでしょうね。人によっては自分では思っていなかった資質が結果に出たので、チームでやって公開しあうとおもしろいかもしれません。リーダークラスの人はメンバーのストレングスファインダーを見て資質にあったタスクを割り振るようにしたら効果が出たりするのかな。なかなか面白かったので興味ある方はぜひ。