LoginSignup
13
2

はじめに

この記事は Elixirアドベントカレンダーのシリーズ4の6日目の記事です

ElixirDekstopのiOSアプリを以下を参考にXCodeでプロジェクトを作るところから初めて
実際に起動するまで行います

フォルダ構成

別のネイティブアプリを作るライブラリにLiveViewNativeというものがあり、そのサンプルアプリのフォルダ構成が、 app_name/native/[ios or android pj]となっていたのでそちらを踏襲します

mkdir native

プロジェクトを新規に作成

Native C++ を選択
スクリーンショット 2023-10-07 21.18.22.png

Save Locationは bookshelf/native/androidを指定します
gradleはElixirDesktopに合わせてgrooby DSLにする

スクリーンショット 2023-12-06 0.52.19.png

defaultでFinish

スクリーンショット 2023-12-06 0.52.40.png

okをクリック

スクリーンショット 2023-12-06 0.52.59.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/bookshelf/にコピー

packageを記述を差し替えます

native/android/app/src/main/java/com/example/bookshelf/Bridge.kt
- package io.elixirdesktop.example
+ package com.example.bookshelf

import android.annotation.SuppressLint
...

MainActivity.ktpackage以外を差し替えます
コピー後
databinding.ActivityMainBindingの前半分を差し替え

native/android/app/src/main/java/com/example/bookshelf/MainActivity.kt
- 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の変更漏れに気をつけましょう

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.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をコピーして編集します

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

- 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に修正

以下を追加

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("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の改修

一旦全部コピーして環境依存の箇所のみ修正します

native/android/app/src/main/cpp/native-lib.cpp:L160
extern "C" JNIEXPORT jstring JNICALL
- Java_io_elixirdesktop_example_Bridge_startErlang(
+ Java_com_example_bookshelf_Bridge_startErlang(

build.gradleの修正

色々足していきます

native/android/app/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で同期してからビルドを実行します

スクリーンショット 2023-10-07 21.42.53.png

ファイルを追加したらAndroidの実行ボタンをクリックすると以下のように表示されます

スクリーンショット 2023-12-06 2.23.01.png

最後に

Androidでプロジェクトを作るところから実際にElixirDesktopが動くところまでを実装しました
ファイルをコピーしたりといろいろ面倒なので、LiveViewNativeみたいに自動化されないかなーとは思っています

次はCRUD画面作成とデータベースマイグレーションについて書きます

本記事は以上になります、ありがとうございました

13
2
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
13
2