本文概述
- 什么是Kotlin, 为什么要使用它?
- 什么是MVP, 为什么?
- 具有MVP和Kotlin的示例应用
- 下一步是什么?
- 本文总结
文章图片
Kotlin是Android世界中颇受欢迎的新玩家。但是它能代替Java吗?
鸣叫
什么是Kotlin, 为什么要使用它? 因此, 第一语言。我认为Java并不是优雅或清晰的大师, 它既不是现代的也不是表现力强的(我想你同意)。缺点是, 在Android N以下, 我们仍然仅限于Java 6(包括Java 7的一些小部分)。开发人员还可以附加RetroLambda以在其代码中使用lambda表达式, 这在使用RxJava时非常有用。在Android N之上, 我们可以使用Java 8的一些新功能, 但是仍然是旧的, 沉重的Java。我经常听到Android开发人员说:” 我希望Android支持更好的语言, 就像iOS与Swift一样。” 如果我告诉过你, 你可以使用一种非常漂亮, 简单的语言, 并具有null安全性, lambda和其他许多不错的新功能, 该怎么办?欢迎来到科特林。
什么是科特林?
Kotlin是由JetBrains团队开发的一种新语言(有时称为Android的Swift), 现在是其1.0.2版本。它在Android开发中有用的原因是它可以编译为JVM字节码, 也可以编译为JavaScript。它与Java完全兼容, 并且Kotlin代码可以简单地转换为Java代码, 反之亦然(JetBrains提供了一个插件)。这意味着Kotlin可以使用任何用Java编写的框架, 库等。在Android上, 它通过Gradle集成。如果你有现有的Android应用程序, 并且想要在Kotlin中实现新功能而无需重写整个应用程序, 则只需开始在Kotlin中编写即可, 它将起作用。
但是什么是” 新功能” ?让我列出一些:
可选和命名函数参数
fun createDate(day: Int, month: Int, year: Int, hour: Int = 0, minute: Int = 0, second: Int = 0) {
print("TEST", "$day-$month-$year $hour:$minute:$second")
}
我们可以用不同的方式调用方法createDate
createDate(1, 2, 2016) prints:‘1-2-2016 0:0:0’
createDate(1, 2, 2016, 12) prints: ‘1-2-2016 12:0:0’
createDate(1, 2, 2016, minute = 30) prints: ‘1-2-2016 0:30:0’
空安全 如果变量可以为null, 则除非我们强迫它们进行编译, 否则代码将不会编译。以下代码将出现错误-nullableVar可能为null:
var nullableVar: String? = "";
nullableVar.length;
要进行编译, 我们必须检查它是否不为null:
if(nullableVar){
nullableVar.length
}
或者, 更短:
nullableVar?.length
这样, 如果nullableVar为null, 则什么也不会发生。否则, 我们可以将变量标记为不可为空, 在类型之后不带问号:
var nonNullableVar: String = "";
nonNullableVar.length;
这段代码会编译, 如果我们想将null分配给nonNullableVar, 则编译器将显示错误。
还有一个非常有用的Elvis运算符:
var stringLength = nullableVar?.length ?: 0
然后, 当nullableVar为null(因此nullableVar?.length返回null)时, stringLength的值为0。
可变和不可变变量 在上面的示例中, 定义变量时使用了var。这是可变的, 我们可以在需要时重新分配它。如果我们希望该变量是不可变的(在许多情况下应该如此), 则可以使用val(作为值, 而不是变量):
val immutable: Int = 1
之后, 编译器将不允许我们将其重新分配为不可变的。
Lambdas 我们都知道lambda是什么, 因此在这里我将展示如何在Kotlin中使用它:
button.setOnClickListener({ view ->
Log.d("Kotlin", "Click")})
或者, 如果函数是唯一或最后一个参数:
button.setOnClickListener { Log.d("Kotlin", "Click")}
扩展名 扩展是一项非常有用的语言功能, 借助它, 我们可以” 扩展” 现有的类, 即使它们是最终的或我们无法访问其源代码。
例如, 要从编辑文本中获取字符串值, 而不是每次都编写editText.text.toString(), 我们可以编写以下函数:
fun EditText.textValue(): String{
return text.toString()
}
或更短:
fun EditText.textValue() = text.toString()
现在, 对于每个EditText实例:
editText.textValue()
或者, 我们可以添加一个返回相同的属性:
var EditText.textValue: String
get() = text.toString()
set(v) {setText(v)}
操作员超载 如果我们想添加, 乘或比较对象, 有时会很有用。 Kotlin允许重载二进制运算符(加, 减, plusAssign, 范围等), 数组运算符(获取, 设置, 获取范围, 设置范围)以及等于和一元运算符(+ a, -a等)的重载。
资料类别 你需要多少行代码才能在Java中实现具有三个属性的用户类:copy, equals, hashCode和toString?在Kaotlin中, 你只需要一行:
data class User(val name: String, val surname: String, val age: Int)
该数据类提供equals(), hashCode()和copy()方法, 以及toString(), 它们将User打印为:
User(name=John, surname=Doe, age=23)
数据类还提供了一些其他有用的功能和属性, 你可以在Kotlin文档中看到它们。
Anko扩展 你使用的是Butterknife或Android扩展程序, 不是吗?如果你甚至不需要使用此库, 并且在用XML声明视图之后, 只需按其ID从代码中使用它即可(例如C#中的XAML):
<
Button
android:id="@+id/loginBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
loginBtn.setOnClickListener{}
Kotlin具有非常有用的Anko扩展, 因此你无需告诉活动什么是loginBtn, 它只需通过” 导入” xml即可知道:
import kotlinx.android.synthetic.main.activity_main.*
Anko还有许多其他有用的功能, 包括开始活动, 展示敬酒等等。这不是Anko的主要目标-它旨在轻松地从代码创建布局。因此, 如果你需要以编程方式创建布局, 那么这是最好的方法。
这只是科特林的一小部分。我建议阅读Antonio Leiva的博客和他的著作-Android开发者专用的Kotlin, 当然还要阅读Kotlin的官方网站。
什么是MVP, 为什么? 好的, 功能强大且清晰的语言是不够的。在没有良好架构的情况下, 用各种语言编写混乱的应用程序非常容易。 Android开发人员(大多数是入门人员, 但也包括更高级的开发人员)通常将Activity责任交给周围的所有人。活动(或片段或其他视图)下载数据, 发送以保存, 呈现它们, 响应用户交互, 编辑数据, 管理所有子视图。 。 。甚至更多。对于诸如活动或片段之类的不稳定对象来说, 这太过分了(足以旋转屏幕, 并且活动说” 再见… ” 。)。
一个很好的想法是将责任与观点隔离开来, 并使它们尽可能地愚蠢。视图(活动, 片段, 自定义视图或任何在屏幕上显示数据的视图)应仅负责管理其子视图。视图应该有演示者, 演示者将与模型进行交流, 并告诉他们应该做什么。简而言之, 这就是Model-View-Presenter模式(对我来说, 应该命名为Model-Presenter-View以显示层之间的连接)。
文章图片
“ 嘿, 我知道这样的东西, 它叫做MVC!” -你没想到吗?不, MVP与MVC不同。在MVC模式中, 你的视图可以与模型进行通信。使用MVP时, 你不允许在这两层之间进行任何通信-View与Model进行通信的唯一方法是通过Presenter。 View唯一了解Model的就是数据结构。 View知道例如显示用户的方式, 但不知道何时显示。这是一个简单的示例:
View知道” 我正在活动, 我有两个EditText和一个Button。当有人单击该按钮时, 我应该将其告知演示者, 然后将其传递给EditTexts的值。就是这样, 我可以一直睡觉, 直到下次单击或演示者告诉我该怎么做为止。”
演示者知道某个地方是一个View, 并且他知道该View可以执行哪些操作。他还知道, 当他收到两个字符串时, 应该从这两个字符串中创建用户, 并将数据发送到模型以进行保存, 如果保存成功, 请告诉视图” 显示成功信息” 。
模型只知道数据在哪里, 应该将它们保存在哪里, 以及应该对数据执行哪些操作。
用MVP编写的应用程序易于测试, 维护和重用。纯粹的演示者应该对Android平台一无所知。它应该是纯Java类(在我们的例子中为Kotlin)。因此, 我们可以在其他项目中重用演示者。我们还可以轻松编写单元测试, 分别测试Model, View和Presenter。
MVP模式通过将用户界面和业务逻辑真正分开来实现更好, 更简单的代码库。
有点题外话:MVP应该是Bob叔叔的Clean Architecture的一部分, 以使应用程序更加灵活且结构良好。下次我会尝试写些有关。
具有MVP和Kotlin的示例应用 理论已经足够了, 让我们看一些代码!好的, 让我们尝试创建一个简单的应用。该应用程序的主要目标是创建用户。第一个屏幕将具有两个EditTexts(名称和姓氏)和一个Button(保存)。输入名称和姓氏并单击” 保存” 后, 应用程序应显示” 用户已保存” , 然后转到下一个屏幕, 其中显示已保存的名称和姓氏。如果名称或姓氏为空, 则该应用程序不应保存用户, 并显示错误提示。
创建Android Studio项目后的第一件事是配置Kotlin。你应该安装Kotlin插件, 重新启动后, 在” 工具” > ” Kotlin” 中, 你可以单击” 在项目中配置Kotlin” 。 IDE会将Kotlin依赖项添加到Gradle。如果你有任何现有代码, 则可以通过以下方式轻松地将其转换为Kotlin:(Ctrl + Shift + Alt + K或” 代码” > ” 将Java文件转换为Kotlin” )。如果出了点问题并且项目无法编译, 或者Gradle没有看到Kotlin, 则可以检查GitHub上可用应用程序的代码。
Kotlin不仅可以与Java框架和库良好地互操作, 还可以让你继续使用已经熟悉的大多数相同工具。
现在我们有了一个项目, 首先创建第一个视图-CreateUserView。该视图应该具有前面提到的功能, 因此我们可以为此编写一个接口:
interface CreateUserView : View {
fun showEmptyNameError() /* show error when name is empty */
fun showEmptySurnameError() /* show error when surname is empty */
fun showUserSaved() /* show user saved info */
fun showUserDetails(user: User) /* show user details */
}
如你所见, Kotlin在声明函数方面与Java类似。所有这些都是什么都不返回的函数, 最后一个只有一个参数。区别在于, 参数类型位于名称之后。 View界面并非来自Android, 而是我们简单的空白界面:
interface View
基本Presenter的界面应具有View类型的属性, 至少应具有on方法(例如onDestroy), 该属性将设置为null:
interface Presenter<
T : View>
{
var view: T?fun onDestroy(){
view = null
}
}
在这里, 你可以看到Kotlin的另一个功能-你可以在接口中声明属性, 并在那里实现方法。
我们的CreateUserView需要与CreateUserPresenter进行通信。 Presenter唯一需要的附加功能是带有两个字符串参数的saveUser:
interface CreateUserPresenter<
T : View>
: Presenter<
T>
{
fun saveUser(name: String, surname: String)
}
我们还需要模型定义-前面提到的数据类:
data class User(val name: String, val surname: String)
声明所有接口后, 我们可以开始实现。
CreateUserPresenter将在CreateUserPresenterImpl中实现:
class CreateUserPresenterImpl(override var view: CreateUserView?): CreateUserPresenter<
CreateUserView>
{override fun saveUser(name: String, surname: String) {
}
}
第一行, 带有类定义:
CreateUserPresenterImpl(override var view: CreateUserView?)
是一个构造函数, 我们用它来分配在接口中定义的view属性。
MainActivity是我们的CreateUserView实现, 需要对CreateUserPresenter的引用:
class MainActivity : AppCompatActivity(), CreateUserView {private val presenter: CreateUserPresenter<
CreateUserView>
by lazy {
CreateUserPresenterImpl(this)
}override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)saveUserBtn.setOnClickListener{
presenter.saveUser(userName.textValue(), userSurname.textValue()) /*use of textValue() extension, mentioned earlier */}
}override fun showEmptyNameError() {
userName.error = getString(R.string.name_empty_error) /* it's equal to userName.setError() - Kotlin allows us to use property */}override fun showEmptySurnameError() {
userSurname.error = getString(R.string.surname_empty_error)
}override fun showUserSaved() {
toast(R.string.user_saved) /* anko extension - equal to Toast.makeText(this, R.string.user_saved, Toast.LENGTH_LONG) */}override fun showUserDetails(user: User) {}override fun onDestroy() {
presenter.onDestroy()
}
}
在课程开始时, 我们定义了演示者:
private val presenter: CreateUserPresenter<
CreateUserView>
by lazy {
CreateUserPresenterImpl(this)
}
它定义为不可变(val), 由延迟委托创建, 它将在第一次需要时分配。此外, 我们确保它不会为null(定义后没有问号)。
当用户单击” 保存” 按钮时, View将使用EditTexts值将信息发送到Presenter。发生这种情况时, 应该保存用户, 因此我们必须在Presenter中实现saveUser方法(以及某些模型的功能):
override fun saveUser(name: String, surname: String) {
val user = User(name, surname)
when(UserValidator.validateUser(user)){
UserError.EMPTY_NAME ->
view?.showEmptyNameError()
UserError.EMPTY_SURNAME ->
view?.showEmptySurnameError()
UserError.NO_ERROR ->
{
UserStore.saveUser(user)
view?.showUserSaved()
view?.showUserDetails(user)
}
}
}
创建用户后, 会将其发送到UserValidator以检查有效性。然后, 根据验证结果, 调用适当的方法。 when(){}构造与Java中的switch / case相同。但这功能更强大-Kotlin不仅允许在” case” 中使用enum或int, 还可以使用范围, 字符串或对象类型。它必须包含所有可能性或具有else表达式。在这里, 它涵盖了所有UserError值。
通过使用view?.showEmptyNameError()(在视图后带有问号), 我们可以免受NullPointer的攻击。可以在onDestroy方法中使视图为空, 并且采用这种构造, 将不会发生任何事情。
当User模型没有错误时, 它将告诉UserStore保存它, 然后指示View显示成功和显示详细信息。
如前所述, 我们必须实现一些模型化的事情:
enum class UserError {
EMPTY_NAME, EMPTY_SURNAME, NO_ERROR
}object UserStore {
fun saveUser(user: User){
//Save user somewhere: Database, SharedPreferences, send to web...
}
}object UserValidator {fun validateUser(user: User): UserError {
with(user){
if(name.isNullOrEmpty()) return UserError.EMPTY_NAME
if(surname.isNullOrEmpty()) return UserError.EMPTY_SURNAME
}return UserError.NO_ERROR
}
}
这里最有趣的是UserValidator。通过使用对象词, 我们可以创建一个单例类, 而不必担心线程, 私有构造函数等。
接下来的事情-在validateUser(user)方法中, 有with(user){}表达式。此类块中的代码在对象的上下文中执行, 传入的名称和姓氏是用户的属性。
还有另一件事。以上所有代码, 从枚举到UserValidator, 定义都放在一个文件中(User类的定义也在此处)。 Kotlin不会强迫你在单个文件中拥有每个公共类(或名称类与文件完全相同)。因此, 如果你有一些简短的相关代码(数据类, 扩展名, 函数, 常量-Kotlin不需要函数或常量的类), 则可以将其放在一个文件中, 而不必在项目中遍历所有文件。
保存用户后, 我们的应用程序应显示该信息。我们需要另一个视图-它可以是任何Android视图, 自定义视图, 片段或活动。我选择了活动。
因此, 让我们定义UserDetailsView界面。它可以显示用户, 但是当用户不存在时它也应该显示错误:
interface UserDetailsView {
fun showUserDetails(user: User)
fun showNoUserError()
}
接下来, UserDetailsPresenter。它应该具有用户属性:
interface UserDetailsPresenter<
T: View>
: Presenter<
T>
{
var user: User?
}
该接口将在UserDetailsPresenterImpl中实现。它必须覆盖用户属性。每次分配此属性时, 应在视图上刷新用户。我们可以为此使用属性设置器:
class UserDetailsPresenterImpl(override var view: UserDetailsView?): UserDetailsPresenter<
UserDetailsView>
{
override var user: User? = null
set(value) {
field = value
if(field != null){
view?.showUserDetails(field!!)
} else {
view?.showNoUserError()
}
}}
UserDetailsView实现UserDetailsActivity非常简单。和以前一样, 我们有一个通过延迟加载创建的presenter对象。要显示的用户应通过意图传递。现在有一个小问题, 我们将在稍后解决。当我们有意图的用户时, View需要将其分配给他们的演示者。此后, 用户将在屏幕上刷新, 或者如果为null, 则将出现错误(并且活动将结束-但主持人不知道这一点):
class UserDetailsActivity: AppCompatActivity(), UserDetailsView {private val presenter: UserDetailsPresenter<
UserDetailsView>
by lazy {
UserDetailsPresenterImpl(this)
}override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_details)val user = intent.getParcelableExtra<
User>
(USER_KEY)
presenter.user = user
}override fun showUserDetails(user: User) {
userFullName.text = "${user.name} ${user.surname}"
}override fun showNoUserError() {
toast(R.string.no_user_error)
finish()
}override fun onDestroy() {
presenter.onDestroy()
}}
通过意图传递对象要求该对象实现Parcelable接口。这是非常” 肮脏” 的工作。就个人而言, 由于所有CREATOR, 属性, 保存, 还原等原因, 我都不愿意这样做。幸运的是, 有一个合适的插件Parcelable for Kotlin。安装后, 我们只需单击一下即可生成Parcelable。
最后要做的是在MainActivity中实现showUserDetails(user:User):
override fun showUserDetails(user: User) {
startActivity<
UserDetailsActivity>
(USER_KEY to user) /* anko extension - starts UserDetailsActivity and pass user as USER_KEY in intent */
}
就这样。
文章图片
我们有一个简单的应用程序可以保存用户(实际上, 它不会保存, 但是我们可以添加此功能而无需触摸演示者或视图)并将其显示在屏幕上。将来, 如果我们想更改用户在屏幕上的显示方式, 例如从两个活动更改为一个活动中的两个片段或两个自定义视图, 则更改将仅在View类中进行。当然, 如果我们不更改功能或模型的结构。演示者不知道View到底是什么, 就不需要进行任何更改。
你的Android应用有性能问题吗?查看这些优化技巧和技术。
下一步是什么? 在我们的应用程序中, 每次创建活动时都会创建Presenter。如果Presenter应该在活动实例之间持续存在, 则这种方法或与之相反的方法是Internet上很多讨论的主题。对我而言, 这取决于应用程序, 其需求和开发人员。有时最好销毁主持人, 有时则不行。如果你决定坚持一个, 那么非常有趣的技术是使用LoaderManager。
如前所述, MVP应该是Bob叔叔的Clean架构的一部分。此外, 优秀的开发人员应使用Dagger将演示者对活动的依赖项注入。它还有助于将来维护, 测试和重用代码。目前, Kotlin与Dagger(在正式发行之前还不那么容易)和其他有用的Android库配合得很好。
本文总结 对我来说, 科特林是一门很棒的语言。它是现代的, 清晰的和富有表现力的, 同时仍然由伟大的人们开发。我们可以在任何Android设备和版本上使用任何新版本。无论是什么让我对Java感到生气, Kotlin都会有所改进。
当然, 正如我所说, 没有什么是理想的。 Kotlin也有一些缺点。最新的gradle插件版本(主要来自alpha或beta)不适用于该语言。许多人抱怨构建时间比纯Java要长一些, 而apk则有一些额外的MB。但是Android Studio和Gradle仍在改进, 手机拥有越来越多的应用程序空间。这就是为什么我认为Kotlin对于每个Android开发人员来说都是一种很好的语言。只需尝试一下, 然后在你的想法下方的评论部分中分享。
【Kotlin简介(适用于人类的Android编程)】示例应用程序的源代码可在Github上找到:github.com/tomaszczura/AndroidMVPKotlin
推荐阅读
- Applescript-同步iPhone
- 详细解释React Native和Cordova有哪些区别()
- Kotlin的十项功能可促进Android开发
- 适用于开发人员的Android 7.0(新功能,性能升级和你不关心的其他内容)
- iOS开发人员不知道的10个最常见错误
- 大多数Swift开发人员不知道自己犯的错误
- iOS ARKit教程(用裸露的手指画画)
- 如何在Mac上用C#制作Android和iOS应用
- 使用Android修复的移动视口