この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
最近、弊社はカフェを始めたようです。
新章シーズン3は「実店舗カフェ”Developers.IO Cafe”のオープン」。『横田deGoプロジェクト』最新レポート in デブサミ2019 #devsumi | DevelopersIO
一般公開前の社内公開のときにはカフェのアプリはiOS版しかなった。Androidには人権がなかった。俺はコーヒーが飲みたかったんだ。iOSソースを読み注文できるだけのを5日で作った。飲んだ。うまかった。そして、その日、次の日にストアへのリリースが決まった。コーヒー飲みたいおじさんは、そのままAndroid担当者となり、そして今に至る。
自社アプリなので、コルーチンやAndroidXなど最新トピックをガンガン素振りしています!そしてカフェメンバーも募集しているらしいです。
社内外で横田deGo専任エンジニア募集してます!
— さとし? (@sato_shi) 2019年2月19日
アーキテクチャ
スピードが最優先のプロジェクトでカッチリ要件が決まっているわけでなく、カフェのスタッフ、お客様からフィードバックで改善していくため、仕様変更が激しいプロジェクトです。
正直MVPでもMVVMでもクリーンアーキテクチャでもなんでも、コーヒーを飲みたい要求は満たせるので問題ないのですが、カッチリしすぎず柔軟で、ある程度処理の関心分離できてばOK。AndroidにはDataBindingがあるからMVVMでいっかといった感じで選びました。MVPは結構やったことあるのであきちゃったのも原因としてあります。
UI(activity/Fragment) <-> Viewmodel <-> Repository(API/Sharedpreferences) を基本的なデーターフローであります。あとは、APIなどで使うクラスやUtil(拡張関数/BindingAdapter)があります。UseCaseを基本作らずRepositoryを直接ViewModelが触っています。腐敗が激しそうなところだけUseCase書いています!
各階層の依存改善はDI(Koin)で解決し、ファイルも長くなるのでそれぞれの層ごとに分けて入れています。
Applicationクラス
override fun onCreate() {
super.onCreate()
startKoin(
this,
listOf(
AppModule.module(),
RepositoryModule.module(),
ViewModelModule.module(),
NetworkModule.module(),
ApiModule.module(),
UseCaseModule.module()
)
)
}
API取得
OkHttp + Retrofit + Gson の構成です。RxJavaを使わずすべてコルーチンで処理しています。Gsonを使っているのは、moshiだと、API変化が激しいプロジェクトなので結構詰みそうなので、いい感じにゆるいGsonにしています。
- square/okhttp: An HTTP+HTTP/2 client for Android and Java applications.
- Retrofit
- google/gson: A Java serialization/deserialization library to convert Java Objects into JSON and back
Retrofitとコルーチンを組み合わせるためにAdapterをいれているのですが、これは将来的にいらない可能性があります。
非同期
RxJavaを使わずすべてコルーチンでやっています。たしかにパラダイムが非同期と中断なのでそれぞれで得意不得意があったり、比較するものではないかもしれないが、素振りなのでRxJava縛りでコルーチンでやっています。やりづらさがあるかもしれないです。
基本APIから取得しUIで表示するパターンをViewModelで実現する際にコルーチンを用いています。ViewModel以外のUIが絡まない処理は、suspendでかき、実際にViewModelでlaunchするときスコープを設定しています。ViewModel以外はsuspendで、asyncやjobを返さないようにする。あとで合成したりするときに楽かなっと思います。そこは別途コルーチン部分は詳しく書きます。
画像周り
画像は、Picassoを使っています。APIとPicassoで使うOkHttpは違うインスタンスをDIでいれており、Picassoで使うは、キャッシュを少しするように設定しています。
object NetworkModule {
fun module() = module {
//API用
single<OkHttpClient>("api") {
val builder = OkHttpClient.Builder()
.readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.addNetworkInterceptor(get())
if (BuildConfig.DEBUG) {
builder.addNetworkInterceptor(StethoInterceptor())
builder.addNetworkInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
})
}
builder.build()
}
single<OkHttpClient>("picasso") {
val builder = OkHttpClient.Builder()
.readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
if (BuildConfig.DEBUG) {
builder.addNetworkInterceptor(StethoInterceptor())
builder.addNetworkInterceptor(HttpLoggingInterceptor())
}
builder.cache(get("image_cache"))
builder.build()
}
single<Cache>("image_cache") {
CacheUtils.okHttpImageCache(androidContext())
}
}
}
UI周り
UI周りはなるべくマテリアルコンポーネントを使っています。しかし挑戦はしているが完全に対応できずにいるのでいい感じにアニメーションをつけてかっこよくしていきたいです。
implementation 'com.google.android.material:material:1.0.0'
Fragmentの遷移は、Navigation Editorをもりもり使っています。意外と素直だけどハマりどころ満載なので、素振りは必要です。案件で使うか迷うが、知識が足りないだけかもしれない。遷移がギクシャクしているところがあるので、いい方法があったら教えてほしいです。
Get started with the Navigation component | Android Developers
デバッグツール
結構便利どころは入れてるはずで、何かあったら教えてほしいです!
- JakeWharton/timber: A logger with a small, extensible API which provides utility on top of Android's normal Log class.
- Stetho
- Hyperion-Android
Assets
svgとwebpを使います。こちらは両方共Android Studioで変換ツールなどがあるので積極的に使いましょう。脱png。 - Vector Asset Studio - Create WebP images | Android Developers
特に決まっていないがアイコン類はマテリアルアイコンからSVGで拾ってきて使うのが良いかもしれない。神プラグインを使わせていただくと簡単です。ありがたく拝みながら使いましょう。
まとめ
素振りしまくっていて安定があまりないかもしれないが、新しいことにチャレンジできる案件なので、刺激が欲しい方ぜひよろしくお願い致します!