Kotlin 的類型系統旨在從咱們的代碼中消除 NullPointerException。android
Kotlin基於Java的空指針提出了一個空安全的概念,即每一個屬性默認不可爲null。編程
例如:安全
var a: String = "test kotlin"
a = null //編譯錯誤
複製代碼
若是要容許爲空,咱們須要手動聲明一個變量爲可空字符串類型,寫爲String?閉包
var a: String? = "test kotlin"
a = null //編譯成功
複製代碼
!!是非空斷言運算符。將任何值轉換爲非空類型,若該值爲空則拋出異常。app
object Test {
var s:String?=null
@JvmStatic
fun main(args: Array<String>) {
println(s!!.length)
}
}
複製代碼
執行上述代碼會拋出以下異常。框架
Exception in thread "main" kotlin.KotlinNullPointerException
複製代碼
在App快要發佈時,咱們會進行檢查儘可能避免使用「!!」,轉而考慮使用lateinit或者let函數來代替它。ide
在某個類中,若是某些成員變量沒辦法在一開始就初始化,而且又不想使用可空類型(也就是帶?的類型)。那麼,可使用lateinit來修飾它。函數
被lateinit修飾的變量,並非不初始化,它須要在生命週期流程中進行獲取或者初始化。post
若是訪問未初始化的 lateinit 變量會致使 UninitializedPropertyAccessException。字體
let函數把當前對象做爲閉包的it參數,返回值是函數裏面最後一行,或者指定return。它看起來有點相似於run函數。
let函數跟run函數的區別是:let函數在函數內能夠經過 it 指代該對象。
/** * Calls the specified function [block] with `this` value as its argument and returns its result. */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
複製代碼
跟?結合使用, let函數能夠在對象不爲 null
的時候執行函數內的代碼,從而避免了空指針異常的出現。
通常是這樣使用:
?.let {
....
}
複製代碼
在使用Kotlin高效地開發Android App(二)中,曾經介紹過結合run和apply函數一塊兒使用的方式。其實,裏面使用了「!!」是有隱患的。
viewModel.email.run {
if (value!!.isEmpty()) {
toast(resources.getString(R.string.you_have_not_completed_the_email_address)).show()
return@onClickRight
}
if (!Util.checkEmail(value!!)) {
toast(resources.getString(R.string.the_email_format_you_have_filled_is_incorrect)).show()
return@onClickRight
}
viewModel
}.subject.run {
if (value!!.isEmpty()) {
toast(resources.getString(R.string.you_have_not_completed_the_feedback_subject)).show()
return@onClickRight
}
viewModel
}.content.apply {
if (value!!.isEmpty()) {
toast(resources.getString(R.string.you_have_not_completed_the_details)).show()
return@onClickRight
}
}
複製代碼
可使用let函數進行優化,避免出現空指針的狀況。
viewModel.email.run {
value?.let {
if (it.isEmpty()) {
toast(string(R.string.you_have_not_completed_the_email_address)).show()
return@onClickRight
}
if (!Util.checkEmail(it)) {
toast(string(R.string.the_email_format_you_have_filled_is_incorrect)).show()
return@onClickRight
}
}
viewModel
}.subject.run {
value?.let {
if (it.isEmpty()) {
toast(string(R.string.you_have_not_completed_the_feedback_subject)).show()
return@onClickRight
}
}
viewModel
}.content.apply {
value?.let {
if (it.isEmpty()) {
toast(string(R.string.you_have_not_completed_the_details)).show()
return@onClickRight
}
}
}
複製代碼
在Kotlin中,函數能夠擁有默認參數,這樣一來就再也不須要像Java那樣爲了默認參數而寫一大長串重載函數了。
例如,咱們使用RxBinding時,可能會考慮到防止UI控件被重複點擊,因而寫下了這樣的Transformer
/** * 防止重複點擊的Transformer */
@JvmStatic
fun <T> preventDuplicateClicksTransformer(windowDuration:Long=1000,timeUnit: TimeUnit=TimeUnit.MILLISECONDS): ObservableTransformer<T, T> {
return ObservableTransformer { upstream ->
upstream.throttleFirst(windowDuration, timeUnit)
}
}
複製代碼
在1秒內不能重複點擊某個UI控件,能夠這樣寫,由於使用了默認參數。
RxView.clicks(textview)
.compose(RxJavaUtils.preventDuplicateClicksTransformer())
.subscribe({
......
})
複製代碼
去年的時候,我曾經寫過一篇關於kotlin dsl的文章——用kotlin來實現dsl風格的編程,使用dsl的方式編寫代碼我的感受更加簡潔和直觀。
在項目中,我對toast以及glide框架嘗試使用dsl的方式來封裝。以前的用法是使用Kotlin的擴展函數,因爲團隊的其餘成員更偏好鏈式調用,目前暫時保留了兩種寫法。
glide的擴展函數,能夠知足項目中的使用。
/** * 佔位符矩形 */
fun ImageView.load(url: String?) {
get(url).placeholder(R.drawable.shape_default_rec_bg)
.error(R.drawable.shape_default_rec_bg)
.into(this)
}
/** * 佔位符圓角矩形 */
fun ImageView.loadRound(url: String?, centerCrop: Boolean = false) {
get(url).placeholder(R.drawable.shape_default_round_bg)
.error(R.drawable.shape_default_round_bg)
.transform(RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = centerCrop))
.into(this)
}
/** * 佔位符圓形 */
fun ImageView.loadCircle(url: Drawable?) {
get(url).placeholder(R.drawable.shape_default_circle_bg)
.apply(RequestOptions.circleCropTransform())
.error(R.drawable.shape_default_circle_bg)
.into(this)
}
fun ImageView.loadCircle(url: String?) {
get(url).placeholder(R.drawable.shape_default_circle_bg)
.apply(RequestOptions.circleCropTransform())
.error(R.drawable.shape_default_circle_bg)
.into(this)
}
fun ImageView.get(url: String?): GlideRequest<Drawable> = GlideApp.with(context).load(url)
fun ImageView.get(url: Drawable?): GlideRequest<Drawable> = GlideApp.with(context).load(url)
複製代碼
加載某個圖片以後,讓它呈現出圓角矩形的效果
holder.itemView.iv_game.loadRound(image_url)
複製代碼
使用dsl進行封裝
class GlideWrapper {
var url:String? = null
var image: ImageView?=null
var placeholder: Int = R.drawable.shape_default_rec_bg
var error: Int = R.drawable.shape_default_rec_bg
var transform: Transformation<Bitmap>? = null
}
fun load(init: GlideWrapper.() -> Unit) {
val wrap = GlideWrapper()
wrap.init()
execute(wrap)
}
private fun execute(wrap:GlideWrapper) {
wrap.image?.let {
var request = it.get(wrap.url).placeholder(wrap.placeholder).error(wrap.error)
if (wrap?.transform!=null) {
request.transform(wrap.transform!!)
}
request.into(it)
}
}
複製代碼
仍然是加載該圖片,讓它呈現出圓角矩形的效果
load {
url = image_url
image = holder.itemView.iv_game
transform = RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = false)
}
複製代碼
提示信息是任何App必不可少的,在咱們的項目中也使用擴展函數對toast進行封裝。
fun Toast.setGravityCenter(): Toast {
setGravity(Gravity.CENTER, 0, 0)
return this
}
/** * 設置Toast字體及背景顏色 * @param messageColor * @param backgroundColor * @return */
fun Toast.setToastColor(@ColorInt messageColor: Int, @ColorInt backgroundColor: Int) {
val view = view
if (view != null) {
val message = view.findViewById(android.R.id.message) as TextView
message.setBackgroundColor(backgroundColor)
message.setTextColor(messageColor)
}
}
/** * 設置Toast字體及背景 * @param messageColor * @param background * @return */
fun Toast.setBackground(@ColorInt messageColor: Int = Color.WHITE, @DrawableRes background: Int = R.drawable.shape_toast_bg): Toast {
val view = view
if (view != null) {
val message = view.findViewById(android.R.id.message) as TextView
view.setBackgroundResource(background)
message.setTextColor(messageColor)
}
return this
}
//@SuppressLint("ShowToast")
fun toast(text: CharSequence): Toast = Toast.makeText(App.instance, text, Toast.LENGTH_LONG)
.setGravityCenter()
.setBackground()
//須要的地方調用withErrorIcon,默認不要添加
// .withErrorIcon()
//@SuppressLint("ShowToast")
fun toast(@StringRes res: Int): Toast = Toast.makeText(App.instance, App.instance.resources.getString(res), Toast.LENGTH_LONG)
.setGravityCenter()
.setBackground()
//須要的地方調用withErrorIcon,默認不要添加
// .withErrorIcon()
fun Toast.withErrorIcon(@DrawableRes iconRes: Int = R.drawable.ic_toast_error): Toast {
val view = view
if (view != null) {
val layout = this.view as LinearLayout
val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
val icon = ImageView(getApplicationContext())
icon.setImageResource(iconRes)
icon.setPadding(0, 0, Util.dip2px(8f), 0)
icon.layoutParams = layoutParams
layout.orientation = LinearLayout.HORIZONTAL
layout.gravity = Gravity.CENTER_VERTICAL
layout.addView(icon, 0)
}
return this
}
fun Toast.withSuccIcon(@DrawableRes iconRes: Int = R.drawable.ic_right_circle): Toast {
val view = view
if (view != null) {
val layout = this.view as LinearLayout
val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
val icon = ImageView(getApplicationContext())
icon.setImageResource(iconRes)
icon.setPadding(0, 0, Util.dip2px(8f), 0)
icon.layoutParams = layoutParams
layout.orientation = LinearLayout.HORIZONTAL
layout.gravity = Gravity.CENTER_VERTICAL
layout.addView(icon, 0)
}
return this
}
複製代碼
要展現一個錯誤的提示,大體須要這樣寫。
toast(resources.getString(R.string.you_have_not_completed_the_email_address)).withErrorIcon().show()
複製代碼
使用dsl進行封裝
class ToastWrapper {
var text:String? = null
var res:Int? = null
var showSuccess:Boolean = false
var showError:Boolean = false
}
fun toast(init: ToastWrapper.() -> Unit) {
val wrap = ToastWrapper()
wrap.init()
execute(wrap)
}
private fun execute(wrap:ToastWrapper) {
var taost:Toast?=null
wrap.text?.let {
taost = toast(it)
}
wrap.res?.let {
taost = toast(it)
}
if (wrap.showSuccess) {
taost?.withSuccIcon()
} else if (wrap.showError) {
taost?.withErrorIcon()
}
taost?.show()
}
複製代碼
使用dsl的方式展現一樣的錯誤信息。
toast {
res = R.string.you_have_not_completed_the_email_address
showError = true
}
複製代碼
目前該系列的文章整理得比較隨意,更像是一些經常使用的tips。
文中的dsl仍是結合了擴展函數來使用的,我的認爲是進一步的封裝。相比起鏈式調用,我仍是比較偏向dsl。
該系列的相關文章:
使用Kotlin高效地開發Android App(五)完結篇
Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公衆號二維碼並關注,期待與您的共同成長和進步。