🌟

Dart(Flutter) / Swift(iOS) / Kotlin(Android) 色々比較

2022/06/26に公開

dart/swift/kotlinでコードを書いてて、これswiftだったらどう書くのだっけという状態によくなるので、自分がよくそうなるところをメモする。
書いてたらDart(Flutter) / Swift(iOS) / Kotlin(Android) な比較にもなってしまっているのでその辺はすいません。
また、個人の経験での感覚で書いてしまっているので、もっと詳しい人はより正しい見解を述べられるのではとも思うので、へぇそういうふうに思ってるんだくらいに思って見てもらえると良いかもしれません🙏

nullだったらデフォルト値を指定

dart

  final Item? item = null;
  final name = item?.name ?? "default";
  print(name);

kotlin

    val item: Item? = null
    val name = item?.name ?: "default"
    println(name)

swift

let item: Item? = nil
let name = item?.name ?? "default"
print(name)

if ~ else ~ で変数を指定する

dart

final isHoge = true;
final name = isHoge ? "a" : "b";
// kotlinみたいに if else 形式では書けない

kotlin

val isHoge = true
val name = if (isHoge) {
    "a"
} else {
    "b"
}

swift

let isHoge = true
let name = isHoge ? "a" : "b"
// kotlinみたいに if else 形式では書けない

日時周り

dart

標準のDateTimeクラスが結構優れている様に感じます。

kotlin

標準のDate型, Calendar型で頑張るパターンもあるけど、どちらかというとライブラリを利用した方が良い気がします。 kotlinx-datetimeとかjoda-time などの日時系のライブラリを使うのがよくあるパターンだと思われる。

swift

標準のDate, Calendar型でExtensionして頑張ることが多い気がするが実際他のプロジェクトではどうなのかあまりわかってないです。
なんとなく、Kotlin(Android)よりも標準のDate, Calendarなどで頑張れる気がします。

エンティティやデータ用の構造体

dart

freezed 使う

import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'item.freezed.dart';


class Item with _$Item {
  const factory Item({required String name}) = _Item;
}

kotlin

data class Item(val name: String)

swift

struct Item {
    let name: String
}

構造体のプロパティに複数の値をセット

dart

.. を使う

class Item {
  Item();
 
  String name = "";
  String label = "";
}

void main() {
  final item = Item()..name = "a"
                     ..label = "b";
  print(item.name);
  print(item.label);
}

kotlin

with を使う

data class Item(var name: String = "", var label: String = "")

fun main() {
    var item = Item()
    with(item) {
        name = "a"
        label = "b"
    }
    println(item)
}

swift

swiftは普通にやるしかない。少なくとも私はこれ系のswiftのシンタックスシュガーを知らない。

struct Item {
    var name: String = ""
    var label: String = ""
}
var item = Item()
item.name = "a"
item.label = "b"
print(item)

nullチェック、スマートキャスト

dart

スマートキャストできる

class Item {
  Item(this.name);
 
  String name = "";
}

void main() {
  final Item? item = Item("a");
  // finaなどの固定値に対して item == null や != nullで nullじゃないことが解れば、その後は
  // ?などでアンラップしなくても良くなる
  if (item == null) {
    return;
  }
  print(item.name);
}

kotlin

dartと同じようにスマートキャストできる

data class Item(var name: String = "")

fun main() {
    val item: Item? = Item(name = "a")
    if (item == null) {
        return
    }
    item.name = "a"
}

swift

スマートキャストはできない。その代わりswiftだけguard文がある。

struct Item {
    var name: String = ""
}

func smartCastExample() {
    let item: Item? = Item(name: "a")
    if item == nil {
        return
    }

    // スマートキャストできないので ? が必要
    print(item?.name)
}

func guardExample() {
    let item: Item? = Item(name: "a")
    guard let item = item else {
        return
    }

    print(item.name)
}

guardExample()
smartCastExample()

List操作まわり

dart

標準でも色々できるが、
collection (多分dart公式ライブラリ) というpackageがあって便利。
それ使うと、 firstOrNullfirstWhereOrNull とかが使える様になる。

kotlin

標準でも色々できると思うが、このライブラリで色々できるとらしい(わたしはよく知らない)。
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/

swift

ここに色々書いてある。基本的に標準で結構色々できるイメージ。
https://developer.apple.com/documentation/swift/collection

REST API Client

dart

dio が有名な気がします。
公式だと httpというパッケージが紹介されていました。
(どちらもわたしは使ったことがないです)

kotlin

retrofitがデファクトスタンダートだと思われる。

swift

alamofireがデファクトスタンダートだと思われる。

パッケージ管理

dart

pubspec一択だと思われる。

kotlin

gradle一択だと思われる。
実はgradleでもdependency lockingできるようになったらしい(結構前らしいが)。
http://blog.64p.org/entry/2020/05/13/100039

swift

いっぱいある。

Swift Package Manager

Swift公式。今後の本命。
https://www.swift.org/package-manager/

cocoapods

SPMが台頭してくる前までは、一番利用されていたと思われる。Flutterのプロジェクト作るとiOSのプロジェクトのところにはPodfileができていて、Flutterアプリはcocoapodsに依存していると思います。
https://cocoapods.org/

cocoapodsで入れたライブラリはソースからビルドされるので、その分ビルドが長くなる問題はあってそれを解消するためにpre buildしたライブラリをリンクするという cocoapods-binary もあったらしいが、いつの間にか開発が止まっている様でした。

carthage

カッセージとかカルタゴとか呼ばれてて、ビルド済みのモジュールをリンクする感じだからcocoapodsよりもリンクするアプリ側のビルドが速くなるというので一時期結構流行ったと思います。
いまはそこまで流行ってはいないとは思うものの使っているプロジェクトはそこそこあるんじゃないかなと思います。
https://github.com/Carthage/Carthage

モック化

dart

dartは Implicit interfaces という思想があるので、interfaceみたいなのを定義せずに実体のクラスをインターフェースとして利用してモックする、というのが主流のようです(わたしはabstruct classでinterface定義してしまってますが)。riverpodのテストのRepositoryのモック化もそんな感じになっています。
https://riverpod.dev/ja/docs/cookbooks/testing/

mock化のライブラリはmockito とか mocktailなどありそうです(まだ使ったことはないです)。

ライブラリ使わなかったらこんな感じだと思います(riverpod testの説明からのコピペ...)。

class Todo {
  Todo({
    required this.id,
    required this.label,
    required this.completed,
  });

  final String id;
  final String label;
  final bool completed;
}

class Repository {
  Future<List<Todo>> fetchTodos() async => [];
}

class FakeRepository implements Repository {
  
  Future<List<Todo>> fetchTodos() async {
    return [
      Todo(id: '42', label: 'Hello world', completed: false),
    ];
  }
}

kotlin

mock化のライブラリはmockk とか mockito-kotlinなどありそうです。

ライブラリ使わなかったらこんな感じだと思います。

data class Todo(val id: String, val label: String, val completed: Boolean)

interface Repository {
  suspend fun fetchTodos(): List<Todo>
}

class FakeRepository: Repository {
  override suspend fun fetchTodos(): List<Todo> = listOf(
      Todo(id = "42", label = "Hello world", completed = false)
  )
}

swift

mock化のライブラリはmockolo とか Cuckooなどありそうです。

ライブラリ使わなかったらこんな感じだと思います。

struct Todo {
    let id: String
    let label: String
    let completed: Bool
}

protocol Repository {
    func fetchTodos() async throws -> [Todo]
}

struct FakeRepository: Repository {
    func fetchTodos() async throws -> [Todo] {
        [
            Todo(id: "42", label: "Hello world", completed: false)
        ]
    }
}

DI周り

DIライブラリは、本来の目的である依存性注入以外の機能を有しているかどうかが広く普及するかどうかのポイントなんじゃないかとわたしは感じています。
実際、下記で紹介しているriverpodやDaggerはDI以外の機能を有していると思っています。

dart

いまは riverpodが人気だと思われる。
ちょっと前まではriverpod公式の説明にDIコンテナと状態管理を簡単にするライブラリという説明があったと思うのですが、今はパッと見、DIコンテナであることが書かれてなさそうで、今見たらリアクティブ・キャッシングとデータバインディングを実現するフレームワーク と書かれていました。
riverpodはDI以外にアプリの状態管理をできて、むしろそれが主要な機能になっていると思います。

kotlin

いまは公式でも推奨しているDaggerやDagger Hiltがデファクトとなっていると思われる。koinとかも話題になったことはあったがいまどうなのかはよく知らない。
Dagger使うと、アプリのライフサイクルと関連づけできたり、シングルトン(androidでdagger使わないでstatic変数を使ったシングルトンパターンを適用すると、static変数が勝手に初期化されたりするので苦労します)パターンが簡単にできたりする。

https://developer.android.com/training/dependency-injection/dagger-basics?hl=ja
https://developer.android.com/training/dependency-injection/hilt-android?hl=ja

swift

swift(iOS)に関しては、あまりデファクトスタンダートなDIライブラリを私は知らないです。
swinject とかneedleが存在していますが、デファクトスタンダートではないような気がします。
ライブラリ使わずにコンストラクタインジェクション(できない場合はフィールドインジェクションとか)とか、別のアプローチでFactory Patternとかが利用されていると思われます。

ローカルDB

dart

👇の拝見させていただいたりして、いろいろあってデファクトはよくわらないのですが、
https://kabochapo.hateblo.jp/entry/2020/02/01/144411

個人開発しているFlutterアプリではisarを利用しています。

kotlin

多分もうRoomがデファクトスタンダートな気がします。
昔はRealmが流行りかけたと思うが昔はバグも結構あってiOSのようには流行らなかった印象がある。

swift

RealmiOS標準のCoreDataなど。
おそらく、Realmが人気だと思われる。

アーキテクチャ

アーキテクチャはある程度そのときの流行り廃りがあると思うのですが、あまりそういうのに左右されずに判断していけるのが理想なのだろうなと思います。
実際は流行り廃りなどの影響を受けることはよくあるので、途中でアーキテクチャが変わってアプリ内に複数のアーキテクチャが存在するような状況によくなると思うのですが、それはそれでアリだと思いますし、許容/移行しつつアプリの開発保守していければいいと思います。
また、宣言的UI(Flutter, SwiftUI, Jetpack Compose)かどうかもアーキテクチャの選定に影響を与えると思います。

dart(Flutter)

基本的にはriverpodprovider, getxなどの状態管理パッケージを利用したアプリの設計が主流だと思います。
Flutterのアーキテクチャは、状態管理を何でやるかが設計に影響を与える部分がそれなりにあるんじゃないかと思われますが、いまはriverpodが人気があると思います。

個人開発しているFlutterアプリ では、
riverpodを利用したレイヤードアーキテクチャを採用しているのですが、本来ドメインレイヤーとするところでfirestoreを直接操作したりしているので、そのあたりのIFを
もっと抽象化した方がすっきりするかな、とアプリが複雑になってきて思い始めています。
最初だからレイヤー細かく区切らずというふうに思ってたが、長期で育てていくことを考えると、最初からUI, State, ドメイン、データレイヤーくらいにはレイヤーを区別しておいた方が良いのだろうなと思ってきてます。

https://note.com/hikarusato/n/n59ad456faaf1
https://zenn.dev/hs7/articles/617549d7a3e8f8

👇は個人的にはとても良い記事だと思いました。
https://developers.cyberagent.co.jp/blog/archives/36149/

※色々書いておいて、わたしは会社でFlutterアプリを開発したことがないので、普通どうやっているのかはそんなによくはわかりません。

kotlin(Andorid)

公式で推奨しているアーキテクチャがあるし、Android Architecture Components(AAC)もあるので、
公式の推奨する通りにやっていくと良さそうに思います。

Jetpack Compose 用の設計も公式にあるようです。
https://developer.android.com/jetpack/compose/architecture?hl=ja

swift(iOS)

Apple公式のMVC がそんなに流行ってなくて色々存在していると思います。
個人的には、UIKitなら VIPERなどのMVP + Clean Architecture、SwiftUIならThe Composable Architecture(TCA)みたいなstateを管理するようなアーキテクチャが良さそうな気がしますが、SwiftUIのアーキテクチャはもう少し自分でも検証してみたい。
VIPERは結構前の記事ですが👇のがわかりやすかったです。
https://dev.classmethod.jp/articles/developers-io-2020-viper-architecture/

昔、RxSwiftがとても流行っていた時は双方向バイディング(ViewModel -> View, View -> ViewModel)を利用したアーキテクチャが流行っていたと思います(こういう感じのバインディングです)。
https://github.com/sergdort/CleanArchitectureRxSwift/blob/2a8e744380222278d1e58d4acc4ace0b10915893/CleanArchitectureRxSwift/Scenes/AllPosts/PostsViewController.swift#L26-L52

これの View -> ViewModel方向のバインディングがrx.sentMessage(#selector(UIViewController.viewWillAppear(_:))).mapToVoid().asDriverOnErrorJustComplete() という感じでviewのイベントをRxSwiftのObservableに変えていくのが結構面倒 + 過剰にRxSwift依存を高める様に感じているので、自分の個人開発しているiOSアプリでは、ViewModel -> View方向のバインディングのみかつRxSwiftを使わない形(代わりにクロージャーを利用)のMVVMにしています(これが正しいかはよくわからないですが個人開発なので)。

SDKのバージョン管理

dart(Flutter)

fvmasdf など
fvm使うと 最初に fvm って書かなければならなくなるので、 asdf を今後検討していきたいと👇の記事を見て思っています。
https://blog.dalt.me/2730

kotlin(Android)

kotlinもライブラリみたいな位置付けになっているので、gradleで管理する感じになっていると思います。

swift(iOS)

XcodeとiOS SDKやSwiftのバージョンがセットになっていると思います。

参考

https://www.linkedin.com/pulse/dart-vs-kotlin-swift-ultimate-reference-table-main-matuzenko

さいごに

間違ってるところなど、こういうのあるよなどあればコメントください🙏

Discussion