在上一篇文章中,我们完成了UI界面的编写
接下来我们就要把搜索的结果,显示在界面上。
在Android开发中有很多种方式访问网络,本次视频将向大家介绍Retrofit,
Retrofit由Square 开发的,它构建在OkHttp之上。它是一个流行的库,可以轻松地进行异步网络调用并将JSON数据处理为模型对象。
在Android开发中有很多种方式访问网络,本次视频将向大家介绍Retrofit,Retrofit由Square 开发的,它构建在OkHttp之上。它是一个流行的库,可以轻松地进行异步网络调用并将JSON数据处理为模型对象。本次视频您将了解Retrofit库的简单实用,Moshi解析Json等知识。
在使用Retrofit之前,我们需要先在项目中添加Retrofit库的依赖。编辑app/build.gradle文件,在dependencies闭包中添加如下内容:
//retrofitimplementation "com.squareup.retrofit2:retrofit:2.9.0"//moshiimplementation("com.squareup.moshi:moshi-kotlin:1.12.0")//retrofit with moshiimplementation "com.squareup.retrofit2:converter-moshi:2.9.0"//coilimplementation("io.coil-kt:coil-compose:1.3.2")//kotlin coroutineimplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"//viewmodeldef lifecycle_version = "2.3.1"implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")retrofit库的依赖,主要负责网络请求,允许我们发送GET,POST请求
moshi库的依赖,moshi库将帮助我们将json数据转换为kotlin对象
converter-moshi库,增加了对retrofit使用moshi进行JSON解析的支持
coil库,允许我们使用url加载网络图片,我们可以使用少量的代码,完成图片的加载,coil库也是采用kotlin编写
kotlin coroutine,我们使用kotlin coroutine 的Flow处理网路的异步请求
ViewModel依赖,使用ViewModel使视图和数据能够分离开
完成之后我们重新编译一下项目
Postman是查看API接口返回结果非常优秀的程序,我们启动Postman。
使用默认的GET方法,输入下面的URL地址https://api.map.baidu.com/weather/v1/?district_id=110100&data_type=all&ak=m5ABoErD6VuCKdyGfqoEjflYvSmn1XqR,然后单击Send。如下图:
在搜索结果中,将输出类型设置为JSON。您将看到格式良好的JSON显示:如下图:
接下来,我们将创建天气信息的数据类
数据类的创建,我们借助Kotlin插件,将Json字符串快速转换为Kotlin数据类代码
在model包下右键;如下图:
点击 Advanced,这个支持(几乎)各种JSON库注释(Gson、Jackson、Fastjson、MoShi和LoganSquare)这个我们选择MoShi
点击生成,如下图:
这样我们的数据类型就很方便的创建了。如下图:
接下来,编写我们的网络服务
object WeatherApiClient { private val BASE_URL = "https://api.map.baidu.com" private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() private val retrofit: Retrofit by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(MoshiConverterFactory.create(moshi)) .build() } val weatherApiService: WeatherApiService by lazy { retrofit.create(WeatherApiService::class.java) }}interface WeatherApiService{ @GET("/weather/v1/") suspend fun getWeatherData(@Query("district_id") district_id: String, @Query("data_type") data_type: String, @Query("ak") ak: String) : WeatherModel}在上面的代码中,我们创建一个私有的BASE_URL变量,我们需要为moshi构造器创建一个变量,添加KotlinJson的适配器工厂 (KotlinJsonAdapterFactory),创建Retrofit,这里使用by lazy 关键字创建Retrofit实例,这样仅在需要时进行初始化,传入BASE_URL,添加MoshiConverterFactory转换器工厂,然后构建.接下来,创建一个接口,获取api接口数据,这里我们创建一个函数,设置了查询参数,调用这个方法就会返回查询的数据。接下来,创建api接口的实例 这里也是使用by lazy关键字创建延迟加载的实例,通过创建好的Retrofit来创建api接口服务。
下面创建Repository(数据仓库)
class WeatherRepository { companion object{ fun getWeather(district_id: String, data_type: String, ak: String): Flow<WeatherModel> = flow { var weather = WeatherApiClient.weatherApiService.getWeatherData( district_id, data_type, ak ) emit(weather) }.flowOn(Dispatchers.IO) }}下面创建ViewModel
class WeatherViewModel: ViewModel() { val weatherData: MutableState<WeatherState> = mutableStateOf(WeatherState.Empty) init { getWeatherData("110100","all","m5ABoErD6VuCKdyGfqoEjflYvSmn1XqR"); } fun getWeatherData(district_id: String, data_type: String, ak: String){ viewModelScope.launch { WeatherRepository.getWeather(district_id,data_type,ak) .onStart { weatherData.value = WeatherState.Loading } .catch { e -> weatherData.value = WeatherState.Failure(e) } .collect { response -> weatherData.value = WeatherState.Success(response) } } }}在创建ViewModel之前,需要创建Model的状态类
sealed class WeatherState { class Success(val weather: WeatherModel) : WeatherState() class Failure(val error: Throwable) : WeatherState() object Loading : WeatherState() object Empty : WeatherState()}创建好之后,我们在Activity中使用ViewModel
class MainActivity : ComponentActivity() { private val weatherViewModel: WeatherViewModel by viewModels() @ExperimentalMaterialApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { WeatherAppTheme { Surface(color = MaterialTheme.colors.background) { setWeather(weatherViewModel = weatherViewModel) } } } }}添加数据请求的状态,如下图:
@ExperimentalMaterialApi@Composablefun setWeather(weatherViewModel: WeatherViewModel) { when (val result = weatherViewModel.weatherData.value) { is WeatherState.Success -> { Log.d("--result--",result.weather.toString()) } is WeatherState.Failure -> { Text(text = "${result.error}") } is WeatherState.Loading -> { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxSize() .background( brush = Brush.verticalGradient( colors = listOf( color1, color2 ) ) ) ) { Surface( onClick = { }, modifier = Modifier.fillMaxWidth(0.6f), color = Color.Transparent ) { Row( modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { CircularProgressIndicator( modifier = Modifier .height(16.dp) .width(16.dp), strokeWidth = 2.dp, color = Color.White ) Spacer(modifier = Modifier.width(10.dp)) ComposeText(text = "加载天气数据中...", textColor = Color.White, fontSize = 16.sp) } } } } WeatherState.Empty -> { } }}在上面的代码中,我们添加Loading,状态的UI。
下面在WeatherState.Success状态下,对UI界面赋值。
Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxSize() .background( brush = Brush.verticalGradient( colors = listOf( color1, color2 ) ) )) { Spacer(modifier = Modifier.height(20.dp)) LocationScreen(result.weather.result.location) Spacer(modifier = Modifier.height(40.dp)) NowScreen(result.weather.result.now) Spacer(modifier = Modifier.height(20.dp)) WeatherDaysScreen(result.weather.result.forecasts)}设置定位信息@Composablefun LocationScreen(location: WeatherModel.Result.Location) { ComposeText(text = "${location.city},${location.name}",fontSize = 30.sp)}2.设置当天天气信息
@Composablefun NowScreen(now: WeatherModel.Result.Now) { Column(horizontalAlignment = Alignment.CenterHorizontally) { val img_url = when (now.text) { "阴" -> "http://www.moji.com/templets/mojichina/images/weather/weather/w2.png" "雷阵雨" -> "http://www.moji.com/templets/mojichina/images/weather/weather/w4.png" "多云" -> "http://www.moji.com/templets/mojichina/images/weather/weather/w1.png" else -> "http://www.moji.com/templets/mojichina/images/weather/weather/w1.png" } Image( painter = rememberImagePainter(img_url), contentDescription = "", modifier = Modifier.size(100.dp)) ComposeText(text = "${now.temp}°", fontSize = 48.sp) } Surface( modifier = Modifier.fillMaxWidth(0.5f), color = color3, shape = RoundedCornerShape(48) ) { Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .padding(8.dp) ) { ComposeText(text = "${now.windDir}", textColor = colortext, fontSize = 12.sp) ComposeText(text = "${now.windClass}", textColor = colortext, fontSize = 12.sp) ComposeText(text = "湿度", textColor = colortext, fontSize = 12.sp) ComposeText(text = "${now.rh}%", textColor = colortext, fontSize = 12.sp) } }}3.设置未来5天的天气信息
@Composablefun WeatherDaysScreen(forecasts: List<WeatherModel.Result.Forecast>) { LazyRow { items(forecasts){ forecast -> WeatherItems(forecast) } }}@Composablefun WeatherItems(forecast: WeatherModel.Result.Forecast) { Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(start = 8.dp,end = 10.dp)) { ComposeText(text = "${forecast.week}",fontSize = 16.sp) Spacer(modifier = Modifier.height(18.dp)) val img_url = when (forecast.textDay) { "阴" -> "http://www.moji.com/templets/mojichina/images/weather/weather/w2.png" "雷阵雨" -> "http://www.moji.com/templets/mojichina/images/weather/weather/w4.png" "多云" -> "http://www.moji.com/templets/mojichina/images/weather/weather/w1.png" else -> "http://www.moji.com/templets/mojichina/images/weather/weather/w1.png" } Image( painter = rememberImagePainter(img_url), contentDescription = "", modifier = Modifier.size(50.dp)) Spacer(modifier = Modifier.height(2.dp)) ComposeText(text = "${forecast.high}°/${forecast.low}°",fontSize = 22.sp) }}