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

前言

我曾经是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上也可以体验到它,虽然它是类型前置,但是写法倒是一样的在类型后加上"?"即可。

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

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

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

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

dart 复制代码
audioMediaItem?.mediaUrl ?? "default";

对应Kotlin的

kotlin 复制代码
audioMediaItem?.mediaUrl ?: "default"

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

延迟初始化

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

dart 复制代码
late String name;

相当于Kotlin的

kotlin 复制代码
lateinit var String name

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

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

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

dart 复制代码
late String? name;

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

扩展函数

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

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

kotlin 复制代码
fun String.toColorInt(): Int = Color.parseColor(this)

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

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那样,还是有一些割裂。

dart 复制代码
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中,我们有时候需要在全局使用一些函数,但是不希望写在类里,而是随时随地直接可以调用或者拿到。

注意这些代码不在类里

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也有这样的功能

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函数为例子:

kotlin 复制代码
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:

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中也有。

kotlin 复制代码
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也可以模仿这种手段:

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开发、布局约束和其他感受我在下一篇文章再分享给大家吧。

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

相关推荐
Flutter社区7 小时前
使用 Flutter 3.19 更高效地开发
flutter·dart
一丝晨光7 小时前
逻辑运算符
java·c++·python·kotlin·c#·c·逻辑运算符
消失的旧时光-19438 小时前
kotlin的密封类
android·开发语言·kotlin
Forever不止如此9 小时前
【CustomPainter】绘制圆环
flutter·custompainter·圆环
wills77710 小时前
Flutter Error: Type ‘UnmodifiableUint8ListView‘ not found
flutter
AntDreamer16 小时前
在实际开发中,如何根据项目需求调整 RecyclerView 的缓存策略?
android·java·缓存·面试·性能优化·kotlin
AiFlutter1 天前
Flutter之Package教程
flutter
极客先躯1 天前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
Mingyueyixi1 天前
Flutter Spacer引发的The ParentDataWidget Expanded(flex: 1) 惨案
前端·flutter