50
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-09-05

Overview

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

開発言語は Kotlin を利用

今回作ったアプリは全て Kotlin を利用して開発を行いました
業務では Java で書いたことがあったので違いとかを簡単に書いてみます

String 周りが使いやすい

Java ではStringBuilder()を使うところですが Kotlin では変数をそのまま参照できるので
書いてて見やすいと感じました

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

forEach が扱いやすい

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

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}")
                }
            }
        }

強力な型推論

このおかげでタイピング数がだいぶ減らせた気がします

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 はこの辺が見やすく書ける印象です

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 が見やすい

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

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.gradlesplitsを加えないといけないです
加えなくても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

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

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

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

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

apk ファイルをアップロードする

Realm をそのまま利用するとアプリサイズが大きくなるため各プラットフォームごとに apk ファイルを作成し、アップロードすることができます
Screen Shot 2016-09-06 at 9.01.16 PM.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

50
52
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?