初めてのkotlin

kotlinによるAndroidアプリ開発での気付き

JntelliJ IDEAのターミナルで、プロジェクトで使っているJAVA_HOMEを自動設定する Windows+bash編

IntelliJ IDEAのターミナルは、環境変数JAVA_HOMEが設定されていないようで、 そのままでは./gradlewコマンドが動きません;つД`)

なので、開いているプロジェクトで利用しているjavaにパスを通して使えるようにします(๑˃̵ᴗ˂̵)و

参考にしたと言うか、ほぼズバリの答えはこちら。

Add option to set JAVA_HOME to current project SDK path to the Terminal

今回私が試した環境は、WindowsIntelliJ IDEA + Git for Windowsになります。

どう実現したかも説明するので、異なる環境の方はご自身で改変してください。

まず、プロジェクト用ディレクトリ以外の場所(便宜上スクリプトディレクトリと称します)に、.bash_profileと言うファイルを作ります。

実のところファイル名も場所も何でもいいです。

Linuxを知っている人なら、これだけで何をしようとしているかが理解できるので、こうしています。

.bash_profile

JAVA_HOME=$(JDK_NAME=$(xmllint --xpath 'string(//component[@name="ProjectRootManager"]/@project-jdk-name)' .idea/misc.xml) && xmllint --xpath "string(/application/component/jdk[./name/@value='$JDK_NAME']/homePath/@value)" $APPDATA/JetBrains/IdeaIC2024.1/options/jdk.table.xml | sed 's|\$USER_HOME\$|'"$HOME"'|g')
PATH=$JAVA_HOME/bin:$PATH

では、やっていることの説明です。

まずはxmllintコマンドでプロジェクトの設定ファイル(プロジェクトルート/.idea/misc.xml)から使用しているJDKの名前を取得し、変数JDK_NAMEにセットします。

次に再度xmllintコマンドを使って、今度はIntelliJ IDEAがSDKとして取り込んでいるJDKのリスト(ユーザーのアプリルート/options/jdk.table.xml)からJDKの場所を特定し、JAVA_HOMEにセットしています。

赤字の部分は環境依存する部分で、異なるOS、インストール場所の変更、アプリケーションのバージョンなどによって変わる部分ですので、そこにoptions/jdk.table.xmlファイルがあることを確認してください。

次にこのスクリプトがターミナルが開く時に実行されるように設定します。

IntelliJ IDEAのメニューバーから、ファイル→設定→ツール→ターミナルと辿って、 アプリケーション設定のシェルパスを、 (初期値はPowerShellになっていると思います) Git\bin\bash.exeに切り替えてください。

そして、選択したコマンド名の後ろにパラメータ--init-file スクリプトディレクトリ/.bash_profileを加えます。

私の環境だと以下のようになります。

"C:\Program Files\Git\bin\bash.exe" --init-file D:\Development\IdeaProjects\.bash_profile

また、同じページの少し上にあるプロジェクト設定の開始ディレクトリは空のままとしてください。

(ターミナルはプロジェクトルートをカレントディレクトリにして起動します)

これで、ターミナルを開くとプロジェクトに応じたJAVA_HOMEが設定され、 javaへのパスが通りますので、 ./gradlewコマンドが使えるようになります。

マルチプロジェクト機能を使って、アプリ開発の傍ら、マルチプラットフォームな共通モジュールも一緒に開発しちゃおう

タイトルながっ!

皆さんは個人でアプリを作る時、 ひとつのプロジェクトの中に全てのコードを入れちゃって、 別のアプリを作る時は共通で使えそうな部分をコピって使いまわしていますか?

私は必ず共通ライブラリを作りながらアプリを作っています。

でも、今までIntelliJ IDEAでのマルチプロジェクトなアプリ開発のやり方が分からず、 共通モジュールのソースディレクトリを、アプリのソースディレクトリ内にシンボリックリンクを貼って、 一緒にビルドすると言うやり方をやっていました( ´•ᴗ•ก; )

と言うわけで、 今回開発中のプロジェクト(メインプロジェクト)に開発中のマルチプラットフォームな共通モジュール(サブプロジェクト)をインクルードすることで、 並行開発できるようにします。

メインプロジェクト、サブプロジェクトが既に存在していて、 それぞれが単独でビルドできる状態になっている前提です。

また、バージョンカタログも導入済として書きますので、 導入していなければ、先に前の記事を参考に導入を済ませてください。

尚、触るのはメインプロジェクト側だけです。

サブプロジェクト側でエラーが出たとしても、 サブプロジェクトが単独でビルドできる限り、 メインプロジェクト側の設定の問題だと思います。 たぶん( ´•ᴗ•ก; )

なので、まずはサブプロジェクトのインクルードの設定です。

settings.gradle.kts

・・・

rootProject.name = "HelloWorld"

include(":CommonLib")
project(":CommonLib").projectDir = File("X:/foo/bar/CommonLib")

:CommonLibは各自のサブプロジェクト名に置き換えてください。

File()の中についてもサブプロジェクトの場所に置き換えてください。

次に、メインプロジェクト側からサブプロジェクトをビルドできるようにする設定と、 ソースから見えるようにする設定を加えます。

build.gradle.kts

plugins {
//    kotlin("jvm") version "2.0.0"
    alias(libs.plugins.kotlinJvm)
    alias(libs.plugins.kotlinMultiplatform) apply false
}

group = "com.exsample"
version = "1.0-SNAPSHOT"

dependencies {
    implementation(project(":CommonLib"))
}

サブプロジェクト側のプラグインの内、メインプロジェクトで使われていないものを持ってきて、 更に後ろにapply falseを付け加えてください。

最後に、サブプロジェクトをimplementationして完了です。

IntelliJ IDEA 2024.1.4 Community Edition、Gradle 8.5で確認しました。

バージョンカタログの導入 IntelliJ IDEA

私はマルチプロジェクトをやりたかっただけなのですが、 そのやり方を検索していると決まってバージョンカタログの話が出るので、 どうせplugin内のバージョン指定で変数が使えず不便だったので、 ついでにやっちゃえって思ったらハマったので、 どうやって実現したかを書いておきます。

まずはバージョンカタログを使えるようにします。 と言うか、正確にはデフォルトで使えるようにはなっているのですが、 settings.gradle.ktsをいじらないと名前解決のエラーでbuild.gradle.ktsの構文解析ができなくなるので、 その対策をします。

settings.gradle.kts

pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
        }
    }
}

plugins {
    id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}

rootProject.name = "HelloWorld"

create("libs") {}の1行のために、3日も費やしてしまいました.˚‧º·(°இωஇ°)‧º·˚.

次にバージョンカタログファイルを作ります(サンプル)。

gradle/libs.versions.toml

[versions]
kotlin = "2.0.0"
ktor = "2.3.12"

[libraries]
ktor-client = { module = "io.ktor:ktor-client", version.ref = "ktor" }

[plugins]
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }

この例だと、 kotlinJvmと言うエイリアスでkotlin.jvmのバージョンを2.0.0に指定しますと言う意味になります。

そして最後に、build.gradle.ktsでエイリアスを使った指定を行いますが、 バージョンカタログの内容を反映させるために、 一旦プロジェクトを読み込み直してから編集してください。

build.gradle.kts

plugins {
//    kotlin("jvm") version "2.0.0"
    alias(libs.plugins.kotlinJvm)
}

group = "com.exsample"
version = "1.0-SNAPSHOT"

dependencies {
  implementation(libs.ktor.client)
}

バージョンカタログでエイリアス名にハイフンを使った時は、gradle内で使う時はドットに変換して指定してください。

この例では、ktor-clientで定義したものをlibs.ktor.clientで指定しています。

IntelliJ IDEA 2024.1.4 Community Edition、Gradle 8.5で確認しました。

正規表現

ここでは正規表現の文法については扱いません。

Regexのメソッドの使い方にいつも悩むので、 その覚書としてのメモになります。

perl正規表現は、$str=~ /abc/と書きさえすれば、 正規表現を活用するあらゆる処理が可能となりますが、 kotlinだとなかなかそうは行きません。

判定だけを行いたい時、 一致箇所が欲しい時、 複数の一致箇所を全て抽出したい時で、 求める戻り値が違うので、メソッドを使い分けなければなりません。

また、部分一致なのか($str=~ /abc/)、 全体を一致させたいのか($str=~ /^abc$/s)でもメソッドが分かれていて、 しかも、メソッド名に法則性を感じないので、 これらの使い分けでいつも悩みます。

なので、整理のために一覧にしました。

perlなど、正規表現を多用するスクリプト系から来た人は、 ^ $ \A \zなどのメタ文字が有効なようですので、 部分一致だけ覚えればいいと思いますが、 一応全て書きます。

メソッド 戻り値
部分一致系
containsMatchIn Boolean
find MatchResult
findAll Sequence<MatchResult>
全体一致系
matches Boolean
matchEntire MatchResult
分割
split List<String>
置換
replaceFirst String
replace String

拡張子の取り除き方

androidではあまり使う機会はないかもしれませんが、 応用できるので、使用経験があまりない方も知っておいて損はないかと思います。

拡張子を取り除く方法について、 ネットを調べると、splitやlastIndexOfを用いた方法がたくさんヒットするのですが、 これって実は、ディレクトリに拡張子が付いていると正しく動作しないことがあります。

なので、別のアプローチで拡張子を除外してみました。

    val regex = Regex("""\.[^./\\]+$""")
    println("""aaa\bbb.txt""".replaceFirst(regex,""))
    println("""aaa\bbb.ccc.txt""".replaceFirst(regex,""))
    println("""aaa.dir\bbb.txt""".replaceFirst(regex,""))
    println("""aaa.dir\bbb""".replaceFirst(regex,""))
    println("""aaa\bbb""".replaceFirst(regex,""))

実行結果です。

aaa\bbb
aaa\bbb.ccc
aaa.dir\bbb
aaa.dir\bbb
aaa\bbb

ファイルに拡張子がなくても、ディレクトリに拡張子があっても、正しく動作しました。

正規表現って、perlのプログラムではとてもたくさん登場する便利な機能なのですが、 JavaC++のサンプルでは、あまり登場しませんね。

私もRegexはどのメソッド使えばいいか覚えられていないので、使い辛いです。

正規表現の説明

\. ドット文字(ドットのみだと一文字と言う意味になるので)

\\ バックスラッシュ(または円サイン)

[^a-z]+ 指定された文字を含まない文字列と言う意味

$ 行末(または文末)

\.[^./\\]+$ ディレクトリを含まない最後のドット以降と言う意味

java.langなどjava系のクラスが利用できない

今回、バッチプログラムを書くにあたり、 せっかくなのでバッチでもkotlinを使ってみようと、 IntelliJ IDEAでkotlinのプロジェクトを作ってみたら、 タイトルのとおり、何故かjava.~に由来するクラスが一切使えない状態でした。

build.gradleファイルを触ってみたり、 Mavenでモジュールを漁ってみたりしたけどダメ。

ネットで調べてそれらしいものを色々チェックしてみましたが、 どれも効果がありませんでした。

で、ふとIntelliJ IDEAはどこにインストールされたJavaを見てるんだろうと思い、 いっそ新しいSDKを入れてみようと言うことで、

ファイル→プロジェクト構造 →SDK→+Download JDK...

にてOracle OpenJDKをダウンロードし、

プロジェクト構造→プロジェクト→プロジェクトSDK:

の欄をダウンロードしたJDKに変えたら、 無事解決しました^^

単にIntelliJ IDEAと一緒にインストールしたJDKに不足があっただけのようですね。 たぶん、インストール時の設定に問題があったんだと思うのですが、 それを確認する画面って無いのかな?

バナー広告を設置する

Androidアプリを作れば、やはり収益に結びつけたくなるのが人情。

と言うわけで、バナー広告を設置します。

但し、ここではサンプルIDを用いて広告が表示されるのを確認するまでとします。

実際にアプリを公開する際には、 自身でIDを発行し、それに差し替える必要がありますのでご注意ください。

build.gradle

allprojects {
    repositories {
        ・・・
        google()
    }
}

google()allprojectsセクションに書かれていることを確認します。 (無ければ追加)

app/build.gradle

dependencies {
    ・・・
    implementation 'com.google.android.gms:play-services-ads:19.3.0'
}

Mobile Ads SDKを組み込みます。

main\AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest ・・・
    >

    <application ・・・
    >
        ・・・
        <!-- Sample AdMob App ID: ca-app-pub-3940256099942544~3347511713 -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-3940256099942544~3347511713"/>

    </application>

    <!-- ネット接続の許可 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

</manifest>

AdMob用アプリIDの設定と、ネットワーク接続の許可を追加します。

※ca-app-pub-3940256099942544~3347511713は、テスト用のアプリIDです。

res\layout\activity_main.xml

    <com.google.android.gms.ads.AdView
        xmlns:ads="http://schemas.android.com/apk/res-auto"
        android:id="@+id/adView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        ads:adSize="BANNER"
        ads:adUnitId="ca-app-pub-3940256099942544/6300978111" />

ActivityにAdView(広告ユニット)を設置する。 ca-app-pub-3940256099942544/6300978111はテスト用の広告ユニットIDです。

複数のActivityでアプリが動作している場合は、Activity毎に広告ユニットIDを取得すること。

ActivityMain.kt

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ・・・

        //AdMob初期化処理
        MobileAds.initialize(this) {}    //起動時に最初のActivityで1回だけ実行
        val mAdView = findViewById(R.id.adView) as AdView
        val adRequest = AdRequest.Builder().build()
        mAdView.loadAd(adRequest)

広告ユニットを起動する。 MobileAds.initialize(this) {}については、起動時に最初に表示されるActivityでのみ実行する。

詳細

developers.google.com

developers.google.com