ひとりでAndroidアプリを作ってみた感想

# Overview
Android アプリを個人で作ってみたので、その感想をまとめてみます

# 開発言語は Kotlin を利用
今回作ったアプリは全て [Kotlin](https://kotlinlang.org/) を利用して開発を行いました
業務では Java で書いたことがあったので違いとかを簡単に書いてみます
## String 周りが使いやすい
Java では`StringBuilder()`を使うところですが Kotlin では変数をそのまま参照できるので
書いてて見やすいと感じました

```kotlin:StringBuilder
    fun buildGeocode(): String {
        return "${mMap?.cameraPosition?.target?.longitude},${mMap?.cameraPosition?.target?.latitude}"
    }
```

## forEach が扱いやすい
`filter` が便利でした
以下のサンプルコードではあまり扱いやすい感じがしないですね

```kotlin:forEach
        libraryHoldJson.books.forEach {
            it.libraries.forEach {
                mSystemId = it.id
                it.libKeys.forEach {
                    val key = it.key
                    val status = it.status
                    val notAddedLibrarySystem = mLibrarySystems.filter {
                        it == "$mSystemId$key"
                    }
                    if (notAddedLibrarySystem.size == 0) {
                        Library().updateHoldingBookStatus(Realm.getDefaultInstance(), mSystemId, key, status)
                        val library = Library()
                                .findBySystemIdAndLibKey(Realm.getDefaultInstance(), mSystemId, key)
                        if (library != null) {
                            mListView.add(SearchLibraryHoldsAdapter.Library(library, status))
                        }
                    }
                    mLibrarySystems.add("$mSystemId${it.key}")
                }
            }
        }
```

## 強力な型推論
このおかげでタイピング数がだいぶ減らせた気がします

```kotlin:TypeInference
    private var mAdapter: SearchLibraryHoldsAdapter? = null
    private var mSystemId = ""
    private var mHasContinue = ""
    private var mSession = ""
    private var mRetryCount = 0
```

## cast が見やすい
Android を開発していると View から findViewById して cast することが多々あると思いますが
Java の場合 `()` で囲みまくる事になって非常に見づらいという経験がありました
Kotlin はこの辺が見やすく書ける印象です

```kotlin:cast
        if (view == null) {
            view = LayoutInflater.from(context).inflate(R.layout.listview_data_book, parent, false)
            viewHolder.title = view?.findViewById(R.id.title) as TextView
            viewHolder.author = view?.findViewById(R.id.author) as TextView
            viewHolder.thumbnail = view?.findViewById(R.id.thumbnail) as ImageView
            view.tag = viewHolder
        } else {
            viewHolder = view.tag as ViewHolder
        }
```

## lambda が見やすい
これだけでも導入して良かったと思える可読性
ただし、以下のコードは読みづらい模様

```kotlin:lambda
    override fun onRequestPermissionsResult(requestCode: Int,
                                            permissions: Array<out String>,
                                            grantResults: IntArray) {
        when (requestCode) {
            ACCESS_FINE_LOCATION_REQUEST_CODE -> {
                if (permissions.size == 1 &&
                        permissions[0] == ACCESS_FINE_LOCATION &&
                        grantResults[0] == PERMISSION_GRANTED) {
                    mMap?.isMyLocationEnabled = true
                }
                LibSearchShowcase().moveToMyLocation(this, mMyLocationButton, mSearchLibrary)
            }
            CALL_PHONE_REQUEST_CODE -> {
                if (permissions.size == 1 &&
                        permissions[0] == CALL_PHONE &&
                        grantResults[0] == PERMISSION_GRANTED) {
                    mDerivedMap?.callLibrary(mIdForCallIntent)
                }
            }
        }
    }
```

# アプリ全般
アプリ作る前に考えたこととか
実装時に参考にしたものとか

## パッケージ構成
https://github.com/futurice/android-best-practices/blob/master/translations/Japanese/README.ja.md
デザイン関連、というとなんかイメージが違うが Android でパッケージ構成を考える上で参考にした

## SecretKey の扱い
https://github.com/googlemaps/android-samples/blob/master/AndroidWearMap/Wearable/src/debug/res/values/google_maps_api.xml
実装していて API の SecretKey をどうするかですが Google Map API と同じやり方に全て統一しました
Variants での debug, release の切り替えも違和感ないです

## Endless Scroll View
https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
ページングが必要な API 呼び出し時に使うことになると思う Endless Scroll View についてまとめてあります
とても参考になりました

## Runtime Permission の導入
Android 6.0 から利用出来るので導入
情報はさんざん出ていますが実装はややめんどいですね

# デザイン関連
私の場合デザインの経験がほとんどないため、プログラミングするよりも
デザイン関連に多くの時間を割いた気がします

## Material design に準拠する
デザインに関しては Material design に可能な限り準拠しました
迷った時にベストプラクティスが選択できるのはいいですね
https://material.google.com/

### Snackbars
アプリ内で API 通信の状態を通知するのに多用しました
以前は toasts を使っていましたが、Material design では Snackbars が推奨されているようです
https://material.google.com/components/snackbars-toasts.html

## Material icons を利用する
アプリを作成していると文字ではなくアイコンで視覚的に表現したい時が多々あります
モバイルデバイスだと、画面サイズの制限が厳しいのでアイコンにするケースが多い気がしました
Google がフリー提供しているアイコンを利用出来るので活用しました
https://design.google.com/icons/

## アプリアイコンを作成する
GIMP を使って四隅を丸くしたりしてそれっぽいアプリアイコンを作成しました
こういうのはサクッと作れるスキルが欲しいです


# 利用したライブラリ
使って便利だったライブラリをまとめています

## comile
release ビルドで有効になるライブラリ
### io.realm:realm-gradle-plugin
https://realm.io/
アプリ作成時にちょうど version1.0.0 がリリースされたので利用しました
最初は ActiveAndroid を利用していましたが、あんまりメンテナンスされてなさそうなのでやめました
ただ、Realm は最後にアプリサイズを抑えるためにリリースビルドに少し手を加えないといけないです
https://realm.io/docs/java/latest/#how-big-is-the-realm-library
上記のリンク先で記述されているように `build.gradle`に`splits`を加えないといけないです
加えなくても`apk`ファイルは生成できますがサイズが10MB近くになりました

### android:crashlytics
https://try.crashlytics.com/
個人の趣味アプリレベルで導入するか迷いましたが、簡単に導入できるので導入しました
アプリがクラッシュすると通知してくれる例のやつです
アプリダウンロード数が少ないのでクラッシュレポートは未だにみたことがありません

### firebase:firebase-core
https://firebase.google.com/
GoogleAnalytics の代わりとして利用しています
計測ツールです

### com.google.firebase:firebase-ads
https://firebase.google.com/docs/admob/android/native
AdMob 利用するために入れたライブラリ
ただしダウンロード数が少ないのでお察し
広告入れないなら必要ないです

### com.google.firebase:firebase-messaging
https://firebase.google.com/docs/cloud-messaging/
Firebase のセグメントに対して通知メッセージを送信できます
簡単にメッセージが送れることに感動したまま、終了
いつか「バージョンアップしたよ」みたいな通知したい

### com.github.deano2390:MaterialShowcaseView
https://github.com/deano2390/MaterialShowcaseView
ユーザに次の操作を促すためのチュートリアル作成に利用
もちろん一度表示されたら再表示はされないようになっています

### com.squareup.retrofit2
https://github.com/square/retrofit
アプリ内で呼び出す API 通信周りで利用
使いやすい
Endpoint, Model, Client, UI で分離されるので理解しやすい
Model では Moshi というライブラリを利用しています
https://github.com/square/moshi


#### Endpoint
```kotlin:Endpoint
import com.sampo02.libsearch.models.BookJson
import com.sampo02.libsearch.models.LibraryJson
import okhttp3.ResponseBody
import retrofit2.http.GET
import retrofit2.http.Query
import rx.Observable

interface Endpoint {
    @GET("/library?format=json&callback=no&limit=50")
    fun findLibraries(@Query("appkey") appKey: String,
                      @Query("geocode") geocode: String): Observable<List<LibraryJson>>

    @GET("/check?format=json&callback=no")
    fun findLibraryHolds(@Query("appkey") appKey: String,
                         @Query("isbn") isbn: String?,
                         @Query("systemid") systemId: String): Observable<ResponseBody>

    @GET("/check?format=json&callback=no")
    fun findLibraryHoldsWithInterval(@Query("appkey") appKey: String,
                                     @Query("session") session: String?): Observable<ResponseBody>

    @GET("?format=json&booksGenreId=001")
    fun findBooks(@Query("applicationId") applicationId: String,
                  @Query("affiliateId") affiliateId: String,
                  @Query("keyword") keyword: String,
                  @Query("page") page: Int,
                  @Query("hits") hits: String): Observable<BookJson>
}
```

#### Model
```kotlin:Model
data class LibraryJson(
        @Json(name = "systemid") val systemId: String?,
        @Json(name = "systemname") val systemName: String?,
        @Json(name = "libkey") val libKey: String?,
        @Json(name = "libid") val libId: String?,
        val short: String?,
        val formal: String?,
        @Json(name = "url_pc") val urlPc: String?,
        val address: String?,
        val pref: String?,
        val city: String?,
        val post: String?,
        val tel: String?,
        val geocode: String?,
        val category: String?,
        val image: String?,
        val distance: String?
)
```

#### Client
```kotlin:Client
fun buildClient(baseUrl: String): Endpoint {
    return Retrofit.Builder()
            .client(buildOkHttpClient())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().build()))
            .baseUrl(baseUrl)
            .build().create(Endpoint::class.java)
}

fun calilApiKey(context: Context): String {
    return context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
            .metaData.getString(Calil.API_KEY.string)
}
```

#### UI
```kotlin:UI
    fun searchLibraryHoldsWithInterval(session: String?) {
        buildClientForBrokenJson(Calil.URL_API_BASE.string).findLibraryHoldsWithInterval(calilApiKey(context), session)
                .subscribeOn(Schedulers.newThread())
                .subscribeOn(AndroidSchedulers.mainThread())
                .subscribe(object : Subscriber<ResponseBody>() {
                    override fun onNext(responseBody: ResponseBody?) {
                        updateAdapter(LibraryHoldJson(responseBody!!.string()))
                    }

                    override fun onError(e: Throwable?) {
                        mSnackBarFailed?.show()
                    }

                    override fun onCompleted() {
                        activity?.runOnUiThread {
                            mAdapter?.notifyDataSetChanged()
                        }
                    }
                })
    }
```

### com.github.ksoichiro:android-observablescrollview
https://github.com/ksoichiro/Android-ObservableScrollView
ScrollView で下にスクロールする時にヘッダを隠すやつをやりたかったので導入
Material design でもこの手法が推奨されている
https://material.google.com/patterns/scrolling-techniques.html


### com.google.android.gms:play-services-maps
https://developers.google.com/android/guides/setup
Google Play Service は全部入れるとそれだけで`apk`ファイルサイズが極端に増えるため
必要な分だけ導入
今回のアプリでは Map のみとなった

### com.jakewharton:kotterknife
https://github.com/JakeWharton/kotterknife
ButterKnife の Kotlin 版

## debugCompile
debug 時のみに有効にしたライブラリ
### com.facebook.stetho:stetho
http://facebook.github.io/stetho/
Chrome Dev Tools から Android アプリをデバッグできるライブラリ
発想がすごい
このライブラリ自体ではそこまで便利とはいえなかったが、
後述の API リクエスト関連、Realm の内部データ閲覧のベースとなるライブラリとなっている

### com.facebook.stetho:stetho-okhttp3
Chrome Dev Tools からデバッグしているアプリの通信内容を閲覧できる
Web アプリデバッグしているとよく利用するが Android アプリでもこれが見れるだけで問題の切り分けがだいぶ楽になる

### com.uphyca:stetho_realm
https://github.com/uPhyca/stetho-realm
Chrome Dev Tools からデバッグしているアプリの Realm 内データを閲覧できる
これがないと Realm 導入はやらなかったかも


# Play Store へアプリを公開
## $25を支払い Developer 登録する
お金を払います
アプリの広告でこの金額を稼げる気がしないです

## アプリ公開するために必要な文章を考える
これもちょっと時間かかりました
普段の業務じゃまず考えないことでしたが、こういう文章ももっと上手く書けるようになりたいですね
* Short description(80 characters)
* Full description(4,000 characters)

## アプリ公開に必要なスクリーンショットを撮る
作ったアプリのスクリーンショットを撮ります
あと、Feature Graphic(背景画像みたいなやつ) が必須です
![graphic_assets.png](https://qiita-image-store.s3.amazonaws.com/0/40532/e4793fa3-e697-3ead-0def-c5cd951133bb.png)

## apk ファイルをアップロードする
Realm をそのまま利用するとアプリサイズが大きくなるため各プラットフォームごとに apk ファイルを作成し、アップロードすることができます
![Screen Shot 2016-09-06 at 9.01.16 PM.png](https://qiita-image-store.s3.amazonaws.com/0/40532/8764a3d5-e05c-7525-3de7-1cb52ec9e8ca.png)

## インストールできるデバイスを細かく設定できる
業務だと会社名義で Play Store へのアプリ登録を管理しているので見えていなかった部分
Android アプリで辛いなぁと思ったのはなんだかんだで各デバイスに依存した問題が発生するということ
そういった問題を一時的にユーザに見せないためにインストールするデバイスを細かく設定できるこの機能は使えそうな気がしました
(例えば CrashReport がくる => マイナーバグフィックスを行う傍らで、該当の問題が発生する端末をインストール対象から除外 => マイナーバグフィックス完了とともに解除みたいな)
あとは画面解像度が特殊な端末などに対応するとか
https://support.google.com/googleplay/android-developer/answer/1286017

実際の管理画面はこんな感じです
![Screen Shot 2016-09-06 at 9.16.37 PM.png](https://qiita-image-store.s3.amazonaws.com/0/40532/aee9d360-5993-0379-8bd3-60e6fc56f9d2.png)