0
0

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 1 year has passed since last update.

JetpackCompose x Retrofit x coroutine x MVVM x Hilt(part.2)

Last updated at Posted at 2023-07-12

気象庁の天気予報を取得するアプリを作ろう #3

前回は、地名を選択すると選択ダイアログを表示するところまで作成しました。
今回は、OfficeId(code?)で、天気予報を取得します。

使用するライブラリ

前回から何も変化はなしです。

app/build.gradle
    // retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

    // coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2'
    runtimeOnly 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'

    // gson
    implementation 'com.google.code.gson:gson:2.10.1'

    // hilt
    implementation "com.google.dagger:hilt-android:2.44"
    kapt "com.google.dagger:hilt-compiler:2.44"
    implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'

使用するAPI

以下の気象庁のAPIを使用して、天気予報を取得します。
090000の部分を、取得したい地域のOfficeId(code?)にすることで、その地域の天気予報が取得できます。

curl -X GET https://www.jma.go.jp/bosai/forecast/data/forecast/090000.json

Retrofit

APIの定義を追加します。
可変となるAPIのパスは、@Pathで指定します。
Gsonを使用してjsonをパースする先のオブジェクトとして、ForecastApiModelを作成しました。

ForecastApi.kt
    @GET("forecast/data/forecast/{office-id}.json")
    suspend fun getForecast(
        @Path("office-id") officeId: String,
    ): Response<List<ForecastApiModel>>

API-Repository

APIを呼び出すForecastRepositoryに、定義したAPIを追加します。

ForecastRepository.kt
    fun getForecast(officeId: String): Flow<Future<List<ForecastApiModel>>> {
        return apiFlow { forecastApi.getForecast(officeId = officeId) }
    }

モデルとadapterの定義

このAPIを使用するユースケースを作成します。がその前に、、、
後々、API経由でデータを取得した後に、Roomでストレージに保存して、10分くらいはストレージから取得したデータを使うようにしようと思ってまして、以下の様な構成を考えております。

  • APIのモデル - Adapter - アプリ内部のモデル
  • Roomのモデル - Adapter - アプリ内部のモデル

今回はRoomのことは考えず、アプリ内部用のForecastモデルと、APIの生データを流し込むForecastApiModel、変換アダプタのForecastAdapterクラスを作成します。

UseCase

OfficeIDをキーにして、APIから天気予報を取得するユースケースを作成します。

GetForecastUseCase.kt
interface GetForecastUseCase {
    suspend fun invoke(office: Office): Flow<Future<Forecast>>
}
GetForecastUseCaseImpl.kt
class GetForecastUseCaseImpl @Inject constructor(
    private val forecastRepository: ForecastRepository,
) : GetForecastUseCase {
    override suspend fun invoke(office: Office): Flow<Future<Forecast>> {
        return forecastRepository.getForecast(officeId = office.id).map { apiModelFuture ->
                when (apiModelFuture) {
                    is Future.Error -> {
                        Future.Error(apiModelFuture.error)
                    }

                    is Future.Success -> {
                        Future.Success(ForecastAdapter.adaptFromApi(apiModelFuture.value))
                    }

                    is Future.Proceeding -> {
                        Future.Proceeding
                    }

                    else -> {
                        Future.Idel
                    }
                }
            }
    }
}

ViewModel

ViewModelに、天気予報のStateを追加します。また、検索を行うと、ユースケース経由でデータを取得する様にします。

ForecastViewModel.kt
    var office: Office? by mutableStateOf(null)
        private set

    var forecastFuture: Future<Forecast> by mutableStateOf(Future.Idel)
        private set

      :

    fun searchForecast() {
        val office = office ?: return

        viewModelScope.launch {
            forecastFuture = Future.Proceeding
            getForecastUseCase.invoke(office).collectLatest {
                forecastFuture = it
                if (it is Future.Success) {
                    Log.d("Search!!", "${it.value}")
                }
            }
        }
    }

Screen

最後に、検索ボタンをタップした際の動作と、天気予報の表示部分を記述します。
ひとまずは、ダラダラっとForecast.toString()が画面に表示される様にします。

ForecastScreen.kt
            Box(
                modifier = Modifier.align(Alignment.End),
            ) {
                Button(onClick = {
                    viewModel.searchForecast()
                }) {
                    Text("検索")
                }
            }

            when (val forecastState = viewModel.forecastFuture) {
                is Future.Error -> {
                    forecastState.error.localizedMessage?.let { Text(it) } ?: Text("天気予報の取得に失敗")
                }

                is Future.Idel -> {
                    Text("検索を押してください")
                }

                is Future.Proceeding -> {
                    CircularProgressIndicator(
                        modifier = Modifier.size(60.dp), color = MaterialTheme.colorScheme.primary, strokeWidth = 10.dp
                    )
                }

                is Future.Success -> {
                    Text(forecastState.value.toString())
                }
            }

いざ実行

検索ボタンを押すと、ダラダラっと結果が表示されるようになりました。
全コードはこちらです

次回は、この見た目をどうにかしていこうと思います。
search_1_AdobeExpress.gif

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?