はじめに
この記事は Elixirアドベントカレンダーのシリーズ4の6日目の記事です
ElixirDekstopのiOSアプリを以下を参考にXCodeでプロジェクトを作るところから初めて
実際に起動するまで行います
フォルダ構成
別のネイティブアプリを作るライブラリにLiveViewNativeというものがあり、そのサンプルアプリのフォルダ構成が、 app_name/native/[ios or android pj]
となっていたのでそちらを踏襲します
mkdir native
プロジェクトを新規に作成
Save Locationは bookshelf/native/androidを指定します
gradleはElixirDesktopに合わせてgrooby DSLにする
defaultでFinish
okをクリック
サンプル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/bookshelf/
にコピー
packageを記述を差し替えます
- package io.elixirdesktop.example
+ package com.example.bookshelf
import android.annotation.SuppressLint
...
MainActivity.kt
のpackage
以外を差し替えます
コピー後
databinding.ActivityMainBinding
の前半分を差し替え
- package io.elixirdesktop.example
+ package com.example.bookshelf
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.bookshelf.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ファイル変更
providerのauthoritiesの変更漏れに気をつけましょう
<?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.bookshelf.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をコピーして編集します
#!/bin/bash
set -e
BASE=`pwd`
APP_FILE="$BASE/src/main/assets/app.zip"
export MIX_ENV=prod
export MIX_TARGET=android
- if [ ! -d "elixir-app" ]; then
- git clone https://github.com/elixir-desktop/desktop-example-app.git elixir-app
- fi
- # using the right runtime versions
- if [ ! -f "elixir-app/.tool-versions" ]; then
- cp .tool-versions elixir-app/
- fi
- cd elixir-app
+ cd ../../../
if [ ! -d "deps/desktop" ]; then
mix local.hex --force
mix local.rebar
mix deps.get
fi
# npmライブラリを使うまではコメントアウト
# 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/todo_app" && \
+ cd "_build/${MIX_TARGET}_${MIX_ENV}/rel/bookeshelf" && \
zip -9r "$APP_FILE" lib/ releases/ --exclude "*.so"
proguardに修正
以下を追加
-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の改修
project("bookshelf")
# 以下差し替え
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の改修
一旦全部コピーして環境依存の箇所のみ修正します
extern "C" JNIEXPORT jstring JNICALL
- Java_io_elixirdesktop_example_Bridge_startErlang(
+ Java_com_example_bookshelf_Bridge_startErlang(
build.gradleの修正
色々足していきます
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.example.trareco'
- compileSdk 33
+ compileSdk 34
+ 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
+ targetSdk 34
versionCode 1
versionName "1.0"
+ ndk {
+ abiFilters "armeabi-v7a", "arm64-v8a" , "x86_64"
+ }
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ externalNativeBuild {
+ cmake {
+ cppFlags ''
+ }
+ }
}
buildTypes {
release {
- minifyEnabled false
+ 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で同期してからビルドを実行します
ファイルを追加したらAndroidの実行ボタンをクリックすると以下のように表示されます
最後に
Androidでプロジェクトを作るところから実際にElixirDesktopが動くところまでを実装しました
ファイルをコピーしたりといろいろ面倒なので、LiveViewNativeみたいに自動化されないかなーとは思っています
次はCRUD画面作成とデータベースマイグレーションについて書きます
本記事は以上になります、ありがとうございました