0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

iOSAdvent Calendar 2024

Day 15

Skip toolsを使ったSwiftベースのクロスプラットフォーム開発 with Supabase

Last updated at Posted at 2024-12-15

突然のお達し

いきなりですが、みなさんは上長などにクロスプラットフォーム勉強してよ
と言われたことはありませんか?

かく言う私は、今勤めている会社の社長から直々に言われてしまいました。

でも、FlutterやReactNativeを触る意欲が湧かないんだよなぁ...

(DartでUI書くとネストが深くなりがちだし、(個人的な感想です)
ReactNativeはTypeScriptが個人的に好きじゃない...
linter突っ込んでもeslint ignore やbiome ignore, tsignore など好き勝手にされた挙句、心が折れた Any (個人的なトラウマです) Any

僕はSwiftで開発したいの...

という時に見つかったのがSkip Toolsです

Skip Toolsとは

Swiftで書いたコードをビルド時にkotlinへトランスパイルしてくれる優れ者

今年の8月ごろにversion 1.0.0が正式リリース
※ 2024/12/15現在の最新バージョンは1.2.8

それなりに開発が活発であり、どんなもんなのかなぁ〜と試しに手を動かして試してみることとしました

環境セットアップ

公式Docに環境設定方法が記載されているので、こちらを参照

基本的な流れは

・brew経由でskipをインストール
・Android Studioインストール
・ライセンスキーを設定
・CLIからプロジェクトを作成

になります。
今回はこちらについては詳しく記載しません

(確か、他にも設定方法を記載している人はいたと思うので

いざ、開発

と、言っても特に作りたいな〜ってものが思い浮かばなかったので普段お世話になっているSupabaseに繋げてみることにしました

※ Supabaseのローカル開発環境セットアップは省略

最初はsupabase-swiftとsupabase-ktの両方を入れて、コード上でこねくり回してsupabase連携するのかな?
と思ったのですが、公式がライブラリを提供していました

※ ちなみにSkip ToolsはSwiftのコード上にkotlin codeを組み込んだり、java kotlinライブラリを導入することもできます(ネイティブコードに直接アクセスできるのは嬉しい限り

Swift Package導入

と、ここで第一関門、Packageの導入

普段はXcodeのPackage Dependenciesから雑に突っ込むだけでいけてましたが、Skipはどうやらそのようにはいかないみたいです

project root/project name/Package.swiftに直接記述して

let package = Package(
    ... 省略
    dependencies: [
        .package(url: "https://source.skip.tools/skip.git", from: "1.0.0"),
        .package(url: "https://source.skip.tools/skip-ui.git", from: "1.0.0"),
        // git urlとversionを指定
        .package(url: "https://source.skip.tools/skip-supabase.git", from: "0.0.5"),
    ],
    targets: [
        .target(
            name: "ProjectName",
            dependencies: [
                .product(name: "SkipUI", package: "skip-ui"),
                // targetの追加
                .package(url: "https://source.skip.tools/skip-supabase.git", from: "0.0.5"),
            ],
            resources: [.process("Resources")],
            plugins: [.plugin(name: "skipstone", package: "skip")]
        ),
        ... 省略
    ]
)

terminalからswiftコマンドを実行してpackageの解決を実施

cd path/to/root/project-name
swift package resolve

supabase signIn

ここまで終わったら
supabaseのアクセス情報とsignIn処理を書いて

import Foundation
import SkipSupabase

#if SKIP
let debugUrl = "http://10.0.2.2:54321"
#else
let debugUrl = "http://127.0.0.1:54321"
#endif

fileprivate let client = SupabaseClient(
    supabaseURL:
        URL(string: debugUrl)!,
    supabaseKey: "anon-key"
)

struct MySupabaseClient {
    func signIn(user: User) async throws {
        let ac: AuthClient = client.auth
        try await ac.signIn(email: user.mailAddress, password: user.password)
    }
}

あとはログイン画面を雑に作って、ログインできたら画面遷移するような処理を書く
めんどくさいので省略

Screenshot_1734226377.png Screenshot_1734226413.png
Android Login Android Logined
Simulator Screenshot - iPhone 16 Pro Max - 2024-12-15 at 10.32.55.png Simulator Screenshot - iPhone 16 Pro Max - 2024-12-15 at 10.33.32.png
iOS Login iOS Logined

Table操作

続いて、テーブル操作を見ていきます

開発者に尋ねたところ、Testを見ればわかるようになっているよ
とのことでした

select

let countryCount0: PostgrestResponse<Void> = try await client
    .from(table)
    .select(count: CountOption.exact)
    .execute()

Insert

let icountryResponse: PostgrestResponse<[Country]> = try await client
    .from("countries")
    .insert(Country(id: 1, name: "USA"), returning: PostgrestReturningOptions.representation)
    // .single() // TODO: handle single results
    .execute(options: FetchOptions(head: false, count: CountOption.exact))

delete

let voidResponse: PostgrestResponse<Void> = try await client
    .from("countries")
    .delete()
    .gte("id", value: 0)
    .execute()

update

try await client
    .from("countries")
    .update(["name": "Australia"])
    .eq("id", value: 1)
    .execute()

第二関門 rpc functionが呼び出せない...

どうやら、rpc functionがサポートされていませんでした

ってことでContributeしていきましょう

skip supabaseをローカルに落として、local packageとして読み込ませる

Package.swift

dependencies: [
        .package(url: "https://source.skip.tools/skip.git", from: "1.0.0"),
        .package(url: "https://source.skip.tools/skip-ui.git", from: "1.0.0"),
        .package(path: "path/to/root/skip-supabase/"),
        // .package(url: "https://source.skip.tools/skip-supabase.git", from: "0.0.5"),
    ],

スクリーンショット 2024-12-15 11.00.15.png

そして二、三日悩む...(うーん。難しいなぁ〜

kotlin compile時の挙動やsupabase-swift, supabase-ktの仕様の違いに苦労し、妥協を重ね

rpcのparameterにはDictionaryを指定することで運用回避的な対策
動くしまぁいいか()というダメダメ思想で PR作成
(色々あったんですよ、compilerの仕組みが違うからGenericsの型情報消えたり。ここに書くと長文になりそうだから一旦割愛)

スクリーンショット 2024-12-15 10.55.28.png

無事マージしてリリースされました

で、実際の呼び出しはこんな感じ

受け渡す際は全部Stringにして

    func register(title: String, startDate: Date, endDate: Date, details: [ExampleDTO], expenses: [RegisterExpenseDTO]) async throws {
        let detailsJsonData = try! JSONEncoder().encode(details)
        let detailsJsonString = String(data: detailsJsonData, encoding: .utf8)!
        
        let _ =
        try await client
            .rpc(
                "insert_education_plan",
                params: [
                    "plan_name_input": title,
                    "start_date_input": startDate.format(style: "YYYY/MM/dd"),
                    "end_date_input": endDate.format(style: "YYYY/MM/dd"),
                    "details_input": detailsJsonString
                ]
            )
            .execute()
    }

postgre function側で受け取る際に適宜型情報を調整
...JSONはうまく行かなかったのでtextで受け取って、jsonに型変換してる

CREATE OR REPLACE FUNCTION public.insert_example(
    plan_name_input text,
    start_date_input date,
    end_date_input date,
    details_input text,  -- 文字列として受け取る
) 
RETURNS VOID 
LANGUAGE plpgsql 
AS $function$
DECLARE 
    expenses_jsonb jsonb;  -- jsonbに変換したもの
BEGIN 
    -- 文字列をjsonbにキャスト
    details_jsonb := details_input::jsonb;

supabase edge functionとかも対応していないので、時間があったらcontributeしてください🙇(僕が喜びます)

感想

個人的にはSwift + SwiftUIでAndroidも作れることや、困ったらkotlinのコードを記載できるのでアリだと思います
(途中で添付したカレンダーも実はkotlin + kotlinライブラリで作ったものだったりします

SwiftUIでかける基本的なものはサポートされているし、swiftやkotlin, javaで作られてきた資産を使い回すことができること

まだまだ、全てをワンソースで書き切るという段階には至っていなかったですが、今後の成長に期待したいな!と思わせるツールでした(現状、仕事で使うなら筋肉が必要。笑)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?