Savvy Apps 在 2016 年底开始在新的 Android 项目中使用 Kotlin,就在 Kotlin 1.0.4 发布之际。最初,我们得到了在规模较小的项目中尝试 Kotlin 的机会,当尝试过后,我们发现了它的易用性,使用扩展函数可以很容易的将功能和业务逻辑分离开,而且它为我们节省了开发时间,因此,我们觉得它将是一门先进的语言选型。从那时开始,我们使用 Kotlin 创建了多个 Android App,同时也开发了一些内部的 Kotlin 函数库。
初级建议
延迟加载
val purchasingApi: PurchasingApi by lazy {
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
retrofit.create(PurchasingApi::class.java)
}// bounds is created as soon as the first call to bounds is made
val bounds: RectF by lazy {
RectF(0f, 0f, width.toFloat(), height.toFloat())
}自定义 Getters/Setters
@ParseClassName("Book")
class Book : ParseObject() {
// getString() and put() are methods that come from ParseObject
var name: String
get() = getString("name")
set(value) = put("name", value)
var author: String
get() = getString("author")
set(value) = put("author", value)
}val book = api.getBook() textAuthor.text = book.author
Lambdas 表达式
button.setOnClickListener { view ->
startDetailActivity()
}toolbar.setOnLongClickListener {
showContextMenu()
true
}数据类
data class User(val name: String, val age: Int)就这么简单,不需要再增加其他的定义,这个类就可以正常工作了。如果将数据类和 Gson 或者类似的 JSON 解析函数库一起使用,你可以像下面代码这样使用默认值创建默认的构建方法:
// Example with Gson's @SerializedName annotation
data class User(
@SerializedName("name") val name: String = "",
@SerializedName("age") val age: Int = 0
)集合过滤
val users = api.getUsers()
// we only want to show the active users in one list
val activeUsersNames = items.filter {
it.active // the "it" variable is the parameter for single parameter lamdba functions
}
adapter.setUsers(activeUsers)对象表达式
package com.savvyapps.example.util
import android.os.Handler
import android.os.Looper
// notice that this is object instead of class
object ThreadUtil {
fun onMainThread(runnable: Runnable) {
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post(runnable)
}
}ThreadUtil.onMainThread(runnable)这意味着再也不需要声明私有的构造方法,或者不得不指定在哪里存放静态实例。对象表达式本质上是 Kotlin 语言的第一公民。类似的,我们可以使用对象表达式来代替匿名内部类,如下所示:
viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) {
bindUser(position)
}
});Companion 对象
class User {
companion object {
const val DEFAULT_USER_AGE = 30
}
}
// later, accessed like you would a static variable:
user.age = User.DEFAULT_USER_AGEclass ViewUserActivity : AppCompatActivity() {
companion object {
const val KEY_USER = "user"
fun intent(context: Context, user: User): Intent {
val intent = Intent(context, ViewUserActivity::class.java)
intent.putExtra(KEY_USER, user)
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cooking)
val user = intent.getParcelableExtra<User>(KEY_USER)
//...
}
}val intent = ViewUserActivity.intent(context, user) startActivity(intent)
全局常量
package com.savvyapps.example import android.support.annotation.StringDef // Note that this is not a class, or an object const val PRESENTATION_MODE_PRESENTING = "presenting" const val PRESENTATION_MODE_EDITING = "editing"
import com.savvyapps.example.PRESENTATION_MODE_EDITING val currentPresentationMode = PRESENTATION_MODE_EDITING
Optional Parameters
fun View.fadeOut(duration: Long = 500): ViewPropertyAnimator {
return animate()
.alpha(0.0f)
.setDuration(duration)
}icon.fadeOut() // fade out with default time (500) icon.fadeOut(1000) // fade out with custom time
中级建议
扩展
fun Activity.hideKeyboard(): Boolean {
val view = currentFocus
view?.let {
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE)
as InputMethodManager
return inputMethodManager.hideSoftInputFromWindow(view.windowToken,
InputMethodManager.HIDE_NOT_ALWAYS)
}
return false
}class Article(val title: String, val numberOfViews: Int, val topic: String)
// In another Kotlin file, possibly named ArticleLogic.kt or something similar
fun Article.isArticleRelevant(user: User): Boolean {
return user.favoriteTopics.contains(topic)
}lateinit
var total = 0 // declared right away, no possibility of null var toolbar: Toolbar? = null // could be a toolbar, could be null
lateinit var toolbar: Toolbar
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ButterKnife.bind(this)
// you can now reference toolbar with no problems!
toolbar.setTitle("Hello There")
}安全的类型转换
var feedFragment: FeedFragment? = supportFragmentManager .findFragmentByTag(TAG_FEED_FRAGMENT) as FeedFragment这实际上会导致一个 crash。当你使用
as 时,它会尝试对对象进行类型转换,在这个例子中,转换后可能为 nul,同时可能引起空指针异常。你需要使用 as? 来代替 as,意思是对对象进行类型转换,如果转换失败,则返回 null。因此,Fragment 的正确初始化应该如下所示:var feedFragment: FeedFragment? = supportFragmentManager
.findFragmentByTag(TAG_FEED_FRAGMENT) as? FeedFragment
if (feedFragment == null) {
feedFragment = FeedFragment.newInstance()
supportFragmentManager.beginTransaction()
.replace(R.id.root_fragment, feedFragment, TAG_FEED_FRAGMENT)
.commit()
}使用 let
if (currentUser != null) {
text.setText(currentUser.name)
}user?.let {
println(it.name)
}isNullOrEmpty | isNullOrBlank
if (TextUtils.isEmpty(name)) {
// alert the user!
}在这个例子中,你会发现如果用户将它们的用户名 name 设置为空白,它将通过上面的检验。isNullOrEmpty 和 isNullOrBlank 是内置在 Kotlin 语言中的,它们消除了使用 TextUtisl.isEmpty(someString) 的需要,同时提供检查空白的额外功能。你可以在适当的时候使用这两个方法:// If we do not care about the possibility of only spaces...
if (number.isNullOrEmpty()) {
// alert the user to fill in their number!
}
// when we need to block the user from inputting only spaces
if (name.isNullOrBlank()) {
// alert the user to fill in their name!
}fun TextInputLayout.isValidForEmail(): Boolean {
val input = editText?.text.toString()
if (input.isNullOrBlank()) {
error = resources.getString(R.string.required)
return false
} else if (emailPattern.matcher(input).matches()) {
error = resources.getString(R.string.invalid_email)
return false
} else {
error = null
return true
}
}高级建议
避免为 Kotlin 类定义唯一的抽象方法
public interface OnClickListener {
void onClick(View v);
}textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// do something
}
});textView.setOnClickListener { view ->
// do something
}view.setOnClickListener(object : OnClickListener {
override fun onClick(v: View?) {
// do things
}
})private var onClickListener: ((View) -> Unit)? = null
fun setOnClickListener(listener: (view: View) -> Unit) {
onClickListener = listener
}
// later, to invoke
onClickListener?.invoke(this)使用协程代替 AnsyncTask
compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.13"
kotlin.coroutines=enable
fun bindUser() = launch(UI) {
// Call to API or some other things that takes time
val user = Api.getUser().execute()
// continue doing things with the ui
text.text = user.name
}结论
英文:https://savvyapps.com/blog/kotlin-tips-android-development