在上一篇文章中,我们完成了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")//retrofitwithmoshiimplementation"com.squareup.retrofit2:converter-moshi:2.9.0"//coilimplementation("io.coil-kt:coil-compose:1.3.2")//kotlincoroutineimplementation"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"implementation"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"//viewmodeldeflifecycle_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编写
kotlincoroutine,我们使用kotlincoroutine的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
点击生成,如下图:
这样我们的数据类型就很方便的创建了。如下图:
接下来,编写我们的网络服务
objectWeatherApiClient{privatevalBASE_URL="https://api.map.baidu.com"privatevalmoshi=Moshi.Builder().add(KotlinJsonAdapterFactory()).build()privatevalretrofit:Retrofitbylazy{Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(MoshiConverterFactory.create(moshi)).build()}valweatherApiService:WeatherApiServicebylazy{retrofit.create(WeatherApiService::class.java)}}interfaceWeatherApiService{@GET("/weather/v1/")suspendfungetWeatherData(@Query("district_id")district_id:String,@Query("data_type")data_type:String,@Query("ak")ak:String):WeatherModel}在上面的代码中,我们创建一个私有的BASE_URL变量,我们需要为moshi构造器创建一个变量,添加KotlinJson的适配器工厂(KotlinJsonAdapterFactory),创建Retrofit,这里使用bylazy关键字创建Retrofit实例,这样仅在需要时进行初始化,传入BASE_URL,添加MoshiConverterFactory转换器工厂,然后构建.接下来,创建一个接口,获取api接口数据,这里我们创建一个函数,设置了查询参数,调用这个***就会返回查询的数据。接下来,创建api接口的实例这里也是使用bylazy关键字创建延迟加载的实例,通过创建好的Retrofit来创建api接口服务。
下面创建Repository(数据仓库)
classWeatherRepository{companionobject{fungetWeather(district_id:String,data_type:String,ak:String):Flow<WeatherModel>=flow{varweather=WeatherApiClient.weatherApiService.getWeatherData(district_id,data_type,ak)emit(weather)}.flowOn(Dispatchers.IO)}}下面创建ViewModel
classWeatherViewModel:ViewModel(){valweatherData:MutableState<WeatherState>=mutableStateOf(WeatherState.Empty)init{getWeatherData("110100","all","m5ABoErD6VuCKdyGfqoEjflYvSmn1XqR");}fungetWeatherData(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的状态类
sealedclassWeatherState{classSuccess(valweather:WeatherModel):WeatherState()classFailure(valerror:Throwable):WeatherState()objectLoading:WeatherState()objectEmpty:WeatherState()}创建好之后,我们在Activity中使用ViewModel
classMainActivity:ComponentActivity(){privatevalweatherViewModel:WeatherViewModelbyviewModels()@ExperimentalMaterialApioverridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContent{WeatherAppTheme{Surface(color=MaterialTheme.colors.background){setWeather(weatherViewModel=weatherViewModel)}}}}}添加数据请求的状态,如下图:
@ExperimentalMaterialApi@ComposablefunsetWeather(weatherViewModel:WeatherViewModel){when(valresult=weatherViewModel.weatherData.value){isWeatherState.Success->{Log.d("--result--",result.weather.toString())}isWeatherState.Failure->{Text(text="${result.error}")}isWeatherState.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)}设置定位信息@ComposablefunLocationScreen(location:WeatherModel.Result.Location){ComposeText(text="${location.city},${location.name}",fontSize=30.sp)}2.设置当天天气信息
@ComposablefunNowScreen(now:WeatherModel.Result.Now){Column(horizontalAlignment=Alignment.CenterHorizontally){valimg_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天的天气信息
@ComposablefunWeatherDaysScreen(forecasts:List<WeatherModel.Result.Forecast>){LazyRow{items(forecasts){forecast->WeatherItems(forecast)}}}@ComposablefunWeatherItems(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))valimg_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)}}