盒子
盒子
文章目录
  1. 界面
    1. 主界面
    2. 详情界面
    3. 订阅界面
  2. 网络请求
    1. 接口请求封装
    2. 图片加载封装
  3. Interface
  4. Delegated Properties
  5. Anko
  6. End

Kotlin最佳实践

前一段时间开发比较轻松,所以就研究了一下google主推的kotlin语言。我一直坚信,快速学习与掌握一门语言的最好方式就是实践,边开发项目边学习。这样才能快速的将学习的知识运用到实践中,从而发现问题,总结经验。如果你也想学习kotlin或者也正在学习kotlin,那么我们不妨可以携手共济,在实践中提升自己,快速掌握kotlin

下面我要介绍的项目是完全使用kotlin编写的一个关于新闻的App,由于是实践介绍,我这里主要是介绍一下项目中运用到的知识点,方便后续想要一起学习的同学们快速入门与运用。

界面

这是一个关于新闻的App,我这里主要包括三个界面,分别为新闻主界面、新闻详情界面与新闻订阅界面。下面简要介绍下每个界面运用到的知识点。

主界面

主界面为了展示不同的分类新闻,这里使用了TabLayout+ViewPager+Fragment来实现,因此主要是逻辑就是NewsCategoryFrament中的接口请求与数据处理。接口请求主要运用的是Retrofit+RxJava+RxAndroid。来看下界面图:

主界面

我这里只是简单的介绍项目,如果想要进一步了解kotlin的实现可以查看后面的源码地址

详情界面

由于使用的是国外的News Api所以,对于详情的展示这里使用的是一个WebView,全部通过原生的WebView来展示。后续查看源码会发现该处的代码比较简单。

详情界面

订阅界面

订阅界面主要是对主界面的分类News进行添加订阅与取消订阅操作。界面结构使用的是两个滑动的RecyclerView。实现它们间item的联动。

订阅界面

网络请求

上面主要介绍了界面相关的知识,下面来说下项目中使用的有关网络方面的知识。主要包括接口请求图片加载

接口请求封装

前面已经介绍过了,项目使用的是Retrofit+RxJava+RxAndroid来进行接口的请求,为了在项目中进行更好的调用,这里对接口请求进行了封装。主要code如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun <T> doGet(params: MutableMap<String, Any?>, tClass: Class<T>): Observable<T?> {
return initParams(params, tClass)!!.observeOn(Schedulers.io()).flatMap { tempString ->
if (tClass == NewsArticleListModel::class.java) {
getNewsApi().getArticles(tempString).subscribeOn(Schedulers.io())
} else {
getNewsApi().getSources(tempString).subscribeOn(Schedulers.io())
}
}.flatMap { newsResponse ->
if (newsResponse is NewsArticleListModel && newsResponse.status.equals("ok")
|| (newsResponse is NewsSourcesListModel && newsResponse.status.equals("ok"))) {
val json = getGson()?.toJson(newsResponse!!)
if (json.isNullOrEmpty()) return@flatMap null!!
val data = getGson()?.fromJson(json, tClass)
Observable.just(data)
} else {
Observable.error<T> { Exception() }
}
}.observeOn(AndroidSchedulers.mainThread())
.doOnError { t -> Log.d("TAG", "throwable: " + t) }
}

由于数据可以来自与网络与本地,所以这里将来源屏蔽,请求抽象成接口。方便以后本地数据请求的加入(目前还没有引入本地数据库,后续有时间将引入)。分别为RequestCommandProvider接口,对应的实现就是NewsReqeustCommandNewsProvider。因此最终的调用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun setupRequest(refresh: Boolean) {
val params = mutableMapOf<String, Any?>(
NewsConfig.REQUEST_SOURCE to id,
NewsConfig.REQUEST_SORT_BY to NewsConfig.SortBy.TOP.sort
)
requestCommand = NewsRequestCommand(params, NewsArticleListModel::class.java,
object : DefaultDisposableObserver<NewsArticleListModel>() {
override fun onNext(t: NewsArticleListModel) {
if (refresh) swipeRefreshLayout.isRefreshing = false
dataList.clear()
dataList.addAll(t.articles)
recyclerView.adapter.notifyDataSetChanged()
}

override fun onError(e: Throwable) {
super.onError(e)
if (refresh) swipeRefreshLayout.isRefreshing = false
}
})
requestCommand?.execute()
}

自己最近没有时间,手上的项目将要大改版。如果客官们有兴趣的话可以在GithubStart/Fork提交PR

图片加载封装

图片加载使用的是facebookfresco,为了全局使用统一的图片加载,这里使用kotlinExtension Functions特性,对图片加载进行封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun SimpleDraweeView.displayImage(url: String?, width: Int = 200, listener: BaseControllerListener<Any>? = null) {
if (url == null) return
this.hierarchy.setPlaceholderImage(R.drawable.news_default_bg)
val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
.setResizeOptions(ResizeOptions(width, width))
.build()
val controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(imageRequest)
.setAutoPlayAnimations(true)
.setControllerListener(listener)
.setOldController(this.controller)
.build()
this.controller = controller
}

所以最终的调用是这样的:

1
itemView.image.displayImage(url = urlToImage)

就是这么简单,当然参数可以动态选择

Interface

kotlin中的interfacejava中的interface有一个巨大的区别。我们都知道在javainterface中是不能够对方法进行实现,但在kotlin中的interface可以对方法进行具体实现。所以可以借助这一特性来实现一个统一的ToolBarManager,方便界面的导航设置。具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface ToolBarManager {
val toolBar: Toolbar
var toolBarTitle: String
set(value) {
toolBar.title = value
}
get() = toolBar.title.toString()

fun initToolBar() {
toolBar.inflateMenu(R.menu.menu)
toolBar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.subscribe -> toolBar.context.startActivity<SubscribeActivity>()
}
true
}
}

fun enableHomeAsUp(back: () -> Unit) {
toolBar.navigationIcon = DrawerArrowDrawable(toolBar.context)
toolBar.setNavigationOnClickListener { back() }
}

所以后续界面导航的实现,可以通过实现ToolBarManager接口即可。

Delegated Properties

我们都知道在开发过程中会遇到单例模式的编写,基本的kotlin编写如下:

1
2
3
4
5
6
7
8
9
10
11
class App : Application() {
companion object {
private var instance: Application? = null
fun instance() = instance!!
}

override fun onCreate() {
super.onCreate()
instance = this
}
}

对于kotlin也提供了standard Delegates的实现,其中可以通过lazy来进行单例模式的懒加载。

1
2
3
companion object {
val instance by lazy { NewsApp() }
}

或者是lateinit

1
2
3
4
5
6
7
8
9
10
11
class App : Application() {
companion object {
lateinit var instance: App
private set
}

override fun onCreate() {
super.onCreate()
instance = this
}
}

最后也可以通过自定义Delegated来实现,通过使用ReadWriteProperty<in R, T>接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
object DelegatesExt {
fun <T> notNullSingleValue(): ReadWriteProperty<Any?, T> = NotNullSingleValueVar()
}
class NotNullSingleValueVar<T> : ReadWriteProperty<Any?, T> {

private var value: T? = null

override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("${property.name} not initialized")
}

override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = if (this.value == null) value else throw IllegalStateException("${property.name} not initialized")
}
}

当对其进行设置值与获取值时,会调用相应的getValuesetValue方法,最终调用如下:

1
2
3
companion object {
var instance: NewsApp by DelegatesExt.notNullSingleValue<NewsApp>()
}

当然,项目中还对Preference进行了相似的操作。

Anko

其实使用kotlin进行Android开发也有相应的工具库。可以快速提升我们的开发效率,其中Anko就是一个不错的选择。它可以通过Anko DSL代码书写代替XML来进行UI布局。这是一个非常有效的特性,当然它也有许多有用的functions,可以大大提升我们的开发效率。例如:

1.find()代替findViewById()
2.longToast()代替原生Toast()
3.startActivity(vararg params: Pair<String, Any>代替原生startActivity()
4.Anko SQLite简化原生的SQLite的使用
5.Dialogs的简化使用

End

好了,大概就是这么多了。希望能够帮助想学与正在学习kotlin的客官们,少走弯路,快速成长。如有不足之处欢迎交流!

项目地址

支持一下
赞赏是一门艺术