Overview
Android アプリを個人で作ってみたので、その感想をまとめてみます
開発言語は Kotlin を利用
今回作ったアプリは全て Kotlin を利用して開発を行いました
業務では Java で書いたことがあったので違いとかを簡単に書いてみます
String 周りが使いやすい
Java ではStringBuilder()
を使うところですが Kotlin では変数をそのまま参照できるので
書いてて見やすいと感じました
fun buildGeocode(): String {
return "${mMap?.cameraPosition?.target?.longitude},${mMap?.cameraPosition?.target?.latitude}"
}
forEach が扱いやすい
filter
が便利でした
以下のサンプルコードではあまり扱いやすい感じがしないですね
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}")
}
}
}
強力な型推論
このおかげでタイピング数がだいぶ減らせた気がします
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 はこの辺が見やすく書ける印象です
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 が見やすい
これだけでも導入して良かったと思える可読性
ただし、以下のコードは読みづらい模様
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
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
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
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
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(背景画像みたいなやつ) が必須です
apk ファイルをアップロードする
Realm をそのまま利用するとアプリサイズが大きくなるため各プラットフォームごとに apk ファイルを作成し、アップロードすることができます
インストールできるデバイスを細かく設定できる
業務だと会社名義で Play Store へのアプリ登録を管理しているので見えていなかった部分
Android アプリで辛いなぁと思ったのはなんだかんだで各デバイスに依存した問題が発生するということ
そういった問題を一時的にユーザに見せないためにインストールするデバイスを細かく設定できるこの機能は使えそうな気がしました
(例えば CrashReport がくる => マイナーバグフィックスを行う傍らで、該当の問題が発生する端末をインストール対象から除外 => マイナーバグフィックス完了とともに解除みたいな)
あとは画面解像度が特殊な端末などに対応するとか
https://support.google.com/googleplay/android-developer/answer/1286017