20201018のAndroidに関する記事は9件です。

AndroidStudio4.1でViewModelを使う

なんか前のやり方でできなくなってたので

環境

  • Android Studio 4.1
  • Kotlin
  • Mac

結論

ライブラリ

build.gradle(app)
plugins {
    id 'kotlin-android-extensions'
}

dependencies {
    // ViewModel
    implementation "androidx.activity:activity-ktx:1.1.0" // Activityから使う時
    implementation "androidx.fragment:fragment-ktx:1.2.5" // Fragmentから使う時
}

バージョンは
Activity - AndroidDeveloper
Fragment - AndroidDeveloper
から安定版を使用

extensionssyntheticでfindViewByIdの代わりにする時のやつ
Kotlin Android Extensionsを試してみた - Qiita
今まではデフォルトで入ってたのに4.1で消えた

Activity

MainActivity.kt
val viewModel: SampleViewModel by viewModels() 

Activityから呼ぶ時はby viewModels()

Fragment

SampleFragment.kt
val viewModel: SampleViewModel by activityViewModels() // Activityと同じViewModel使う時はこっち
val viewModel: SampleViewModel by viewModels() // こっちだと新しいインスタンスを作成っぽい

Fragmentから呼ぶ時は二通り、好きな方を使う
Activityと同じインスタンスを使いたければby activityViewModels()

ViewModel

SampleViewModel.kt
class SampleViewModel: ViewModel() {
    // 中身
}

ViewModelは変わらず、ViewModel()を継承するだけ

コード全文

とにかく簡単にViewModelまとめリスペクトで、ActivityとViewModelそれぞれの変数からtextViewに表示してボタンを押したら値を+1するサンプル
ViewModelって何?の説明もリスペクト元の記事に短くまとまってるのでどうぞ
Github

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textActivity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toTopOf="@+id/textViewModel"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textViewModel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewModel" />

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels // by viewModels()用
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val viewModel: SampleViewModel by viewModels() // ViewModelのインスタンスを作成
        var activityVariable: Int = 0 // Activityに保存する変数

        // 画面作成時に表示
        textActivity.text =  activityVariable.toString() // Activity
        textViewModel.text = viewModel.viewModelVariable.toString() // ViewModel

        // ボタンを押したら+1する
        button.setOnClickListener {
            // 変数の値を+1
            activityVariable++
            viewModel.viewModelVariable++

            // 表示
            textActivity.text =  activityVariable.toString()
            textViewModel.text = viewModel.viewModelVariable.toString()
        }
    }
}
SampleViewModel.kt
import androidx.lifecycle.ViewModel

class SampleViewModel: ViewModel() {
    var viewModelVariable: Int = 0 // ViewModelに保存する変数
}

参考にした記事

Android Jetpack(AndroidXライブラリ)の最近の更新
[Android]lifecycleライブラリ2.2.0からViewModelProviders.ofが非推奨になっちゃった件

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android/Kotlin]AndroidStudio4.1でViewModelを使う

なんか前のやり方でできなくなってたので

環境

  • Android Studio 4.1
  • Kotlin
  • Mac

結論

ライブラリ

build.gradle(app)
plugins {
    id 'kotlin-android-extensions'
}

dependencies {
    // ViewModel
    implementation "androidx.activity:activity-ktx:1.1.0" // Activityから使う時
    implementation "androidx.fragment:fragment-ktx:1.2.5" // Fragmentから使う時
}

バージョンは
Activity - AndroidDeveloper
Fragment - AndroidDeveloper
から安定版を使用
いやなんでこれデフォルトで入ってないんだよ

extensionssyntheticでfindViewByIdの代わりにする時のやつ
Kotlin Android Extensionsを試してみた - Qiita
今まではデフォルトで入ってたのに4.1で消えた

Activity

MainActivity.kt
val viewModel: SampleViewModel by viewModels() 

Activityから呼ぶ時はby viewModels()

Fragment

SampleFragment.kt
val viewModel: SampleViewModel by activityViewModels() // Activityと同じViewModel使う時はこっち
val viewModel: SampleViewModel by viewModels() // こっちだと新しいインスタンスを作成っぽい

Fragmentから呼ぶ時は二通り、好きな方を使う
Activityと同じインスタンスを使いたければby activityViewModels()

ViewModel

SampleViewModel.kt
class SampleViewModel: ViewModel() {
    // 中身
}

ViewModelは変わらず、ViewModel()を継承するだけ

コード全文

とにかく簡単にViewModelまとめリスペクトで、ActivityとViewModelそれぞれの変数からtextViewに表示してボタンを押したら値を+1するサンプル
ViewModelって何?の説明もリスペクト元の記事に短くまとまってるのでどうぞ
Github

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textActivity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toTopOf="@+id/textViewModel"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textViewModel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewModel" />

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels // by viewModels()用
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val viewModel: SampleViewModel by viewModels() // ViewModelのインスタンスを作成
        var activityVariable: Int = 0 // Activityに保存する変数

        // 画面作成時に表示
        textActivity.text =  activityVariable.toString() // Activityの変数を表示
        textViewModel.text = viewModel.viewModelVariable.toString() // ViewModelの変数を表示

        // ボタンを押したら+1する
        button.setOnClickListener {
            // 変数の値を+1
            activityVariable++
            viewModel.viewModelVariable++

            // 表示
            textActivity.text =  activityVariable.toString()
            textViewModel.text = viewModel.viewModelVariable.toString()
        }
    }
}
SampleViewModel.kt
import androidx.lifecycle.ViewModel

class SampleViewModel: ViewModel() {
    var viewModelVariable: Int = 0 // ViewModelに保存する変数
}

参考にした記事

Android Jetpack(AndroidXライブラリ)の最近の更新
[Android]lifecycleライブラリ2.2.0からViewModelProviders.ofが非推奨になっちゃった件

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Studioでエディタの縦線を消す

エディタに表示される縦線(下記画像のオレンジ矢印参照)を非表示にする方法です。
毎回忘れてググっているので備忘録に
スクリーンショット 2020-10-18 16.04.53.png

  1. Preferences
  2. Editor
  3. General
  4. Appearance
  5. Show hard wrap and visual guidesのチェックをはずす スクリーンショット 2020-10-18 16.14.11.png
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#9 Kotlin Koans Introduction/Extension functions 解説

1 はじめに

Kotlin公式リファレンスのKotlin Koans/Extension functionsの解説記事です。

Kotlin Koansを通してKotlinを学習される人の参考になれば幸いです。

ただし、リファレンスを自力で読む力を養いたい方は、
すぐにこの記事に目を通さないで下さい!

一度各自で挑戦してから、お目通し頂ければと思います:fist:

2 拡張関数

拡張関数とは、継承を用いずに特定のクラスに対して追加した関数のことです。

拡張関数はオリジナルのクラスに定義された関数と同様に参照することができます。

以下が拡張関数の定義と参照です。

拡張関数の定義
fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] 
    this[index1] = this[index2]
    this[index2] = tmp
}

拡張関数の参照
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) 

上記のMutableList<Int>レシーバー型と呼び、このクラスに関数を追加します(今回の場合はswap()関数を追加します。)。

拡張関数内のthisレシーバーオブジェクトを意味しています。

なので、thisはswap()関数を呼び出している変数list(MutableListオブジェクトが代入されている。)自身ということになります。

3 Introduction/Extension functionsの解説

Kotlin Koans Introduction/Extension functionsの解説です。
随時本サイトの内容を引用させていただきます。

右側の本文を見てみましょう。

Read about extension functions. Then implement extension functions Int.r() and Pair.r() and make them convert Int and Pair to RationalNumber.

extension functionsについて読みなさい。拡張関数Int.r()とPair.r()を定義し、IntとPairをRationalNumberに変換するようにしなさい。

左側の実装するコードです。

fun Int.r(): RationalNumber = TODO()
fun Pair<Int, Int>.r(): RationalNumber = TODO()

data class RationalNumber(val numerator: Int, val denominator: Int)

つまり、Int型のオブジェクトとPair型のオブジェクトがそれぞれ拡張関数r()を呼び出したときに、RationalNumber型に変換すれば良いです。

Int型のオブジェクトが拡張関数r()を呼び出すことを考えてみましょう。

Int型のオブジェクトは任意の整数を意味するので、これをRationalNumber型に変換するにはTODO()の部分を

RationalNumber(this,1)

と定義すればよいです。

(※)2個目の引数が1なのは、RationalNumberクラスが有理数を生成するクラスを意図しており、引数名もdenominator(分母)とあるからだと推測しています。

Pair型のオブジェクトが拡張関数r()を呼び出すことを考えてみましょう。

Pairクラスはfirstとsecondというプロパティを持っています。
それぞれの型はPairの右横の<,>内に記されています。今回の場合、firstもsecondもInt型ということになります。

なので、

RationalNumber(first,second)

と定義すればよいです。

4 最後に

次回はKotlin Koans Introducion/Object expressionsの解説をします:muscle:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#8 Kotlin Koans Introduction/Smart casts 解説

1 はじめに

Kotlin公式リファレンスのKotlin Koans/Smart castsの解説記事です。

Kotlin Koansを通してKotlinを学習される人の参考になれば幸いです。

ただし、リファレンスを自力で読む力を養いたい方は、
すぐにこの記事に目を通さないで下さい!

一度各自で挑戦してから、お目通し頂ければと思います:fist:

2-1 when構文

when構文は、if構文の仲間のようなもので、条件ごとに結果が分岐します。
以下がwhen構文の例です。

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { 
        print("x is neither 1 nor 2")
    }
}

変数xが1のときx == 1 が表示され、2のときはx == 2が表示され、それ以外の場合x is neither 1 nor 2が表示されます。

2-2 Smart cast

cast(キャスト)とは変数(インスタンス)の型を変換させる仕組みのことを言います。

Javaではキャストを実行するには、変数名の前に()でクラス名を囲む必要があります。

一方で、Kotlinではコードの文脈を理解し自動的にキャストしてくれます。

これをスマートキャストと言います。

3 Introduction/Smart castsの解説

Kotlin Koans Introduction/Smart castsの解説です。
随時本サイトの内容を引用させていただきます。

右側の本文を見てみましょう。

Rewrite the following Java code using smart casts and when expression:

public int eval(Expr expr) {
    if (expr instanceof Num) {
        return ((Num) expr).getValue();
    }
    if (expr instanceof Sum) {
        Sum sum = (Sum) expr;
        return eval(sum.getLeft()) + eval(sum.getRight());
    }
    throw new IllegalArgumentException("Unknown expression");
}

eval()関数について考えてみましょう。

Javaのコード内のinstanceof右側の型と左側の型が同じならtrueをとることを表現します。

eval()関数が引数exprを受ってNum型かSum型かを判断し、型に応じて戻り値が変化します。

exprがNum型のときは、(Num) exprとしてNum型に変換してgetValue()関数を呼び出します(これがキャストを用いた型の変換です。)。

exprがSum型のときは、Sum sum = (Sum) exprとしてSum型に変換して変数に代入し、getleft()関数とgetRight()関数を呼び出します。

このJavaのコードを左側のKotlinのコードに書き換えましょう。

fun eval(expr: Expr): Int =
        when (expr) {
            is Num -> TODO()
            is Sum -> TODO()
            else -> throw IllegalArgumentException("Unknown expression")
        }

interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

Numクラスではコンストラクタ内でプロパティvalueが、Sumクラス内ではleftとrightが定義されています。

また、条件に応じた結果の分岐をwhen式を用いて表現しています。

exprがNum型である場合、JavaのコードではgetValue()関数を用いて値を取得していましたが、

kotlinのコードではプロパティの値をそのまま利用すればよいので、1つ目のTODO()は

is Num -> expr.value

とすればよいです。

このとき、exprは条件式でNum型であることが明らかなのでキャストが省略されています(これがスマートキャストによる型の変換です。)。

exprがSum型である場合、変数leftとrightをeval()関数に引数として渡せばよいので、

is sum -> eval(expr.left) + eval(expr.right)

とすればよいです。

4 最後に

次回はKotlin Koans Introducion/Extension functionsの解説をします:muscle:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FlutterアプリをGitHub Actionsを使ってwebアプリとandroid apkを同時にbuildし公開するまで

はじめに

私は普段休みの日の朝を中心に、普段家庭内の生活に使ったり、子供と遊ぶための便利なアプリなどを作ってます。
個人開発なので、あまり手間をかけずにサクッと色んなものを作りたいと思っていたときに
Flutterに出会いました。
Flutterはほぼローコード開発ツールやノーコードツールと同じくらいと言っていいくらい
あまり手間がかからずそれなりのアプリをサクッと開発できます。
はじめは、手元でbuildし、いちいちサーバにdeployするといった作業をしていましたが、貴重な土日の朝の時間
少しでも手間を減らしたいと思っていました。
手元ではコードを書いてテストを走らせ、かんたんにエミュレータで確認すると言った作業だけに集中し、
buildやdeployはGitHub Actionsに任せようと思い色々調査をした結果をメモしています。

flutter web を使ってwebアプリを gh-pagesに公開する

前提

この記事では前提として下記を想定しています。
- Flutterでアプリ開発をしている
- GitHub上にレポジトリが配置されている
- レポジトリ上にgh-pagesというbranchが作成されている

gh-pagesの設定

まずはweb版をdeployするための下地としてgh-pagesを設定しましょう
https://github.com/[あなたのユーザID]/[対象のレポジトリ]/settings
に行き gh-pagesの設定を下記のようにします
image.png

GitHub Actionsの設定

/path/to/[flutterアプリのルート]/.github/workflows/flutter.yaml
を作成しましょう

name: Flutter CI
'on':
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout ?️
        uses: actions/checkout@v2
      - name: Flutter setting
        uses: subosito/flutter-action@v1
        with:
          channel: beta
      - name: "Install and Build  ?"
        run: |
          flutter config --enable-web
          flutter pub get
          flutter test
          flutter build web
      - name: "Deploy ?"
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
          BRANCH: gh-pages
          FOLDER: build/web

これをmainにpushしましょう

web appがdeployされていることを確認しましょう

pushすると自動的にGitHub Actions Flutter CIが走ると思います
こちらが終わったら(自分のレポジトリの場合は3分位かかります^^;)
https://[あなたのgithubカウント].github.io/[レポジトリ名]/
に行ってあなたのFlutterアプリが動くかどうか確認してください

apkをGitHub Actionsを使ってsignする

なぜsignが必要か

自分の場合これを調査したきっかけは、firebase_authを使った場合、
ではkeystoreのSHA-1 fingerprintをfirebase consoleに設定しないと
google_sign_inがAndroid app版では動かないとう症状に悩まされたことと、
基本的にはbuildはGitHub Actionsを使ってやりたいなと思っていたたため、これを同時に出来る方法が無いか調査していました。
またセキュリティ上の理由から、keystoreファイルや生のパスワードをgithub repositryにpushすることは流石に嫌ですよね。
もしこれをやってしまうと悪意を持った人がリポジトリにアクセスし、
Google Play にアプリをあなたになりすまして公開ができるようになってしまいます。
以下の章ではSigning Flutter Android apps for release in GitHub Actionsを参考にさせていただき
GitHub Actionsを使ってflutter build apkを使って安全にAndroidをリリース署名を行う方法を解説します。

keystoreを作成する

Signing the app にある方法を使って
keystoreを作成してください
例えばWindowsであれば

$ keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias [ALIAS]

このようにして作成します。
また作成時に利用したKEY_STORE_PASSWORD, KEY_PASSWORD, ALIASは忘れないで覚えるかメモしてください

SIGNING_KEYを取得する

opensslコマンドを使えば SIGNING_KEYを取得できます

$ openssl.exe base64 -A -in c:\Users\USER_NAME\key.jks
/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.....xxxxxxxxxxxxxx=

出力されたながーい文字列をコピペしてメモしておきましょう

(option) SHA-1 fingerprintをfirebase consoleのAndroidアプリに設定しましょう

自分がもともとハマっていたのはここで、先程つくったkeystoreを下記のコマンドを使ってSHA-1 fingerprintを得て
それをメモしておきましょう

$ keytool.exe  -list -v --alias [ALIAS] -keystore c:\Users\USER_NAME\key.jks
Enter keystore password:  [KEY_STORE_PASSWORD]
Alias name: [ALIAS]
Creation date: YYYY/MM/DD
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: C=US, O=Android, CN=Android Debug
Issuer: C=US, O=Android, CN=Android Debug
Serial number: 1
Valid from: Fri Sep 25 20:27:37 JST 2020 until: Sun Sep 18 20:27:37 JST 2050
Certificate fingerprints:
         MD5:  XX:XX:XX..
         SHA1: XX:XX:XX..
         SHA256: XX:XX:XX..
Signature algorithm name: SHA1withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 1

上記に表示されるSHA1 XX:XX:XXとSHA256XX:XX:XXをコピーして
Firebase consoleに行き設定しましょう
image.png

手元でbuildする準備をする

key.propertiesを作成

/path/to/[flutterアプリのルート]/android/key.properties
を作成します

storePassword=[先程メモったKEY_STORE_PASSWORD]
keyPassword=[先程メモったKEY_PASSWORD]
keyAlias=[先程メモったALIAS]
storeFile=[c:\Users\USER_NAME\key.jks など]

key.propertiesを.gitignoreに忘れずに入れておきましょう

key.propertiesを公開してしまったら元も子もないので
ちゃんと.gitignoreに入れておきましょう

$ echo android/key.properties >> .gitignore

build.gradleの編集

/path/to/[flutterアプリのルート]/android/app/build.gradle
を編集します
編集する箇所は2つ
まずは key.propertiesがあったら
先程設定した key.propertiesからKEY_STORE_PASSWORD, KEY_PASSWORD, ALIASKEY_PATHを読み込むように編集します
GitHub Actions上では環境変数から読み込むのでelseの中身はGitHub Actions用です

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} else {
    keystoreProperties.setProperty('storePassword', System.getenv('KEY_STORE_PASSWORD'));
    keystoreProperties.setProperty('keyPassword', System.getenv('KEY_PASSWORD'));
    keystoreProperties.setProperty('keyAlias', System.getenv('ALIAS'));
    keystoreProperties.setProperty('storeFile', System.getenv('KEY_PATH'));
}

次にsignできるようにbuildTypesの上にsiningConfigsを追加し、
更にbuildTypesもsigningConfigs.releaseに変えましょう

signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
    /*
    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }*/

 手元でbuildしてみましょう

$ flutter build apk
You are building a fat APK that includes binaries for android-arm, android-arm64, android-x64.
If you are deploying the app to the Play Store, it's recommended to use app bundles or split the APK to reduce the APK size.
    To generate an app bundle, run:
        flutter build appbundle --target-platform android-arm,android-arm64,android-x64
        Learn more: https://developer.android.com/guide/app-bundle
    To split the APKs per ABI, run:
        flutter build apk --target-platform android-arm,android-arm64,android-x64 --split-per-abi
        Learn more: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split
Running Gradle task 'assembleRelease'...                           19.0s
√ Built build\app\outputs\flutter-apk\app-release.apk (7.6MB).

このようにbuildできたら成功です

GitHubのレポジトリのsecretsにこれらを設定していきます

https://github.com/[あなたのユーザID]/[対象のレポジトリ]/settings/secrets
に行って
右上のNew secretを押下してメモしたKEY_STORE_PASSWORD, KEY_PASSWORD, ALIASSIGNING_KEY
を設定しましょう

image.png

.github/workflows/flutter.yamlを編集

さあ 後少しで終わりです
/path/to/[flutterアプリのルート]/.github/workflows/flutter.yaml
を下記のように書き換えましょう

name: Flutter CI
'on':
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout ?️
        uses: actions/checkout@v2
      - name: Flutter setting
        uses: subosito/flutter-action@v1
        with:
          channel: beta
      - name: "Install and Build  ?"
        run: |
          echo $SIGNING_KEY | base64 -d > android/app/key.jks
          flutter config --enable-web
          flutter pub get
          flutter test
          flutter build apk
          flutter build web
          mkdir build/web/apks
          cp build/app/outputs/flutter-apk/*.apk build/web/apks/
        env:
          SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
          KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
          ALIAS: ${{ secrets.ALIAS }}
          KEY_PATH: key.jks
      - name: "Deploy apk ?"
        uses: actions/upload-artifact@v1
        with:
          name: release-apk
          path: build/app/outputs/flutter-apk/app-release.apk
      - name: "Deploy to gh-pages ?"
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
          BRANCH: gh-pages
          FOLDER: build/web

これをまたmainにpushしましょう
成功すると
https://[あなたのgithubカウント].github.io/[レポジトリ名]/apks/app-release.apk にアプリが配置されます

デモアプリはこちら

Demo

Demo App

おわりに

ちょっと長かったですが、なんとかこれで
web appとAndroid apk(野良アプリ)を gh-pages上に同時公開することができました。
すごく極端な話、Android Studioがなくても GitHub上でかんたんなbug fixとか編集してしまえば
自動的にGitHub Actionsが走り、ビルドされ、Deployまでできます

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chromebookで開発するメモ

Chromebookを開発マシンとして使おうと数日設定したときのメモ。

結論 (ChromeOS 86の時点)

結論としてはLinux開発マシンとして十分使える。
特にWebアプリ開発や外部サーバーにSSHでつなげて作業する場合は便利。

しかし、課題はLinux側の日本語入力。Mozcを入れてLinux側でも日本語の入力が可能だが非常に不安定。日本語入力が必要ならChromeOS側のAndroidアプリやLinux側でエディタサーバーを起動してChrome側へアクセスしてやると良い。

どのLinuxを使うか

たくさん選択肢がある。多いことは良いことだがどれにしてよいか悩む。

  • ChromeOS標準のLinuxコンテナのcrostini --- Debian
  • AndroidアプリのTermux --- Androidとの親和性高し
  • AndroidアプリのUserLand --- 好きなOSを選べる
  • ChromeOSを開発者モードにして使うcrouton --- 開発者モードが悪

手軽に始められてDebianが使えるのがcrostini。ChromeOSの設定で、Linux(ベータ)をオンにするだけ。ChromeOS/Androidとの親和性が高いのはTermux。Termuxは別途Termux-APIをインストールすると、クリップボードの操作やいろいろコマンドラインからAndroidの機能が操作できる。ChromeOSのクリップボードを自由に読み書きできるのでちょっとしたツールを実行するのに便利。

Termuxもcrostiniも別個のアプリとしてインストールされるので、ディスクスペースに余裕があるなら、両方入れて良いところどりすると良いかも。個人的には、crostiniとTermuxの二刀流を選択。

ただし、ChromeOSでは、Termuxアプリのコマンドライン上では日本語が一切入力できないというデメリットがある。(sshサーバーを実行してSecure Shellと組み合わせることで回避可能。)

crostiniのメモ

Debian、基本的に便利。ただし、GUIアプリの日本語サポートが微妙。PythonのIDLEなど、Tkベースのコンポーネントでは問題なくMozcで日本語入力できた。しかし、geditなどインストールしてみたが、全く日本語入力ができなかった。原因は不明。

crostiniの端末画面では、マウスで文字を選択するとクリップボードにコピーされる。[ctrl]+[shift]+[v]で文字を端末に貼り付けできる。

Termuxの設定メモ

Termuxの設定については以下に詳しく手順を書いた。

Termuxの端末画面では、マウスで文字を選択するとクリップボードにコピーされる。[ctrl]+[alt]+[v]で文字を端末に貼り付けできる。

ChromeOSとLinux側の連携

Termuxを使えば、クリップボード(termux-clipboard-set, termux-clipboard-get)で読み書きできる。

ネットワークは安全性を考慮され隔離されているので、linux側でサーバーを起動しても、ChromeOSのブラウザ(chrome)側からアクセスはできないようになっている。

# crostiniの場合
sudo ifconfig | grep inet

# Termuxの場合
ifconfig | grep inet

筆者のIdeaPad Duetでは、100.115.92.xxxが表示された。Chromeブラウザでこのアドレスにアクセスすると正しくアクセスできた。

例えば、php7のサーバー機能を使う場合、以下のようにしてポートを指定してサーバーを起動。

php -S 0.0.0.0:8888

crostiniではChromeOSのLinux設定でポート転送を指定する必要がある。Termuxでは指定不要。

ブラウザで、http://100.115.92.xxx にアクセスすると、正しくPHPのプログラムを実行できた。拙作のKonaWiki3 で確認した。

クラウドに保存するときは、Googleドキュメントでも良いが、WIKIを動かせばローカルにいろいろメモできて便利。

Screenshot 2020-10-18 at 11.02.48.png

VSCodeを快適に使う方法

crostiniではVisual Studio Codeをインストールして使うことも可能。しかし、まだ不安定で使いづらいとのこと。そこで、サーバー版のVSCodeをインストールして、Chrome側からアクセスすると快適に使える。

残念ながらTermuxではうまくいかなかったが、crostiniで下記コマンドでサーバー版のVSCodeがインストール可能。

curl -fsSL https://code-server.dev/install.sh | sh

そして、ChromeOSのLinuxの設定でポート転送を有効にする。

Screenshot 2020-10-18 at 10.44.12.png

そして、以下のようにコードサーバーを起動する。

code-server --bind-addr 0.0.0.0:8080

そして、上記で紹介したようにブラウザ側でLinux側のIPアドレス:ポート番号にアクセス。

Screenshot 2020-10-18 at 10.49.02.png

VSCode、ブラウザの中で動いてとても便利。

Permission Deniedの罠

Termuxではtermux-setup-storageを実行すると、ChromeOS側のダウンロード(download)やギャラリー(dcim)にアクセスできるようになる。

しかし、ChromeOSではシステム領域とユーザーが使うストレージ領域が明確に分離されており、ストレージ領域ではスクリプトの実行も拒否される設計となっている。

Go言語など、ChromeOS側でも共通のファイルを編集しようとdownload内にGOPATHを設定していたのだが、download内で作成したバイナリはTermux内にコピーしても実行できなかった。ストレージ領域ではシェルスクリプトも実行できない。

ただし、ストレージ領域でスクリプトファイルを作成し、システム領域にあるコマンドを実行することはできる。例えば、download内でa.pyというファイルを作成し、python a.pyとすればa.pyを実行することはできる。ただし、a.pyのいち行目に#!のシェバンを書いて実行権限を与えても実行はできない。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Kotlin】WebViewからFirebase 向けGoogle アナリティクスにイベントを送りDebugViewで検証する

AndroidアプリのWebView内から発生させたイベントをネイティブ側のFirebase向けGoogleアナリティクス(あるいはApp+Webプロパティ、GA4)にて計測するための方法。

Googleの公式ヘルプガイドにandroid向けにはJavaのサンプルコードが記載されていたもののKoltlin版が無かったので作成しました。

1. Android プロジェクトに Firebase を追加

以下リンクの「オプション1」の手順通りにやればらくらく。
https://firebase.google.com/docs/android/setup
最後の手順の「アプリを実行してインストールを確認」が何度リトライしても上手く行きませんが大抵スキップして翌日チェックしたら成功してます。
image.png

2. JavaScript ハンドラを実装

以下Googleヘルプガイド記載のJavaScriptコードをWebViewで開かれる可能性があるすべてのWebページに実装することで、 logEvent()setUserProperty()をJavaScriptから使えるようになります。
また、アプリ側でネイティブインターフェースが実装されていない場合はconsole.log("No native APIs found.");が実行されるため、User Agentによるアプリ判定が無い場合でもエラーにはなりません。

JavaScript コード

Googleヘルプガイドのスクリプトをそのまま利用
https://firebase.google.com/docs/analytics/webview?platform=android#implement-javascript-handler

logEvent() 実装例

FirebaseアナリティクスのJavaScriptハンドラを実装、更にアプリ側に後述のネイティブインターフェースを実装の上、
あるボタンのタップ数をFirebaseで計測したい場合、以下のようなコードで実行可能。

<button type="button" onclick="logEvent('tapButton',{'param_foo':'key_bar','param_baz':'key_qux'})">logEvent Test Button</button>

image.png

3. ネイティブ インターフェースを実装する

ヘルプガイドにKotlin版が無かったため、Javaのコードを変換して作成しています。

AnalyticsWebInterfaceクラスを実装

image.png

Kotlin コード

/** Instantiate the interface and set the context  */
class AnalyticsWebInterface (private val mContext: Context) {
    companion object{
        const val TAG = "AnalyticsWebInterface"
    }
    private val firebaseAnalytics: FirebaseAnalytics = FirebaseAnalytics.getInstance(mContext)
    @JavascriptInterface
    open fun logEvent(name: String, jsonParams: String) {
        LOGD("logEvent:$name")
        firebaseAnalytics.logEvent(name, bundleFromJson(jsonParams))
    }

    @JavascriptInterface
    fun setUserProperty(name: String, value: String?) {
        LOGD("setUserProperty:$name")
        firebaseAnalytics.setUserProperty(name, value)
    }

    private fun LOGD(message: String) {
        // Only log on debug builds, for privacy
        if (BuildConfig.DEBUG) {
            Log.d(TAG, message)
        }
    }

    private fun bundleFromJson(json: String): Bundle? {
        if (TextUtils.isEmpty(json)) {
            return Bundle()
        }
        val result = Bundle()
        try {
            val jsonObject = JSONObject(json)
            val keys = jsonObject.keys()
            while (keys.hasNext()) {
                val key = keys.next()
                val value = jsonObject[key]
                when(value){
                    is String -> result.putString(key, value)
                    is Int -> result.putInt(key, value)
                    is Double -> result.putDouble(key, value)
                    else -> Log.w(TAG, "Value for key $key not one of [String, Integer, Double]")
                }
            }
        } catch (e: JSONException) {
            Log.w(TAG, "Failed to parse JSON, returning empty Bundle.", e)
            return Bundle()
        }
        return result
    }
}

変換元 : https://developers.google.com/analytics/devguides/collection/firebase/android/webview#implement_native_interface

必要なimport文は赤字部分にマウスオーバーして Alt+Enter で適宜追加
image.png

ネイティブインターフェースをWebViewに紐づけ

先ほど作成したネイティブインターフェースをWebViewリソースに紐づけ
image.png

Kotlin コード

コード内の「 webview 」の文字を目的のリソースIDに変更の上でご利用ください。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    webview.addJavascriptInterface(AnalyticsWebInterface(this), AnalyticsWebInterface.TAG);
} else {
    Log.w(AnalyticsWebInterface.TAG, "Not adding JavaScriptInterface, API Version: " + Build.VERSION.SDK_INT);
}

DebugViewによるイベント計測状況の確認

手順

  1. PC にadbコマンドを導入する → ADBコマンド導入の方法 - Qiita
  2. 検証用の Android 端末にデバッグ対象のアプリをインストール
  3. Android 端末のデバッグモードを有効化
  4. Android 端末をPCにUSBデバッグモードで接続
  5. PC でコマンドプロンプトを起動し、コマンド adb devices を実行。以下のように端末名とdeviceの文字が表示されれば次の手順へ。unauthorizeと出た場合は端末側でUSBデバッグの認証が通っていないので端末を操作して許可させる。 image.png
  6. コマンド adb shell setprop debug.firebase.analytics.app ●●●を実行(黒丸部分はデバッグ対象のパッケージ名に置換えた上で実行)
  7. Android 端末を PC から外してアプリを操作する
  8. PC のブラウザで Firebase Console を開き、目的の Firebase プロジェクト画面の左側ナビゲーション内の 分析 > DebugView を選択して開く image.png
  9. 「デバッグに使用するデバイス」に先ほど自分が設定した Android 端末があることを確認して選択
  10. Android 端末で操作した内容が反映されるか確認する(15秒程度の遅延あり)

イベントのデバッグ  |  Firebase

イベントが計測されている様子

以下のようにイベントが計測されれば実装および検証が成功。お疲れ様でした。

DebugViewで目的のイベントが表示されることを確認
image.png
表示されたイベントをクリックしてパラメータも正常に計測できていることを確認
image.png


以上。「もっとこうした方がいいよ」などありましたらアドバイス頂けますと幸いです。

参考

WebView でアナリティクスを使用する  |  Firebase

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

futterのインストールからサンプルプログラムを動かすまで

目的

  • モバイルアプリの開発を1つのコードで実装することを目的とし、そのためのツールとしてFlutterを使用することとした
  • Flutterのインストールを行い、インストール後の確認としてサンプルプログラムを作成してiOSとAndroidの各シミューレタで動かす

環境

  • OS:macOS Mojave(10.14.6)
  • iOS向けのコマンドラインツール等はインストール済み(iOSの開発をした端末を使用)
  • 動作確認はVSCodeを使用(インストール済み)

flutterのインストール

  • gitからclone

    # 開発用フォルダへ移動
    cd /Users/xxxx/develop
    # gitから取得
    git clone https://github.com/flutter/flutter.git
    
  • flutterへパスを通す

    # bash_profileへ設定追加
    export PATH="\$PATH:`pwd`/flutter/bin" >> ~/.bash_profile
    # 設定を即時反映させる
    source ~/.bash_profile
    
  • 開発者ツールをダウンロード(任意)

    flutter precache
    
  • 環境構築状況の確認

    flutter doctor
    
  • flutter doctorの結果に応じて必要なツールをインストール

    • 表示されたメッセージの内容に合わせて設定していけばOK
    • 解決しない場合は個別に調査

私の環境で足りなかった設定の追加

  • android studio のセットアップ
  • cocoapods のインストール

    • インストールコマンドを実行
      sudo gem install cocoapods
    
      ERROR:  While executing gem ... (NoMethodError)
        undefined method `invoke_with_build_args' for nil:NilClass
    
    • エラーになったので、エラーメッセージを調べてた結果rubyを入れ直し
      rbenv uninstall 2.6.1
      rbenv install 2.6.1
    
    • インストールコマンドを再実行して問題なく終了することを確認
  • flutter doctorを実行 → android studio のtool chainのエラーが消えない

    [!] Android toolchain - develop for Android devices: is partially installed; more components are
      available. (Android SDK version 30.0.2)
    
    • android studio からコマンドラインツールを追加インストール
    • 次のflutter doctorの実行でandroidのライセンス確認?のエラーが出ていたので、メッセージに合わせてコマンドを実行
      flutter doctor --android-licenses
    
  • デバイスが見つからない

    [!] Connected device
        ! No devices available    
    
    • android studio から仮想端末を起動
      • android studio を起動
      • 「Configure → AVD manager」
      • 設定済みの端末があったので、そのまま再生ボタンのアイコンをクリックして実行
  • flutter doctor ですべての問題が解決していることを確認

    Doctor summary (to see all details, run flutter doctor -v):
    [✓] Flutter (Channel master, 1.23.0-19.0.pre.83, on Mac OS X 10.14.6 18G6020 darwin-x64, locale
        ja-JP)
    [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
    [✓] Xcode - develop for iOS and macOS (Xcode 11.2.1)
    [✓] Android Studio (version 4.1)
    [✓] VS Code (version 1.48.0)
    [✓] Connected device (1 available)
    

サンプルプロジェクトの作成、シミュレータでの動作確認

  • 必要に応じてVSCodeの拡張機能を入れる、これらは入れておきましょう
    • Flutter
    • Dart
  • プロジェクトを作成

    flutter create {project name}
    
  • VSCodeでlib/main.dartを開く

  • iOSでの動作確認

    • F5もしくはrun→start debuggingで実行
    • シミュレータの選択肢が表示されるのでOSを選択
    • iOSシミュレータ起動後、しばらく(数分程度)待つと起動します
    • iOSとして動作することを確認
  • androidでの動作確認

    • コマンドパレットから「Flutter: Select Device」を選択、androidのエミュレータを選択
    • 選択したエミュレータ起動後、androidとして動作することを確認

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む