Dagger2 (android support module)とretrofit2をつかってAPIレスポンスをListViewで表示する
掲題のとおりAndroidのListViewを表示してみる。
APIリクエストは retrofit
を使い天気情報を取得できるOpenWeatherMapのAPIを利用する。
DIにはDaggerを使い、2.11から有効なandroid support moduleを利用する。
APIをリクエストするServiceクラスをつくる
1
2
3
4
5
|
interface OpenWeatherMapService {
@GET("/data/2.5/forecast/daily?q=94043&mode=json&units=metric&cnt=7&APPID=XXXXX")
fun findForecastByDaily(): Observable<Forecasts>
}
|
- レスポンスの型は
Observable<Forecasts>
。型パラメータのForecastsは Parcelable
を実装したDTO。
APPID=XXXXX
はopenweathermapから取得したID
このServiceクラスをRepositoryクラスから呼び出し見通しの良いコードにするためにDIを利用していく。DIについては後述する。
Parcelableを実装したDTO(data class)
1
2
3
4
5
6
7
8
9
|
data class Forecasts(var cod: Int, var list: List<Forecast>) : Parcelable {
constructor(src: Parcel) : this(
cod = src.readInt(),
list = src.createTypedArrayList(Forecast.CREATOR)
)
// -
}
|
ActivityやFragmentにパラメータを渡すために Parcelable
を実装したdata classを用意する。
フィールドにプリミティブ型ではないオブジェクト型を使う場合は次のようにする。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
data class Forecast(var dt: Long, var temp: Temp, var weather: List<Weather>) : Parcelable {
constructor(src: Parcel) : this(
dt = src.readLong(),
temp = src.readParcelable(Temp::class.java.classLoader), // ← data class `Temp`
weather = src.createTypedArrayList(Weather.CREATOR) // ← List型のパラメータに data class `Weather`
)
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeLong(dt)
dest?.writeParcelable(temp, flags) // ← data class `Temp`
dest?.writeList(weather) // ← List型のパラメータに data class `Weather`
}
// -
}
|
MainActivityでDIする
MainActivityでOpenWeatherMapService
を提供するRepositoryクラスをInjectするまでの過程をまとてめていく。
RepositoryModuleをつくる
1
2
3
|
class OpenWeatherMapRepository(val openWeatherMapService: OpenWeatherMapService) {
fun findForecastByDaily() = openWeatherMapService.findForecastByDaily()
}
|
1
2
3
4
5
6
7
8
9
|
@Module
internal object RepositoryModule {
@Provides
@Singleton
@JvmStatic
fun provideOpenWeatherMapRepository(openWeatherMapService: OpenWeatherMapService) =
OpenWeatherMapRepository(openWeatherMapService)
}
|
OpenWeatherMapRepository
を提供するRepositoryModuleをつくった。
DataModuleをつくる
RepositoryModuleをIncludeしたDataModuleをつくる。このモジュールでRetrofitクライアントをビルドする。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
@Module(includes = arrayOf(RepositoryModule::class))
internal object DataModule {
@Provides
@Singleton
@JvmStatic
fun provideMoshi() = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
@Provides
@Singleton
@JvmStatic
fun provideOkHttp(): OkHttpClient = OkHttpClient.Builder()
.build()
@Provides
@Singleton
@JvmStatic
fun provideRetrofit(oktHttpClient: OkHttpClient, moshi: Moshi): Retrofit = Retrofit.Builder()
.client(oktHttpClient)
.baseUrl("http://api.openweathermap.org")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
@Provides
@Singleton
@JvmStatic
fun provideOpenWeatherMapService(retrofit: Retrofit) = retrofit.create(OpenWeatherMapService::class.java)
}
|
- JSONパーサにはKotlinサポートが入っているMoshiをつかう
@ContributesAndroidInjectorをつかいMainActivityへのInjectを定義する
Dagger 2.11の重要ポイントの1つ。ActivityへのInjectは @ContributesAndroidInjector
をつかいUiModuleをつくる。
1
2
3
4
5
6
|
@Module
internal abstract class UiModule {
@ContributesAndroidInjector
internal abstract fun contributeMainActivity(): MainActivity
}
|
Activityが増えたときには、ここにActivityへのInjectを追加する。
ApplicationComponentに AndroidInjector を継承させる
Dagger 2.11の重要ポイントの1つ。ApplicationクラスへInjectさせるためにApplicationComponentに AndroidInjector<KotlinApplication>
を継承させる。後述するApplicationの親クラスに dagger.android.support.DaggerApplication
を使うためmoduleにAndroidSupportInjectionModule
を追加する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Singleton
@Component(modules = arrayOf(AndroidSupportInjectionModule::class,
AppModule::class,
DataModule::class,
UiModule::class))
interface ApplicationComponent : AndroidInjector<KotlinApplication> {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: KotlinApplication): Builder
fun build(): ApplicationComponent
}
override fun inject(application: KotlinApplication)
}
|
Applicationクラスに DaggerApplicationを継承させ実装する
HasActivityInjector
を継承する流れを紹介するエントリもあるが DaggerApplication
はHasActivityInjector
の実装が含まれているのでこちらをつかう。
1
2
3
4
5
6
7
8
9
10
|
class KotlinApplication : DaggerApplication() {
override fun applicationInjector() = DaggerApplicationComponent.builder()
.application(this)
.build()
override fun onCreate() {
super.onCreate()
}
}
|
MainActivityにOpenWeatherMapRepositoryをInjectする
最後にMainActivityにOpenWeatherMapRepositoryをInjectする。Dagger 2.11の重要ポイントの1つ。InjectするためにはonCreateでAndroidInjection.inject(this)
を呼び出す。
1
2
3
4
5
6
7
8
9
|
class MainActivity : AppCompatActivity() {
@Inject lateinit var openWeatherMapRepository: OpenWeatherMapRepository
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
// -
}
|
APIレスポンスをListViewに表示する
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class MainActivity : AppCompatActivity() {
@Inject lateinit var openWeatherMapRepository: OpenWeatherMapRepository
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
openWeatherMapRepository.findForecastByDaily()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { forecasts ->
findViewById<ListView>(R.id.listview).let { view ->
view.adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
forecasts.list.map {
"%s - %s %s/%s".format(
DateUtils.formatDateTime(this, it.dt * 1000L, FORMAT_NO_YEAR),
it.weather.get(0).main, it.temp.min, it.temp.max)
})
}
}
}
}
|
ListのItemViewには simple_list_item_1
をつかって日にちと最高気温と最低気温を表示している。
まとめ
- Daggert2(android support module)のDIをまとめた。android support module以前のDI方法だとコピペコードが増える懸念があり登場した経緯を知ってなるほど、と思った。
- retrofitはシンプルな使い方までに留まっているので引き続き触っていきながら知見をまとめていきたい。
コード
このエントリまでのコードがPull Requestにまとまっていますので参考になれば嬉しいです。(初回のコミットなので不要なlayoutコードなどが散見してます。)
参考