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) } }
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
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() } }
最终项目结构目录如下: