“我报名参加金石方案1期应战——瓜分10万奖池,这是我的第37篇文章,点击检查活动概况”

本章记录一个根底的 demo 项目,运用 kotlin+协程+retrofit+okhttp3+MVVM 完成。

功能需求

调用气候 api,在主页显现气候情况。

大致流程

  1. api 恳求及实体剖析
  2. 网络恳求权限
  3. 增加 kotlin,协程,网络结构等依靠
  4. 网络结构 Retrofit+okhttp3
  5. 主页页面制作
  6. 根底类构建
  7. 调用接口并显现在当时页面

api 恳求及实体剖析

这里运用万维易源的数据源,首要注册并登录账号。

  1. 进入气候预报入口。
    安卓系列之 kotlin 项目实战--基础 demo
  2. 购买一个月的气候预报 api ,这里运用地址查询当时气候作为比如。
    安卓系列之 kotlin 项目实战--基础 demo
  3. api 恳求示例。
    http[s]://route.showapi.com/9-2?showapi_appid=替换自己的值&showapi_sign=替换自己的值&area=“杭州”
  4. 进入控制台,能够检查自己的 appId 和 sign。
    安卓系列之 kotlin 项目实战--基础 demo
  5. 若是没有,则增加,并创立 app,可调用接口选择全部。
    安卓系列之 kotlin 项目实战--基础 demo
  6. 构建 api 接口,拜访得到回来数据如下所示。
    安卓系列之 kotlin 项目实战--基础 demo
  7. 剖析回来数据。
    外层是固定格式,能够统一封装,需要获取当时的气候情况,取关键字 now 的内容即可。归纳总结为三个实体,具体内容如下:

通用网络恳求实体 CResponse

/**
 * 通用网络恳求
 */
data class CResponse<T>(
    @SerializedName("showapi_res_error")
    val msg: String,//错误提示
    @SerializedName("showapi_res_code")
    val code: Int,//错误码
    @SerializedName("showapi_res_body")
    val data: T//数据
)

气候实体

/**
 *  气候
 */
data class Weather(
    val time: Long,//预报发布时刻
    val now: WeatherDetail//气候概况
)

气候概况实体

/**
 * 气候概况
 */
data class WeatherDetail(
    val aqi: String,//空气指数
    val rain: String,//下雨时刻点
    val sd: String,//空气湿度
    val temperature: String,//气温
    @SerializedName("temperature_time")
    val temperatureTime: String,//取得气温的时刻
    val weather: String,//气候
    @SerializedName("weather_pic")
    val weatherPic: String,//气候小图标
    @SerializedName("wind_direction")
    val windDirection: String,//风向
    @SerializedName("windPower")
    val windPower: String//风力
)

网络恳求权限

在 AndroidManifest.xml 中增加网络恳求权限,代码如下:

<!--网络权限-->
<uses-permission android:name="android.permission.INTERNET" />

增加 kotlin,协程,网络结构等依靠

在 build.gradle 文件中增加依靠,代码如下:

// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.7.10"
// 协程中心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
// 协程Android支撑库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
implementation "androidx.activity:activity-ktx:1.2.3"
implementation "androidx.fragment:fragment-ktx:1.3.5"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
// okhttp
implementation "com.squareup.okhttp3:okhttp:4.9.0"
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'//日志拦截器
// retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"//支撑回来结果是string
implementation "com.squareup.retrofit2:converter-gson:2.9.0"//支撑回来结果是实体

网络结构 Retrofit+okhttp3

网络恳求封装,增加根底拜访地址,拦截器等,最终目的是能有个能够得到 service 的办法。

  1. 首要需要一个类,存放静态常量,例如根底拜访地址,appId 以及 sign。
/**
 * 通用数据
 */
object HttpConstant {
    /**
     * 拜访地址
     */
    const val BASE_HTTP = "https://route.showapi.com"
    /**
     * appID
     */
    const val APP_ID = "替换为万维易源的appId"
    /**
     * app_sign
     */
    const val APP_SIGN = "替换为万维易源的sign"
}
  1. 从恳求地址剖析,有两个公共参数(showapi_appid 和 showapi_sign)能够封装,构建通用参数拦截器。
    恳求地址
    http[s]://route.showapi.com/9-2?showapi_appid=替换自己的值&showapi_sign=替换自己的值&area=“杭州”
/**
 * 通用参数拦截器
 */
class CommonInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val oldRequest = chain.request()
        val httUrl = oldRequest.url
        val urlBuilder = httUrl.newBuilder()
        /** 增加公共参数 */
        urlBuilder.addQueryParameter("showapi_appid", HttpConstant.APP_ID)
        urlBuilder.addQueryParameter("showapi_sign", HttpConstant.APP_SIGN)
        val request = oldRequest
            .newBuilder()
            .url(urlBuilder.build())
            .build()
        return chain.proceed(request)
    }
}
  1. 构建 Retrofit 管理类,得到 service 办法。
/**
 * Retrofit管理类
 */
object RetrofitManager {
    /**
     * okhttpClient
     */
    private val okhttpClient: OkHttpClient
        get() = OkHttpClient.Builder()
            .addInterceptor(CommonInterceptor())//通用参数拦截器
            .addInterceptor(HttpLoggingInterceptor())//日志拦截器
            .followRedirects(true)
            .build()
    /**
     * 构建service
     */
    fun <T> getService(serviceClass: Class<T>): T {
        val retrofit = Retrofit.Builder().apply {
            baseUrl(HttpConstant.BASE_HTTP)//根底拜访地址
            client(okhttpClient)
            addConverterFactory(GsonConverterFactory.create())//Gson转换工厂
            addConverterFactory(ScalarsConverterFactory.create())//String转换工厂
        }.build()
        return retrofit.create(serviceClass)
    }
}

主页页面制作

主界面显现当时的气候情况即可,运用 databinding 办法,代码如下。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="com.elaine.little_project.MainViewModel" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.weatherData.weather}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

根底类构建

在项目构架过程中,都会习惯性地将一些公共办法或者属性进行封装操作,便利后续运用。

  1. 抽象类 BaseViewModel,承继 ViewModel(),定义一个初始化数据的办法。
/**
 * 根底ViewModel
 */
abstract class BaseViewModel : ViewModel() {
    /** 初始化数据 */
    abstract fun start()
}
  1. 抽象类 BaseActivity,自定绑定 ViewModel 和 databinding,避免过多重复代码,其他的 Activity 承继 BaseActivity 即可。
/**
 * 根底类Activity
 */
abstract class BaseActivity<VM : BaseViewModel, VB : ViewDataBinding>(private val contentViewResId: Int) :
    AppCompatActivity() {
    lateinit var mViewModel: VM
    lateinit var mBinding: VB
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initViewModel()
        initDataBinding()
        initView()
        initData()
    }
    /**
     * 初始化ViewModel
     */
    private fun initViewModel() {
        //注意:actualTypeArguments[0] 0-->指上面BaseActivity<VM : BaseViewModel, VB : ViewDataBinding>的VM放在第一个
        val type: Class<VM> =
            (this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<VM>
        mViewModel = ViewModelProvider(this).get(type)
        mViewModel.start()
    }
    /**
     * 初始化dataBinding
     */
    private fun initDataBinding() {
        mBinding = DataBindingUtil.setContentView(this, contentViewResId)
        mBinding.apply {
            //绑定生命周期
            lifecycleOwner = this@BaseActivity
            //mBinding绑定viewModel
            setVariable(BR.viewModel, mViewModel)
        }
    }
    /**
     * 初始化UI
     */
    abstract fun initView()
    /**
     * 初始化数据
     */
    abstract fun initData()
}

调用接口并显现在当时页面

  1. 接口文件 Api,这里是挂起函数。
/**
 * api接口
 */
interface Api {
    /**
     * 恳求气候
     * @param area 地址 eg:杭州
     */
    @FormUrlEncoded
    @POST("/9-2")
    suspend fun getWeather(
        @Field("area") area: String,
    ): CResponse<Weather>
}
  1. api 接口完成类 WeatherRepository,通过 Retrofit 管理器获取 service,然后调用气候接口。
/**
 * api接口完成类
 */
object WeatherRepository : Api {
    /** 获取service */
    private val service by lazy { RetrofitManager.getService(Api::class.java) }
    /**
     * 恳求气候
     * @param area 地址 eg:杭州
     */
    override suspend fun getWeather(area: String): CResponse<Weather> {
        return service.getWeather(area)
    }
}
  1. MainActivity 对应的 MainViewModel,利用 viewModelScope 进行网络恳求,其是一个协程。
/**
 * viewModel
 */
class MainViewModel : BaseViewModel() {
    /** 气候数据 */
    var weatherData: MutableLiveData<WeatherDetail> = MutableLiveData()
    override fun start() {
    }
    /**
     * 获取气候数据
     */
    fun getWeather() {
        viewModelScope.launch {
            val result = WeatherRepository.getWeather("杭州")
            weatherData.value = result.data.now
        }
    }
}
  1. 主页 MainActivity,主页就是恳求接口即可。
/**
 * 主页
 */
class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(R.layout.activity_main) {
    override fun initView() {
    }
    override fun initData() {
        getData()
    }
    /**
     * 获取数据
     */
    private fun getData() {
        mViewModel.getWeather()
    }
}

项目效果

安卓系列之 kotlin 项目实战--基础 demo

项目 github 地址

github.com/ElaineTaylo…

具体内容在 little_project 项目中