注册

Kotlin开发者尝试Flutter——错怪了Dart这门语言

你的段落文字.png


前言


我曾经是Java安卓开发者,进入大学后了解并且转向了Kotlin开发安卓,时至今日已经有了一年时间,Kotlin带给我的体验实在是太惊艳了,我深信这就是我最喜欢的语言了。


抱着这种看法,我发现了Flutter+Dart这种抽象的组合,大量的嵌套好像让我在写ifelse,这导致了我迟迟没有接触Flutter跨平台框架,当然还有一些其他原因。


其实在之前Flutter的跨平台能力已经惊艳到我了,这次寒假正好有机会让我学习它。


当我试着用它完成业务时,我发现好像也不是那么不可接受,我甚至还有那么点快感,如果你写过安卓Compose那么你会更加觉得如此,因为在UI和业务的关系上它真的太容易绑定了,我不再考虑像XML监听数据变化,只可惜Dart语法仍然在一些地方让我感觉到不太好用,还是得Kotlin来,等等,那我不就是想要Compose吗?


哈哈,不要着急,为什么这个项目是Flutter而不是KMP随后我们再说。


其实我本身没有很严重的技术癖,面对新的事物和技术,一旦有合适的机会我都是愿意试一试,比起框架的选择,我更加享受开发过程,思想转换为代码的那一刻至少我是享受的。


这次选择Flutter开发不意味着我会一直选择和追捧它,更不会放弃安卓原生和KMP的学习,因此也希望阅读这篇文章读者意识到这点,我作为原生开发者学习Flutter是希望扩展技能而不是代替原生,Flutter本身也不是这么想的,它更像是给大家了一个更低的开发门槛,让更多其他领域的创作者完成他们作品的一种媒介。



如果你希望快速了解Kotlin开发者使用Dart的开发体验,那么直接跳过下面两部分,直接阅读#错怪的Dart。



动机


我觉得主要动机由两部分组成吧,一部分是跨平台开发本身是我感兴趣的方向之一,另一边是未来工作可能需要吧,现在来看国内跨平台趋势还是比较明显的。


不过我更希望这次项目是体验移动跨平台开发,而不是真正的深入学习移动跨平台开发。为此,我希望可以找到学习成本和项目质量相平衡的开发方式,很遗憾我没有那么多的精力做到既要还要,这是我必须面临的选择。


面对众多跨平台框架下我还是选择了Flutter,这主要与它的跨桌面端和生态完善有关,毫无疑问,Flutter有许多的成品组件,这让我可以快速轻松的上手跨平台开发


为什么是Flutter


这个项目的主要功能就是播放器,只不过这个播放器比较特殊,后续文章我们会揭晓它。


单就网络音频播放器开发任务而言,假设使用KMP可能没有现成封装好的库来给我用,可能许多开发者考虑没有就造一个,很遗憾,我不太具备这样的能力,我们需要同时对接多个平台的媒体播放,无论开发周期,单就这样的任务对我已经是很难了。


好吧,我想必须承认我很菜,但是事实如此,因此我选择了更加成熟的Flutter,避免我写不出来,哈哈哈哈。


不过我们今天先不谈Flutter,我们看看Dart。


错怪的Dart


对Dart的刻板印象是从我第一次见到Flutter的语法时形成的,第一次见到Dart时我还没有接触Kotlin。


看着有点像Java,还有好多_的名字是什么鬼东西、怎么要写这么多return、为什么有个?、总之就是反人类啊!!!


当我真正尝试去编写Flutter程序时,我发现,嗯,错怪Dart了,特别是因为我了解Kotlin后,Kotlin和Dart也有几分相似之处,这体现在一些语法特性上。


空安全


可空类型在Kotlin上可以说相当不错,在Dart上也可以体验到它,虽然它是类型前置,但是写法倒是一样的在类型后加上"?"即可。


class AudioMediaItem {
String title;
String description;
AudioMediaType type;
String? mediaUrl;
String? bvId;
//省略其他代码.....
}

当我们试图使用AudioMediaItem的对象时,我们就可以像Kotlin那样做,注意mediaUrl现在是可空的。


audioMediaItem?.mediaUrl,如果我们认为这个属性一定有值,那么就可以使用audioMediaItem!.mediaUrl,需要注意的是,dart中是"!"而不是"!!"


如果你希望使用Kotlin的Elvis操作符 ?: ,那么你可以这么做


audioMediaItem?.mediaUrl ?? "default";

对应Kotlin的


audioMediaItem?.mediaUrl ?: "default"

在这方面,dart和Kotlin是非常相似的,因此,你可以非常平滑的迁移这部分的开发体验和理解。


延迟初始化


在Kotlin中,我们可以使用lateinit var定义一个非空延迟初始化的变量,通俗的讲就是定义一个非空类型,但是不给初始值。dart也有对应从关键字,那就是late了。


late String name;

相当于Kotlin的


lateinit var String name

我们知道延迟初始化意味着这个值必定有值,只是我们希望这个值在代码运行过程中产生并且初始化,初始化后再使用该值,否则就会空指针了。


如果你已经熟悉了Kotlin的lateinit,那这里也可以平滑迁移了。


但是在Android Studio 2023.1.1我发现个有意思的事情。


late String? name;

ide没有提示这是错误的,我没试着运行,但是我觉得这应该是不合理的。


扩展函数


扩展函数在Kotlin当中可以说相当重要,许多内置函数都是这个特性所带来的。


在Kotlin中,我们通过 被扩展的类名.扩展函数名(){} 这样的写法就实现了一个扩展函数。


fun String.toColorInt(): Int = Color.parseColor(this)

Dart中也存在扩展函数的语法糖!


extension StringExtension on String {
/// 将字符串的首字母大写
String capitalize() {
if (isEmpty) {
return this;
}
return '${this[0].toUpperCase()}${substring(1)}';
}
}

其中,StringExtension只是这个扩展的名字,相当于一个标志,可以随便起,on String则代表扩展String类,那么capitalize 自然就是扩展的方法名了。


将Kotlin的内置函数带入


Kotlin的内置函数实在是太棒了,下面以also和let为例子,模仿了Kotlin的扩展函数,只可惜Dart的lambda不太能像Kotlin那样,还是有一些割裂。


extension AlsoExtension<T> on T {
T also(void Function(T) block) {
block(this);
return this;
}
}

extension LetExtension<T> on T {
R let<R>(R Function(T) block) {
return block(this);
}
}

//用法
String demo = "xada".let((it) => "${it}xadadawdwad");


emm不过因为没办法直接传this,在变量很长或者类型可空时还有点用。


顶层函数


Kotlin中,我们有时候需要在全局使用一些函数,但是不希望写在类里,而是随时随地直接可以调用或者拿到。


注意这些代码不在类里


val json = Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
}

var retrofit = Retrofit.Builder()
.baseUrl("https://api.juejin.cn/")
.addConverterFactory(json.asConverterFactory(MediaType.parse("application/json;charset=utf-8")!!))
.build()


在某个类需要我们就直接写retrofit.xxxx() 就可以了,我们不需要再单独从类中找。


Dart也有这样的功能


final _cookieJar = CookieJar();

final Dio dioClient = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 5),
contentType: Headers.jsonContentType,
persistentConnection: true,
))
..transformer = BackgroundTransformer()
..let((it) {
if (!kIsWeb) {
it.interceptors.add(CookieManager(_cookieJar));
return it;
} else {
return it;
}
});


上面的例子只是写了变量,写函数也是一样的,都可以直接在全局任何的位置调用。


高阶函数


在Kotlin中,高阶函数是特殊的一种函数,这种函数接受了另一个函数作为参数。


我们以Kotlin的forEach函数为例子:



public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}

// 用法
fun main() {
val demoList = listOf("da", "da", "da")
demoList.forEach {
println(it)
}
}

forEach本身扩展了Iterable,但是它的参数非常特殊,我们看看action参数的类型:


(T) -> Unit,这是Kotlin匿名函数的写法,意味着这个函数有一个参数,类型为T泛型,这个参数也没有起名字,所以就只有类型T在。


这种情况,在Java中这种实现一般是接口类,我们需要实例化这个匿名类,假设这个接口只有一个方法,那么就可以转换为lambda的写法。


在Kotlin里我们可以直接写为lambda的形式,要方便很多,由于只有一个参数,那么kotlin默认就叫it了。


OK回顾完Kotlin,我们看看Dart:


void forEach(void action(E element)) {
for (E element in this) action(element);
}

//用法
List<String> demoList = ["da","da","da"];

demoList.forEach((element) {
print(element);
});

其实差别不大,只是我们需要写void当作这个参数的类型,内部写法没有太大差异。


不过,Dart的lambda更加贴近JS,写法基本上是一模一样。


相信如果你已经掌握了Kotlin的高阶函数,那么在Dart尝试也是不错的。


运算符重载


Kotlin当中有个不太常用的东西,叫运算符重载,它在Dart中也有。


public operator fun <T> Collection<T>.plus(elements: Iterable<T>): List<T> {
if (elements is Collection) {
val result = ArrayList<T>(this.size + elements.size)
result.addAll(this)
result.addAll(elements)
return result
} else {
val result = ArrayList<T>(this)
result.addAll(elements)
return result
}
}

//用法
val demoList = listOf("da", "da", "da") + listOf<String>("add")

可以看到kotlin通过operator关键字配合扩展函数实现了这个功能,dart也可以模仿这种手段:


// 模仿
extension ListPlusOperatorExtension<T> on List<T> {
List<T> operator +(List<T> elements) {
List<T> result = this;
addAll(elements);
return result;
}
}

// 用法

List<String> demo1 = ["da","da"];

List<String> demo2 = ["da","d1a"] + demo1;

不过这里的加减乘除就是operator + 了。


总结


可以看得出,Dart也有部分我们在Kotlin中喜欢的特性,如果你已经掌握了Kotlin的基本语法,那么相信Dart对你来说也不是太大问题,你可以平滑的迁移一些在Kotlin中的知识到Dart上去。


起初我是很坑距使用Flutter的,现在看见Dart的特性,我似乎又接受了一些,好吧,对于Flutter开发、布局约束和其他感受我在下一篇文章再分享给大家吧。


最后感谢大家看到这里,还有什么好玩的特性欢迎在下面留言,文章内容有错误请指出。


作者:萌新杰少
来源:juejin.cn/post/7329874214378078245

0 个评论

要回复文章请先登录注册