作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Tomasz Czura
验证专家 在工程

Tomasz (MCS)是一个Android向导和团队领导. 他最喜欢的项目是做一个酒店娱乐系统的应用程序和后端.

Read More

以前在

Welltok
Share

在一个完美的Android世界中,Java的主要语言确实是现代的、清晰的和优雅的. 做得多写得少, 每当有新功能出现时, 开发者可以通过在Gradle中增加版本来使用它. 然后,在创建一个非常好的应用程序时,它看起来是完全可测试、可扩展和可维护的. 我们的活动不是太大太复杂, 我们可以将数据源从数据库更改为web,而不会产生大量差异, and so on. 听起来不错,对吧?? 不幸的是,Android世界并非如此理想. 谷歌仍在追求完美,但我们都知道理想的世界是不存在的. 因此,我们必须在Android世界的伟大旅程中帮助自己.

什么是Kotlin,为什么要使用它?

所以,第一种语言. 我认为Java并不是优雅和清晰的大师, 它既不现代也不富有表现力(我猜你也同意). 缺点是低于Android N, 我们仍然局限于Java 6(包括Java 7的一小部分). 开发人员还可以附加RetroLambda以在其代码中使用lambda表达式, 这是非常有用的,而使用RxJava. Android N以上, 我们可以使用Java 8的一些新功能, 但它还是那么古老, heavy Java. 我经常听到 Android开发者 说“我希望Android支持一种更好的语言,就像iOS支持Swift一样”. 如果我告诉你,你可以用一个非常好的, 简单的语言, 零安全, lambdas, 还有许多其他不错的新功能? 欢迎来到Kotlin.

Kotlin是什么?

Kotlin 是一种新语言(有时被称为Android的Swift), 由JetBrains团队开发, 现在是1.0.2 version. 让它在Android开发中有用的是它可以编译成JVM字节码, 也可以编译成JavaScript. 它与Java完全兼容, 和Kotlin代码可以简单地转换为Java代码,反之亦然(有一个插件来自JetBrains). 这意味着Kotlin可以使用任何框架、库等. 用Java编写的. 在Android上,它通过Gradle进行集成. 如果你有一个现有的Android应用程序,你想在Kotlin中实现一个新功能,而不重写整个应用程序, 就开始用Kotlin写吧, 它会起作用的.

但是什么是“伟大的新功能”呢?? 让我列举一些:

可选和命名函数参数

fun createDate(日:Int,月:Int,年:Int,小时:Int = 0,分钟:Int = 0,秒:Int = 0) {
   打印(“测试”,“日-月-年美元美元小时:分钟:美元美元第二”)
}

我们可以用不同的方式调用createDate方法

createDate(1,2,2016)打印:' 1-2-2016 0:0:0 '
createDate(1,2,2016, 12)打印:' 1-2-2016 12:0:0 '
createDate(1,2,2016, minute = 30)打印:' 1-2-2016 0:30:0 '

Null Safety

如果一个变量可以为空,代码将无法编译,除非我们强制它们为空. 下面的代码会有一个错误- nullableVar可能为空:

var nullableVar:字符串? = “”;
nullableVar.length;

为了编译,我们必须检查它是否为null:

如果(nullableVar) {
	nullableVar.length
}

Or, shorter:

nullableVar?.length

这样,如果nullableVar为空,什么也不会发生. 否则,我们可以将变量标记为不可空,在type后面不加问号:

var nonNullableVar:字符串= " ";
nonNullableVar.length;

这段代码编译后,如果我们想把null赋值给nonNullableVar,编译器会显示一个错误.

还有一个非常有用的猫王操作符:

var stringLength = nullableVar?.length ?: 0 

然后,当nullableVar为null时(所以nullableVar?.length返回null), stringLength的值为0.

可变变量和不可变变量

在上面的例子中,我在定义变量时使用了var. 这是可变的,我们可以随时重新赋值. 如果我们想让这个变量不可变(很多情况下我们应该这么做), 我们使用val()作为值, 不变量):

val immutable: Int = 1

在此之后,编译器将不允许对immutable对象重新赋值.

Lambdas

我们都知道lambda是什么,所以这里我将展示如何在Kotlin中使用它:

button.setOnClickListener({ view -> Log.d(“芬兰湾的科特林”、“点击”)})

或者如果函数是唯一或最后一个参数:

button.setOnClickListener {日志.d(“芬兰湾的科特林”、“点击”)}

Extensions

扩展是一个非常有用的语言特性, 得益于此,我们可以“扩展”现有的类, 即使它们是最终的,或者我们无法访问它们的源代码.

例如,要从编辑文本中获取字符串值,而不是每次都写入editText.text.toString()可以这样写函数:

fun EditText.{textValue():字符串
   return text.toString()
}

Or shorter:

fun EditText.textValue() = text.toString()

现在,每个实例的EditText:

editText.textValue()

或者,我们可以添加一个属性返回相同的结果:

var EditText.textValue:字符串
   get() = text.toString()
   集(v) {setText (v)}

操作符重载

有时在我们想要添加、相乘或比较对象时很有用. Kotlin允许重载二进制操作符(plus, minus, plusAssign, range等).)、数组操作符(get、set、get range、set range)、等号和一元操作(+a、-a等).)

Data Class

需要多少行代码才能在Java中实现具有三个属性的User类:copy, equals, hashCode, and toString? 在Kaotlin中,您只需要一行:

数据类User(val name: String, val姓:String, val age: Int)

这个数据类提供了equals(), hashCode()和copy()方法, 还有toString(), 它将用户打印为:

用户(名=John,姓=Doe,年龄=23)

数据类还提供了一些其他有用的函数和属性, 您可以在Kotlin文档中看到.

像扩展

你使用Butterknife或Android扩展,不是吗? 如果您甚至不需要使用这个库怎么办, 在XML中声明视图后,只需通过其ID从代码中使用它(如c#中的XAML):

loginBtn.setOnClickListener {}

Kotlin非常有用 像扩展, 这样你就不需要告诉你的activity loginBtn是什么, 它知道它只是通过“导入”xml:

进口kotlinx.android.synthetic.main.activity_main.*

Anko中还有许多其他有用的功能,包括启动活动、显示祝酒等等. 这并不是Anko的主要目标——它是为了方便地从代码中创建布局而设计的. 因此,如果您需要以编程方式创建布局,这是最好的方法.

这只是Kotlin的一个简短的视图. 我推荐阅读Antonio Leiva的博客和他的书《欧博体育app下载》, 当然还有Kotlin的官方网站.

什么是MVP ?为什么?

一个漂亮、有力、清晰的语言是不够的. 如果没有良好的架构,使用各种语言编写混乱的应用程序是非常容易的. Android开发者(主要是刚刚起步的开发者), 以及更高级的)通常会让Activity负责周围的所有事情. 活动(或片段), 或其他视图)下载数据, sends to save, presents them, 响应用户交互, edits data, 管理所有子视图 . . . 甚至更多. 对于像Activities或Fragments这样不稳定的对象来说,这是太多了(它足以旋转屏幕并且Activity说“再见…….’).

一个非常好的想法是将责任从视图中分离出来,并使它们尽可能愚蠢. 视图(活动, Fragments, custom views, 或者任何在屏幕上呈现数据的东西)应该只负责管理它们的子视图. 视图应该有演示者,演示者将与模型通信,并告诉模型应该做什么. This, in short, 模型-视图-演示器模式(适合我吗, 它应该被命名为模型-呈现器-视图,以显示图层之间的连接).

MVC vs MVP

“嘿,我知道类似的东西,它叫MVC!“——你不这么认为吗? 不,MVP和MVC不一样. 在MVC模式中,视图可以与模型通信. 使用MVP时, 你不允许这两层之间有任何通信——视图与模型通信的唯一方式是通过呈现者. View对Model的唯一了解就是数据结构. 例如,视图知道如何显示User,但不知道何时显示. 这里有一个简单的例子:

视图知道,我是Activity,我有两个edittext和一个Button. 当有人点击按钮时,我应该告诉我的演示者,并传递给他EditTexts的值. 就这样,我可以睡觉,直到下一次点击或主持人告诉我该怎么做.”

演示者知道某处是一个视图,他知道这个视图可以执行什么操作. 他也知道当他收到两个字符串时, 他应该从这两个字符串创建User,并将数据发送到模型以保存, 如果save成功, 告诉视图' Show success info '.

模型只知道数据在哪里, 他们应该被保存在哪里, 以及应该对数据执行哪些操作.

用MVP编写的应用程序易于测试、维护和重用. 一个纯粹的演示者应该对Android平台一无所知. 它应该是纯Java(在我们的例子中是Kotlin)类. 因此,我们可以在其他项目中重用演示器. 我们也可以很容易地编写单元测试,分别测试模型、视图和演示器.

MVP模式导致更好, 通过保持用户界面和业务逻辑真正分离,减少了复杂的代码库.

题外话:MVP应该是Bob大叔的“干净架构”的一部分,以使应用程序更加灵活和良好的架构. 下次我会试着写这个.

示例应用程序与MVP和Kotlin

这是足够的理论,让我们看看一些代码! 好的,让我们试着创建一个简单的应用程序. 这个应用程序的主要目标是创建用户. 第一个屏幕将有两个edittext (Name和姓氏)和一个Button (Save). 输入姓名后,点击“保存”, 应用程序应该显示“用户已保存”并进入下一个屏幕, 保存的姓名和姓氏显示在哪里. 当name或姓为空时, 应用程序不应该保存用户,并显示一个错误,指出什么是错误的.

创造后的第一件事 Android工作室 项目是配置Kotlin. You should 安装Kotlin插件, and, after restart, in Tools > Kotlin you can click ‘Configure Kotlin in Project’. IDE会将Kotlin依赖项添加到Gradle中. 如果您有任何现有的代码, you can easily convert it to Kotlin by (Ctrl+Shift+Alt+K or Code > Convert Java file to Kotlin). 如果出现问题,项目无法编译, 或者Gradle看不到Kotlin, 你可以在GitHub上查看应用程序的代码.

Kotlin不仅可以很好地与Java框架和库进行互操作, 它允许您继续使用您已经熟悉的大多数相同工具.

现在我们有了一个项目,让我们开始创建我们的第一个视图- CreateUserView. 这个视图应该具有前面提到的功能,所以我们可以为此编写一个接口:

接口CreateUserView:视图
   fun showEmptyNameError () /*显示名称为空时的错误*/
   fun showEmptySurnameError () /*显示姓氏为空时的错误*/
   fun showUserSaved () /*显示用户保存信息*/
   fun showUserDetails(user: user) /*显示用户详情*/
}

正如您所看到的,Kotlin在声明函数方面类似于Java. 所有这些都是不返回任何东西的函数,最后一个函数只有一个参数. 这就是区别,参数类型在名称之后. View接口不是来自Android——它是我们简单的空接口:

界面视图

Basic Presenter的接口应该有一个View类型的属性, 并且至少有一个方法(例如onDestroy), 其中此属性将被设置为null:

interface Presenter {
   var view: T?

   乐趣onDestroy () {
       view = null
   }
}

这里你可以看到另一个 芬兰湾的科特林功能 你可以在接口中声明属性,也可以在接口中实现方法.

我们的CreateUserView需要与CreateUserPresenter进行通信. 演示器唯一需要的附加函数是带有两个字符串参数的saveUser:

interface CreateUserPresenter: Presenter {
   有趣的saveUser(名称:字符串,姓氏:字符串)
}

我们还需要模型定义-它在前面提到的数据类:

数据类User(val name: String, val姓:String)

声明所有接口之后,我们就可以开始实现了.

CreateUserPresenter将在CreateUserPresenterImpl中实现:

类createuserpreenterimpl(覆盖var视图:CreateUserView?): CreateUserPresenter {

   覆盖好玩的saveUser(名称:字符串,姓氏:字符串){
   }
}

第一行,类定义:

createuserpreenterimpl(覆盖var视图:CreateUserView?)

是一个构造函数,我们用它来分配视图属性,定义在接口.

MainActivity,这是我们的CreateUserView实现,需要一个CreateUserPresenter的引用:

类MainActivity: AppCompatActivity(), CreateUserView {

   private val presenter: CreateUserPresenter by lazy {
       CreateUserPresenterImpl(这)
   }

   重载onCreate(savedInstanceState: Bundle)?) {
       super.onCreate (savedInstanceState)
       setContentView(右.layout.activity_main)

       saveUserBtn.setOnClickListener {
           presenter.saveUser(用户名.userSurname textValue ().textValue()) /*使用前面提到的textValue()扩展*/


       }
   }

   重载showEmptyNameError () {
       userName.error = getString(R.string.name_empty_error) /*它等于userName.setError() - Kotlin允许我们使用属性*/

   }

   重载showEmptySurnameError () {
       userSurname.error = getString(R.string.surname_empty_error)
   }

   重载showUserSaved () {
       toast(R.string.user_saved) /* anko扩展-等于Toast.makeText (R.string.user_saved,烤面包.LENGTH_LONG) * /

   }

   重载showUserDetails(user: user) {
      
   }

重载onDestroy() {
   presenter.onDestroy()
}
}

在课程开始时,我们定义了我们的演示者:

private val presenter: CreateUserPresenter by lazy {
       CreateUserPresenterImpl(这)
}

它被定义为不可变的(val), 由懒惰委托创建, 哪一个会在第一次需要的时候分配. 此外,我们确信它不会为空(定义后没有问号)。.

当用户单击Save按钮时,View将带有EditTexts值的信息发送给Presenter. 当这种情况发生时, 用户应该被保存, 所以我们必须在Presenter(和Model的一些函数)中实现saveUser方法:

覆盖好玩的saveUser(名称:字符串,姓氏:字符串){
   val user =用户(姓名)
   当(UserValidator.validateUser(用户)){
       UserError.EMPTY_NAME -> view?.showEmptyNameError ()
       UserError.EMPTY_SURNAME -> view?.showEmptySurnameError ()
       UserError.NO_ERROR -> {
           UserStore.saveUser(用户)
view?.showUserSaved ()
           view?.showUserDetails(用户)
       }
   }
}

当一个User被创建时,它被发送到UserValidator来检查有效性. 然后,根据验证结果,调用相应的方法. when(){}结构与Java中的switch/case相同. 但它更强大——Kotlin不仅允许在' case '中使用enum或int, 还有范围, 字符串或对象类型. 它必须包含所有的可能性或者有一个else表达式. 这里,它涵盖了所有UserError值.

By using view?.showEmptyNameError ()(视图后带问号),我们可以避免NullPointer. View可以在onDestroy方法中被null,使用这个构造,什么都不会发生.

当用户模型没有错误时, 它告诉UserStore保存它, 然后指示View显示成功并显示详细信息.

如前所述,我们必须实现一些模型:

enum类UserError {
   EMPTY_NAME,
   EMPTY_SURNAME,
   NO_ERROR
}

对象UserStore {
   好玩的saveUser(用户:用户){
       //将用户保存在:Database, SharedPreferences, send to web...
   }
}

对象UserValidator {

   validateUser(user: user): UserError {
       with(user){
           if(name.返回UserError ().EMPTY_NAME
           if(surname.返回UserError ().EMPTY_SURNAME
       }

       返回UserError.NO_ERROR
   }
}

这里最有趣的是UserValidator. 通过使用宾语词, 我们可以创建一个单例类, 不用担心线程, 私人构造器等等.

下一件事-在validateUser(user)方法中,有with(user){}表达式. 该块内的代码在对象的上下文中执行, 与name和姓氏一起传入的是用户属性.

还有一件小事. 以上代码, 从enum到UserValidator, 定义放在一个文件中(User类的定义也在这里). Kotlin并不强制您在单个文件中拥有每个公共类(或者将类命名为文件)。. Thus, 如果您有一些简短的相关代码片段(数据类), extensions, functions, 常量——Kotlin不需要类作为函数或常量), 您可以将它放在一个文件中,而不是分散在项目中的所有文件中.

当一个用户被保存时,我们的应用应该显示它. 我们需要另一个视图-它可以是任何Android视图,自定义视图,片段或活动. 我选择了活动.

让我们定义UserDetailsView接口. 它可以显示user,但当user不存在时,它也应该显示一个错误:

接口UserDetailsView {
   fun showUserDetails(user: user)
   乐趣showNoUserError ()
}

接下来,UserDetailsPresenter. 它应该有一个user属性:

interface UserDetailsPresenter: Presenter {
   var user:用户?
}

这个接口将在UserDetailsPresenterImpl中实现. 它必须重写user属性. 每次分配此属性时,都应该在视图上刷新user. 我们可以使用属性setter:

类UserDetailsPresenterImpl(覆盖var视图:UserDetailsView?): UserDetailsPresenter {
   override var user:用户? = null
       set(value) {
           field = value
           if(field != null){
               view?.showUserDetails(字段!!)
           } else {
               view?.showNoUserError ()
           }
       }

}

UserDetailsView实现,UserDetailsActivity,非常简单. 就像前面一样,我们有一个通过延迟加载创建的呈现器对象. 要显示的用户应该通过意图传递. 现在有一个小问题,我们马上就会解决它. 当我们有userfromintent时,视图需要把它分配给演示者. After that, 用户将在屏幕上刷新, or, 如果为空, 错误将出现(活动将结束-但演示者不知道):

类UserDetailsActivity: AppCompatActivity(), UserDetailsView {

   private val presenter: UserDetailsPresenter by lazy {
       UserDetailsPresenterImpl(这)
   }

   重载onCreate(savedInstanceState: Bundle)?) {
       super.onCreate (savedInstanceState)
       setContentView(右.layout.activity_user_details)

       Val user = intent.getParcelableExtra(USER_KEY)
       presenter.user = user
   }

   重载showUserDetails(user: user) {
       userFullName.Text = "${user.name} ${user.surname}"
   }

   重载showNoUserError () {
       toast(R.string.no_user_error)
       finish()
   }

重载onDestroy() {
   presenter.onDestroy()
}

}

通过intent传递对象要求该对象实现Parcelable接口. 这是非常“肮脏”的工作. Personally, 我讨厌这样做是因为所有的创造者, properties, saving, restoring, and so on. 幸运的是,有一个适合Kotlin的插件Parcelable. 安装后,我们可以生成Parcelable只需点击一下.

最后要做的是在MainActivity中实现showUserDetails(user: user):

重载showUserDetails(user: user) {
   startActivity(USER_KEY to user) /* anko extension - starts UserDetailsActivity and pass user as USER_KEY in intent */
}

这就是全部.

在Kotlin中演示Android应用程序

我们有一个简单的应用程序,保存一个用户(实际上, 它没有被保存, 但我们可以添加这个功能,而不需要触摸演示器(或视图),并将其呈现在屏幕上. In the future, 如果我们想改变用户在屏幕上的呈现方式, 比如从两个活动到一个活动中的两个片段, 或者两个自定义视图, 更改将只发生在视图类中. 当然,如果我们不改变功能或模型的结构. 呈现者,他不知道视图到底是什么,不需要任何改变.

What’s Next?

在我们的应用中,每次创建activity时都会创建Presenter. This approach, 或者是相反的, 如果呈现者应该跨活动实例持久化, 这个话题在互联网上引起了广泛讨论. 对我来说,这取决于应用、它的需求和开发者. 有时最好是摧毁演示者,有时则不然. 如果您决定持久化一个,一个非常有趣的技术是使用LoaderManager.

如前所述,MVP应该是Bob大叔的Clean架构的一部分. 此外,优秀的开发人员应该使用Dagger向活动注入呈现器依赖. 它还有助于在将来维护、测试和重用代码. Currently, Kotlin与Dagger配合得非常好(在正式发布之前,它不是那么容易), 以及其他有用的Android库.

Wrap Up

对我来说,Kotlin是一门很棒的语言. 它是现代的、清晰的、富有表现力的,同时也是由伟大的人开发的. 我们可以在任何Android设备和版本上使用任何新版本. 无论什么让我对Java感到愤怒,Kotlin都有所改进.

当然,正如我所说,没有什么是理想的. Kotlin也有一些缺点. 最新的gradle插件版本(主要来自alpha或beta)不能很好地使用这种语言. 许多人抱怨构建时间比纯Java要长一些, 和apks有一些额外的MB. 但Android工作室和Gradle仍在不断改进,手机上的应用空间也越来越大. 这就是为什么我相信Kotlin对于每个Android开发者来说都是一门非常好的语言. 试一试吧,并在下面的评论区分享你的想法.

示例应用程序的源代码可在Github上获得: github.com/tomaszczura/AndroidMVPKotlin

聘请Toptal这方面的专家.
Hire Now
Tomasz Czura

Tomasz Czura

验证专家 在工程

克拉科夫,波兰

2016年2月6日成为会员

作者简介

Tomasz (MCS)是一个Android向导和团队领导. 他最喜欢的项目是做一个酒店娱乐系统的应用程序和后端.

Read More
作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

以前在

Welltok

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® community.