Kotlin编程开发Android运用程序的相关介绍:
- Kotlin编程之AndroidStudio(包括3.0与2.x版本)配置与使用
- Kotlin编程开发Android运用程序(Volley+Gson依赖库)
- Kotlin编程之Kotlin Android Extensions(扩展插件)
- Kotlin编程之Glide v4 Generated API(Unresolved reference GlideApp)
Kotlin编写MVP案例
使用Kotlin编程实现一个MVP案例,实现电影列表功能:
前期准备:
项目中,在Gradle中引入框架的配置如下和使用Kotlin Android扩展:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'//扩展插件
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
testCompile 'junit:junit:4.12'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
//Glide v4
compile 'com.github.bumptech.glide:glide:4.0.0-RC0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0-RC0'
//Retrofit 2.x
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
//OkHttp 3.x
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'
//RxJava 1.x
compile 'io.reactivex:rxjava:1.3.0'
compile 'io.reactivex:rxandroid:1.2.1'
}
须注意点:本篇中无findViewById获取控件对象,采用Kotlin Android Extensions方式获取控件对象。
AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
除之外,还有各种框架的混淆规则,这里省略不贴。
开始编写代码:
1. 项目通用的BasePrester和BaseView接口:
项目中通用的BasePresenter接口,具备订阅和取消订阅的行为:
interface BasePresenter{
/**
* 订阅
*/
fun subscribe()
/**
* 取消订阅
*/
fun unsubscribe()
}
项目中通用的BaseView接口,具备绑定Presenter的行为:
interface BaseView<T>{
/**
* 设置Presenter的方法
*/
fun setPresenter(presenter: T)
}
2. 开始编写Model中远程数据源
采用Retrofit作为网络异步框架,OkHttp作为传输层。
这里,采用搜索张艺谋的电影,在douban的API中:https://api.douban.com/v2/movie/search?q=张艺谋
Retrofit的请求和响应结果的配置:请求中发送的Body和Header,Respose的接口 :需要添加retrofit:adapter-rxjava库,实现适配器功能
interface DouBanService {
/**
* 这里返回一个Observable,用于RxJava结合使用
*/
@GET("{path}")
fun movieList(@Path("path") path:String, @QueryMap options: Map<String,String> ):Observable<MovieList>
}
Retrofit的操作类:添加OkHttp作为传输层,RxJava适配器,Gson解析的转换器。
object RemoteDataSource{
val baseURL="https://api.douban.com/v2/movie/"
val retrofit:Retrofit by lazy {
Retrofit.Builder().baseUrl(baseURL)
.client(OkHttpProvider.createOkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build()
}
var douBanService= retrofit.create(DouBanService::class.java)
fun movieList(subscriber:Subscriber<List<MovieList.Movie>>):Subscription{
val url="search"
var map= hashMapOf("q" to "张艺谋" )
var result= douBanService.movieList(url,map).flatMap {
item: MovieList ->Observable.just(item.subjects)
}.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
return result
}
}
OkHttp的配置:需 引入OkHttp库和OkHttp:logging-interceptor库
internal class OkHttpProvider{
companion object{
/**
* 自定义配置OkHttpClient
*/
fun createOkHttpClient():OkHttpClient{
var builder=OkHttpClient.Builder()
var loggingInterceptor=HttpLoggingInterceptor()
loggingInterceptor.level=HttpLoggingInterceptor.Level.BODY
builder.addInterceptor(loggingInterceptor)
return builder.build()
}
}
}
编写返回的实体类:
data class MovieList(var subjects:List<Movie>){
data class Movie(var year:String,var title:String,var images: Images){
data class Images(var small:String,var large:String)
}
}
加载图片的Glide v4的配置:
在上篇已经详细介绍了,请阅读Kotlin编程之Glide v4 Generated API(Unresolved reference GlideApp)。
3. 根据模块业务编写View和Presenter及它的实现类
业务上比较简单,搜索张艺谋的电影,显示在列表上。根据这个分析,写出这个模块的View和Presenter.
interface MovieListConstract{
interface Presenter:BasePresenter
/***
* 抽出Presenter对View的响应行为,在View接口中定义
*/
interface View :BaseView<Presenter>{
fun showToast(msg:String)//Toast提示
fun loadMovie(list: List<MovieList.Movie>)//加载电影数据
fun showDialog()//显示dialog
fun cancleDialog()//取消dialog
}
}
View的实现类: 在Fragment、Activity、Adapter中怎么使用Kotlin Android Extensions,请阅读上篇 Kotlin编程之Kotlin Android Extensions(扩展插件).
class MovieListFragment : Fragment(), MovieListConstract.View {
lateinit var presenters: MovieListConstract.Presenter
lateinit var dialog: ProgressDialog
lateinit var rootView: View
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
rootView = inflater.inflate(R.layout.fragment_movielist, container, false)
return rootView
}
override fun onResume() {
super.onResume()
presenters.subscribe()
}
override fun onPause() {
super.onPause()
presenters.unsubscribe()
}
override fun setPresenter(presenter: MovieListConstract.Presenter) {
this.presenters = presenter
}
override fun showToast(msg: String) {
Toast.makeText(activity.applicationContext, msg, Toast.LENGTH_SHORT).show()
}
/**
* 加载数据
*/
override fun loadMovie(list: List<MovieList.Movie>) {
//无findViewById(),直接引用控件
var recyclerView = rootView.movieList_recyclverView
recyclerView.layoutManager = LinearLayoutManager(activity)
recyclerView.adapter = MovieListAdapter(activity, list)
}
override fun showDialog() {
dialog = ProgressDialog(activity)
dialog.show()
}
override fun cancleDialog() {
if (dialog != null && dialog.isShowing) {
dialog.cancel()
}
}
companion object {
//静态对象
val instance = MovieListFragment()
//静态常量
val Tag = MovieListFragment::class.java.javaClass.simpleName
}
}
RecyclerView中Adapter:
import kotlinx.android.synthetic.main.movielist_item.view.*
/**
* Created by ${新根} on 2017/6/8.
* blog :http://blog.csdn.net/hexingen
*/
internal class MovieListAdapter(var context: Context, var list: List<MovieList.Movie>) : RecyclerView.Adapter<MovieListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ViewHolder(View.inflate(parent.context, R.layout.movielist_item, null))
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
GlideUtils.loadUrlImage(context,list[position].images.large,holder.imageView)
holder.title_Tv.text=list[position].title
}
override fun getItemCount() = list.size
internal class ViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) {
/**
* 这里使用Kotlin Android 扩展,省略了findViewById().
* 在最上面导入了import kotlinx.android.synthetic.main.movielist_item.view.*
*/
var imageView = rootView.movielist_item_iv
var title_Tv= rootView.movielist_item_tv
}
}
Presenter的实现类:
class MovieListPresenter(var view: MovieListConstract.View, var compositeSubscription: CompositeSubscription = defaultCompositeSubscription) : MovieListConstract.Presenter {
init {//init初始化模块
view.setPresenter(this)
}
//开始订阅
override fun subscribe() {
view.showDialog()
excuteTask()
}
//执行任务
fun excuteTask() {
var disposable=RemoteDataSource.movieList(object :Subscriber<List<MovieList.Movie>>() {
override fun onNext(t: List<MovieList.Movie>) = view.loadMovie(t)
override fun onError(e: Throwable) {
view.showToast(e.toString())
view.cancleDialog()
}
override fun onCompleted() {
view.showToast("加载完成")
view.cancleDialog()
}
})
this.compositeSubscription.add(disposable)
}
//取消订阅,释放资源
override fun unsubscribe() {
view.cancleDialog()
compositeSubscription.clear()
}
companion object {//采用伴随对象
//默认的值
val defaultCompositeSubscription get() = CompositeSubscription()
}
}
Activity中创建View和Presenter:
class MovieListActvitiy : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movielist)
//创建View实例
var view=MovieListFragment.instance
//创建Presenter实例
MovieListPresenter(view)
//添加fragment
supportFragmentManager.beginTransaction()
.add(R.id.movielist_content_layout,view,MovieListFragment.Tag).commit()
}
}
最终项目结构目录如下:
4. 效果图如下:
项目代码:https://github.com/13767004362/KotlinMVPDemo
MVP架构相关的介绍: