Kotlin Practice # 13 Compose Retrofit 串接API初探
11 min readApr 13, 2023
上一篇完成了底部導航的Bottom Bar Navigation的理解,在切換頁面有了初步的認識。
這篇則要理解在Jetpack Compose 如何透過網路串接API並取得資料。Android裡最有名的莫屬於Retrofit了。
這次練習要嘗試練習串接經濟部水庫水情API,顯示水庫名稱與目前的蓄水量。
這次要串接的API如下(API已於2022/09停止更新,但仍能串接資料)
https://fhy.wra.gov.tw/WraApi/v1/Reservoir/RealTimeInfo
首先先建立接資料的data Class,蓄水量百分比因可能會有null,先透過optional的值建立。
data class ReservoirInfo(
val StationNo : String,
val PercentageOfStorage : Double?,
val Time: String
)
另外建立map,對應上面StationNo的水庫編號
val reservoirDict: Map<String, String> = mapOf("10501" to "永和山水庫", "40203" to "龍溪壩", "50209" to "菱湖水庫", "50204" to "山西蓄水塘", "30802" to "阿公店水庫", "10216" to "中庄調整池", "30601" to "虎頭埤", "20405" to "士林堰", "40202" to "水簾壩", "20207" to "青山壩", "20202" to "石岡壩", "30603" to "玉峰攔河堰", "10206" to "榮華壩", "10801" to "粗坑溪攔河堰", "20101" to "鯉魚潭水庫", "50104" to "赤崁水庫", "30803" to "鳳山水庫", "50306" to "津沙水庫", "50202" to "田埔水庫", "50109" to "烏溝蓄水塘", "50108" to "小池水庫", "50303" to "珠螺水壩", "20507" to "武界壩", "20508" to "明湖下池", "20509" to "湖山水庫", "30901" to "高屏溪攔河堰", "50214" to "蘭湖", "50208" to "蓮湖水庫", "50201" to "太湖水庫", "30304" to "東口攔河堰", "20504" to "頭社水庫", "10208" to "六堵攔河堰", "20501" to "霧社水庫", "31201" to "牡丹水庫", "10213" to "羅好壩", "20201" to "德基水庫", "10203" to "西勢水庫", "30805" to "中正湖", "20206" to "馬鞍壩", "30306" to "內埔子", "20503" to "集集攔河堰", "10802" to "羅東攔河堰", "20205" to "天輪壩", "10405" to "寶山第二水庫", "10212" to "直潭壩", "50205" to "榮湖水庫", "10601" to "明德水庫", "50105" to "西安水庫", "30403" to "德元埤", "10401" to "寶山水庫", "50210" to "西湖水庫", "20505" to "明潭下池", "40701" to "酬勤水庫", "30401" to "白河水庫", "10204" to "新山水庫", "20204" to "八寶攔河堰", "40201" to "溪畔壩", "50203" to "陽明湖水庫", "10404" to "隆恩堰", "10209" to "桂山壩", "30302" to "蘭潭水庫", "10402" to "青草湖", "20203" to "谷關壩", "30305" to "隘寮堰", "50307" to "津沙1號壩", "50301" to "勝利水庫", "50309" to "東湧水庫", "31202" to "龍鑾潭", "20506" to "銃櫃壩", "10201" to "石門水庫", "50206" to "擎天水庫", "10210" to "三峽攔河堰", "30301" to "仁義潭水庫", "10205" to "翡翠水庫", "10503" to "大埔水庫", "30502" to "曾文水庫", "30503" to "南化水庫", "30501" to "烏山頭水庫", "50212" to "金湖水庫", "20510" to "劍潭水庫", "10207" to "鳶山堰", "10214" to "阿玉壩", "50304" to "儲水沃上壩", "30801" to "澄清湖水庫", "31002" to "甲仙攔河堰", "30402" to "尖山埤", "31301" to "成功水庫", "10215" to "木瓜壩", "50106" to "七美水庫", "50103" to "東衛水庫", "10211" to "青潭堰", "30504" to "鏡面水庫", "30602" to "鹽水埤", "50308" to "坂里水庫", "30303" to "鹿寮溪", "50305" to "儲水沃水庫", "50207" to "金沙水庫", "50213" to "瓊林水庫", "20502" to "日月潭水庫", "50302" to "邱桂山水庫", "30804" to "觀音湖", "50310" to "后沃水庫", "50107" to "澎湖海水淡化廠", "50102" to "興仁水庫")
接著是建立呼叫水庫API的ApiService,透過Retrofit取得API的資料。
interface ApiService {
//取得水庫的列表
@GET("WraApi/v1/Reservoir/RealTimeInfo")
suspend fun getReservoirInfoList() : List<ReservoirInfo>
//建立實例
companion object {
var apiService: ApiService? = null
fun getInstance() : ApiService {
if (apiService == null) {
apiService = Retrofit.Builder()
.baseUrl("https://fhy.wra.gov.tw/")
.addConverterFactory(GsonConverterFactory.create())
.build().create(ApiService::class.java)
}
return apiService!!
}
}
}
接續是建立一個viewModel,進行API的串接
class ReservoirViewModel : ViewModel() {
// 儲存一個水庫的列表
var reservoirInfoListResponse:List<ReservoirInfo> by mutableStateOf(listOf())
//儲存API呼叫錯誤資料
var errorMessage: String by mutableStateOf("")
fun getReservoirInfoList() {
viewModelScope.launch {
//透過try catch進行API呼叫
val apiService = ApiService.getInstance()
try {
val movieList = apiService.getReservoirInfoList()
reservoirInfoListResponse = movieList
}
catch (e: Exception) {
errorMessage = e.message.toString()
}
}
}
}
接著建立Lazy Column內的ReservoirView顯示水庫名稱、蓄水量、更新時間,另外建立一個function依照蓄水量百分比回傳相對應的顏色。
@Composable
fun ReservoirView(reservoirInfo: ReservoirInfo) {
val df = DecimalFormat("#.##")
val percentage = reservoirInfo.PercentageOfStorage ?: -1
Column {
Divider(color = Color.Blue)
Text(text = reservoirDict.getOrDefault(reservoirInfo.StationNo,""))
Row() {
Text(text = "蓄水量:")
Text(text = "${df.format(percentage)} %",
color = checkPercentWater(percentage.toDouble())
)
}
Text(text = "更新時間:${reservoirInfo.Time}")
}
}
fun checkPercentWater(percent: Double): Color {
return when (percent) {
-1.0 -> return Color.Black
in 0f..19.9999999999999999f -> return Color.Red
in 20f..39.9999999999999999f -> return Color.Magenta
in 0f..59.9999999999999999f -> return Color.Yellow
in 0f..79.9999999999999999f -> return Color.Green
in 0f..100f -> return Color.Blue
else -> return Color.Black
}
}
接著建立一個MainView,裡面放入LazyColumn依序放入透過API取得的水庫資訊。
@Composable
fun MainView(reservoirInfoList: List<ReservoirInfo>) {
LazyColumn {
itemsIndexed(items = reservoirInfoList) { index, item ->
ReservoirView(reservoirInfo = item)
}
}
}
最後將MainView放入到MainActivity
class MainActivity : ComponentActivity() {
val reservoirViewModel by viewModels<ReservoirViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KP13RetrofitAPITheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainView(reservoirViewModel.reservoirInfoListResponse)
reservoirViewModel.getReservoirInfoList()
}
}
}
}
}
接下來就可以看看效果
透過Retrofit與MVVM來完成簡單的水庫資料API的串接,對於side project的MVVM架構與API串接也開始有了一些方向,持續加油!!