ElixirDesktop Android Exampleを参考にAndroidアプリを１から作ってみる

# はじめに
本記事は以下を参考にAndroidStudioでプロジェクトを作るところから初めて
実際に起動するまでの手順の備忘録です

https://github.com/elixir-desktop/android-example-app


# プロジェクトを新規に作成
Native C++ を選択
![スクリーンショット 2023-10-07 21.18.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/12b9ee06-2e0d-2207-7d91-65add479745b.png)

Save Locationは phoenix_pj/native/android に設定
Minumum SDKはお好きに自分は10.0にしました
gradleはElixirDesktopに合わせてgrooby DSLにする .ktsにしない

![スクリーンショット 2023-10-07 0.21.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/5088ad0c-aa77-348e-44b1-cf20a4d33e49.png)



# サンプルPJからファイルをコピー

app/libs/erlang.jarを
native/android/app/libs/ にコピー

app/src/main/assets を
native/android/app/src/main/ にコピー

app/src/main/res/xml/provider_paths.xml　を
native/android/app/src/main/res/xml にコピー

app/src/main/res/layout/activity_main.xml　を
native/android/app/src/main/res/layout/activity_main.xml　と差し替える

# main/java のファイルをコピー & fix

app/src/main/java/io/elixirdesktop/example/Bridge.ktを
native/android/app/src/main/java/com/example/trareco/ にコピー

packageを差し替え

```diff_kotlin:native/android/app/src/main/java/com/example/trareco/Bridge.kt
- package io.elixirdesktop.example
+ package com.example.trareco

import android.annotation.SuppressLint
...
```

package 以外を差し替え
コピー後
databinding.ActivityMainBindingの前半分を差し替え


```diff_kotlin:native/android/app/src/main/java/com/example/trareco/MainActivity.kt
- package io.elixirdesktop.example
+ package com.example.trareco

import android.os.Bundle
import android.system.Os
import android.app.Activity
import android.view.KeyEvent
import android.view.View
- import io.elixirdesktop.example.databinding.ActivityMainBinding
+ import com.example.trareco.databinding.ActivityMainBinding
import java.io.*
import java.util.*
import android.webkit.WebView

import android.webkit.WebViewClient

class MainActivity : Activity() {
    private lateinit var binding: ActivityMainBinding

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

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.browser.webViewClient = object : WebViewClient() {
            override fun onPageFinished(view: WebView, url: String) {
                if (binding.browser.visibility != View.VISIBLE) {
                    binding.browser.visibility = View.VISIBLE
                    binding.splash.visibility = View.GONE
                }
            }
        }

        if (bridge != null) {
            // This happens on re-creation of the activity e.g. after rotating the screen
            bridge!!.setWebView(binding.browser)
        } else {
            // This happens only on the first time when starting the app
            bridge = Bridge(applicationContext, binding.browser)
        }
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        if (event.action == KeyEvent.ACTION_DOWN) {
            when (keyCode) {
                KeyEvent.KEYCODE_BACK -> {
                    if (binding.browser.canGoBack()) {
                        binding.browser.goBack()
                    } else {
                        finish()
                    }
                    return true
                }
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    companion object {
        var bridge: Bridge? = null
    }
}

```


# manifestファイル変更

```diff_xml:native/android/app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
+    android:installLocation="internalOnly">

+   <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Trareco"
+        android:extractNativeLibs="true"
+        android:usesCleartextTraffic="true"
        tools:targetApi="31"
        >

+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="com.example.trareco.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/provider_paths" />
+        </provider>


        <activity
+            android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|density|fontScale|keyboard|layoutDirection|mcc|mnc|navigation|smallestScreenSize|touchscreen|uiMode"
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.Trareco">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
```

# run_mixを作成
```bash:native/android/app/run_mix
#!/bin/bash
set -e

BASE=`pwd`
APP_FILE="$BASE/src/main/assets/app.zip"
export MIX_ENV=prod
export MIX_TARGET=android


cd ../../../

if [ ! -d "deps/desktop" ]; then
  mix local.hex --force
  mix local.rebar
  mix deps.get
fi

#if [ ! -d "assets/node_modules" ]; then
#  cd assets && npm i && cd ..
#fi

if [ -f "$APP_FILE" ]; then
  rm "$APP_FILE"
fi

mix assets.deploy && \
  mix release --overwrite && \
  cd "_build/${MIX_TARGET}_${MIX_ENV}/rel/trareco" && \
  zip -9r "$APP_FILE" lib/ releases/ --exclude "*.so"

```
# proguardに修正

以下を追加

```native/android/app/proguard-rules.pro
-dontskipnonpubliclibraryclasses
-dontobfuscate
-forceprocessing
-optimizationpasses 5

-keep class * extends android.app.Activity
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
}
```

# cpp ファイルの改修
CMakeLists.txtの改修
```native/android/app/src/main/cpp/CMakeLists.txt
project("trareco")
# 以下差し替え
add_library(native-lib SHARED native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library(log-lib log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries(native-lib ${log-lib})
```
native-lib.cppの改修

一旦全部コピーして環境依存の箇所のみ修正します
```diff_c:native/android/app/src/main/cpp/native-lib.cpp:L160
extern "C" JNIEXPORT jstring JNICALL
- Java_io_elixirdesktop_example_Bridge_startErlang(
+ Java_com_example_trareco_Bridge_startErlang(
```

# build.gradleの修正
色々足していきます

```diff_groovy:native/android/app/build.gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    namespace 'com.example.trareco'
    compileSdk 33

+    task buildNum(type: Exec, description: 'Update Elixir App') {
+        commandLine './run_mix', 'package.mobile'
+    }

+    tasks.withType(JavaCompile) {
+        compileTask -> compileTask.dependsOn buildNum
+    }

    defaultConfig {
        applicationId "com.example.trareco"
        minSdk 29
        targetSdk 33
        versionCode 1
        versionName "1.0"

+        ndk {
+            abiFilters "armeabi-v7a", "arm64-v8a" , "x86_64"
+        }
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        externalNativeBuild {
+            cmake {
+                cppFlags ''
+            }
+        }
    }

    buildTypes {
        release {
+            minifyEnabled true
+            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
+    externalNativeBuild {
+        cmake {
+            path file('src/main/cpp/CMakeLists.txt')
+            version '3.22.1'
+        }
+    }
+    buildFeatures {
+        viewBinding true
+    }
+    packagingOptions {
+        jniLibs {
+            useLegacyPackaging = true
+        }
+    }
}

dependencies {
+    implementation fileTree(dir: "libs", include: '*.jar')
    
    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
```

全部終わったら右上のsync gradleで同期してからビルドを実行します

![スクリーンショット 2023-10-07 21.42.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/32b50a71-baa3-3e55-d22f-d0474c665fab.png)

# 動いた！
無事Androidも０から構築してElixirDesktopのアプリを起動できました！

![スクリーンショット 2023-10-07 21.12.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/b1215c0b-9753-76a5-afce-573f31d0c545.png)