首次开发:Jetpack Compose项目集成weatherapi.com API全流程指导
完整分步指导:从WeatherAPI获取数据到LazyColumn展示
1. 获取WeatherAPI密钥
- 注册weatherapi.com免费账号,登录后进入后台
- 在"My API Keys"页面复制专属API密钥,注意免费版有请求次数限制,开发时控制请求频率
2. 完善项目依赖(若未完全配置)
确保build.gradle(Module level)包含以下核心依赖:
// Retrofit核心库 implementation 'com.squareup.retrofit2:retrofit:2.9.0' // Gson JSON解析转换器 implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // OkHttp网络客户端(含日志拦截器,用于调试) implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0' // Jetpack Compose组件(用于LazyColumn) implementation platform('androidx.compose:compose-bom:2023.08.00') implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.material3:material3' // ViewModel与协程(处理异步请求) implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' // Coil图片加载库(用于天气图标) implementation 'io.coil-kt:coil-compose:2.4.0'
3. 定义API接口与数据模型
3.1 编写Retrofit API接口
根据WeatherAPI官方文档,定义核心请求接口:
interface WeatherApiService { // 获取当前天气 @GET("current.json") suspend fun getCurrentWeather( @Query("key") apiKey: String, @Query("q") location: String, @Query("aqi") aqi: String = "no" ): CurrentWeatherResponse // 获取未来N天预报(默认3天) @GET("forecast.json") suspend fun getForecastWeather( @Query("key") apiKey: String, @Query("q") location: String, @Query("days") days: Int = 3, @Query("aqi") aqi: String = "no" ): ForecastWeatherResponse }
3.2 构建数据模型(Data Class)
匹配WeatherAPI返回的JSON结构,创建精简版数据类(可按需扩展字段):
// 当前天气响应体 data class CurrentWeatherResponse( val location: Location, val current: Current ) // 预报响应体 data class ForecastWeatherResponse( val location: Location, val current: Current, val forecast: Forecast ) data class Location( val name: String, val country: String, val localtime: String ) data class Current( val temp_c: Double, val condition: Condition, val humidity: Int, val wind_kph: Double ) data class Condition( val text: String, val icon: String ) data class Forecast( val forecastday: List<ForecastDay> ) data class ForecastDay( val date: String, val day: Day ) data class Day( val maxtemp_c: Double, val mintemp_c: Double, val condition: Condition )
4. 创建Retrofit实例
用单例模式封装Retrofit初始化,添加日志拦截器便于调试:
object RetrofitInstance { private const val BASE_URL = "https://api.weatherapi.com/v1/" private val okHttpClient = OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) .build() val api: WeatherApiService by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() .create(WeatherApiService::class.java) } }
5. 实现数据请求逻辑(ViewModel)
用ViewModel管理请求状态,处理异步调用与异常:
class WeatherViewModel : ViewModel() { private val _weatherState = MutableStateFlow<WeatherState>(WeatherState.Loading) val weatherState: StateFlow<WeatherState> = _weatherState.asStateFlow() // 建议从local.properties读取API密钥,不要硬编码 private val API_KEY = "你的API密钥" fun fetchWeather(location: String) { viewModelScope.launch { try { _weatherState.value = WeatherState.Loading val forecastData = RetrofitInstance.api.getForecastWeather(API_KEY, location) _weatherState.value = WeatherState.Success(forecastData) } catch (e: Exception) { _weatherState.value = WeatherState.Error(e.message ?: "请求失败") } } } sealed class WeatherState { object Loading : WeatherState() data class Success(val data: ForecastWeatherResponse) : WeatherState() data class Error(val message: String) : WeatherState() } }
6. 用LazyColumn展示数据(Compose UI)
编写Compose界面,根据状态切换加载、成功、错误视图:
@Composable fun WeatherScreen(viewModel: WeatherViewModel = viewModel()) { val weatherState by viewModel.weatherState.collectAsState() var inputLocation by remember { mutableStateOf("Beijing") } Column( modifier = Modifier .fillMaxSize() .padding(16.dp) ) { // 城市搜索栏 TextField( value = inputLocation, onValueChange = { inputLocation = it }, label = { Text("输入城市名") }, modifier = Modifier.fillMaxWidth(), trailingIcon = { IconButton(onClick = { viewModel.fetchWeather(inputLocation) }) { Icon(Icons.Filled.Search, contentDescription = "查询天气") } } ) Spacer(modifier = Modifier.height(16.dp)) when (weatherState) { is WeatherViewModel.WeatherState.Loading -> { CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally)) } is WeatherViewModel.WeatherState.Success -> { val data = (weatherState as WeatherViewModel.WeatherState.Success).data // 当前天气卡片 CurrentWeatherCard(data.location, data.current) Spacer(modifier = Modifier.height(16.dp)) // 未来预报列表(LazyColumn) Text(text = "未来3天预报", style = MaterialTheme.typography.titleMedium) LazyColumn(modifier = Modifier.fillMaxWidth()) { items(data.forecast.forecastday) { day -> ForecastItem(day) } } } is WeatherViewModel.WeatherState.Error -> { Text( text = (weatherState as WeatherViewModel.WeatherState.Error).message, color = MaterialTheme.colorScheme.error, modifier = Modifier.align(Alignment.CenterHorizontally) ) } } } } @Composable fun CurrentWeatherCard(location: Location, current: Current) { Card(modifier = Modifier.fillMaxWidth(), elevation = CardDefaults.cardElevation()) { Column(modifier = Modifier.padding(16.dp)) { Text(text = "${location.name}, ${location.country}", style = MaterialTheme.typography.titleLarge) Text(text = "本地时间: ${location.localtime}") Spacer(modifier = Modifier.height(8.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Text(text = "${current.temp_c}°C", style = MaterialTheme.typography.displayMedium) Spacer(modifier = Modifier.width(16.dp)) Image( painter = rememberAsyncImagePainter(model = "https:${current.condition.icon}"), contentDescription = current.condition.text, modifier = Modifier.size(64.dp) ) Text(text = current.condition.text, style = MaterialTheme.typography.titleMedium) } Spacer(modifier = Modifier.height(8.dp)) Text(text = "湿度: ${current.humidity}%") Text(text = "风速: ${current.wind_kph} km/h") } } } @Composable fun ForecastItem(day: ForecastDay) { Card(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), elevation = CardDefaults.cardElevation()) { Column(modifier = Modifier.padding(16.dp)) { Text(text = day.date, style = MaterialTheme.typography.titleMedium) Spacer(modifier = Modifier.height(8.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Text(text = "最高: ${day.day.maxtemp_c}°C") Spacer(modifier = Modifier.width(16.dp)) Text(text = "最低: ${day.day.mintemp_c}°C") Spacer(modifier = Modifier.width(16.dp)) Image( painter = rememberAsyncImagePainter(model = "https:${day.day.condition.icon}"), contentDescription = day.day.condition.text, modifier = Modifier.size(48.dp) ) Text(text = day.day.condition.text) } } } }
额外注意事项
- 在
AndroidManifest.xml添加网络权限:<uses-permission android:name="android.permission.INTERNET" /> - 上线前关闭OkHttp的日志拦截器,避免泄露敏感信息
- 补充无网络状态的判断与提示,提升用户体验
内容的提问来源于stack exchange,提问作者SYNTHXX
相关产品推荐
相关产品推荐

