2023年7月31日月曜日

CI/CD 各種サービスを比較し使うものを選定する

この前の続き。CI/CD環境の構築に向けて、良さそうなサービスを探す。

CI/CDと聞いて真っ先に思いつくのはJenkinsだが、オンデマンドに構築できるほど私のプライベート環境は充実していないので却下。外部サービスで検討する。

調査・知っている範囲で、以下のサービスをピックアップした。

  • Github Action
  • Circle CI
  • TravisCI
  • Bitrise

で、結論としてBitriseで進めることにした。

理由としては

  • Bitriseはモバイルアプリ(特にAndroid)のインテグレーション(単体テストなど)、デプロイ(apkやaabの生成、GooglePlayStoreへのアップロード)が初期状態でほぼ構築済み。ユーザーは必要なパラメーターを入力するだけでほぼ実現できる。

Bitrise以外のサービスについては

  • GitHubActionはリポジトリをpublicにしたいという要件を満たせない。署名キーや広告IDは秘匿にできるにせよ、それにより生成されたバイナリが表に丸見えになりうる。やり方次第かもだが、バイナリを生成するワークフローを表に見せたくない…。
  • CircleCIはfastlaneの導入が必要になる。iOSアプリ開発者だと、コマンド一つでipaを生成するために導入したりなど比較的馴染みのあるツールだが、AndroidでCI/CDするためだけにわざわざ導入したいと思わない。
  • 残るTravisCIがBitriseの対抗馬となったが、導入の容易さから結果としてBitriseに軍配があがることとなった。

ただし注意事項として、Bitriseは1アカウントにつき処理できるジョブやタスクの回数?リソースの使用量?に制限がある。無料アカウントでは、毎月「300クレジット」という単位でサービスを利用できるが、これを越えると次のクレジット支給まで使えなくなる様子。
正直どんなもん?というところだが、(構築後日談として)1回のワークフロー(ビルドをするためのセットアップからビルド完了までの一連の流れ)を処理するのに大体10クレジット程度を消費しているため、30日間ではおよそ30回しか利用できない、ということになる。
とはいえ「プライベートで回している開発フローで、複数のプロジェクトを加味する必要があるとはいえ1ヶ月に30回もリリースビルド→デプロイすることあるか…?」と考えると、これが制限となってしまうことは稀になりそうだ。

というわけで今回はCI/CD環境を構築するためのサービスの選定とその理由について投稿した。次回はBitriseを利用したAndroidアプリのデプロイ環境の構築について投稿予定だ。

2023年7月30日日曜日

正規表現 文字列の一部だけを加工したい

Logcatとかコンソールでログとか漁ってるときに、「必要なところを探して、必要な所だけくり抜くのマジだるい…」ってなる。

正規表現を使えば簡単なんだけど、「必要な所だけくり抜く」表現がわからなかったので放置してた。でもいい加減(ちゃんと書ければ秒で済む話を何時間もかけて作業してるの、マジ無能…と思い)なんとかしたかったので、調べて備忘録として残す。

知ってしまえば本当に簡単なんだわ。

検索での表現

(検索したいけど置換したくない文字)なにかしら置換したい文字(検索したいけど置換したくない文字)

置換の表現

$1置換後の文字$2

ポイントは

  • 「くり抜くところ」を括弧書き()にすると、置換するときに$*(数字)て書けば検索したときに括弧書きに該当するところがそのまま残る。ちなみに、カッコはグループ化する表現らしい。
  • そのまま残したいところが複数ある場合でも、$に付ける数字を増やしていけばOK。

<div>タグを<p>タグに変えたい。"/"も気にせず置換したい。

検索での表現

<(/*)div>
  • 検索対象のdivを指定する。
  • /があるかないかは、置換後のテキストにも影響するので、()で書く。
  • *は0文字以上の意味。

置換での表現

<$1p>

  • 置換したい文字としてpタグを指定する。
  • ()で書いた箇所が$1として置換後のテキストに反映される。
  • 今回は、/があった場合に限り置換後に/が入るようにする。

参考

2023年7月29日土曜日

Android 環境変数や定数を build.gradleに持たせる実装を解説

どうも。どっことです。今回は環境変数や定数を build.gradleで管理する実装を解説します。

build.gradleで環境変数・定数を管理する実装

Gradleが自動生成してくれるBuildConfigを使うことで、buildTypesproductFlavorごとに初期値や定数値を持たせることができます。よくあるのはAPIのドメインや、広告IDの切り分けなどですね。開発版(buildType:debug)とリリース版(buildType:release)で開発用と本番用で切り替えるなどに利用できます。今回はBuildConfigにアプリ内で参照できるパラメータを乗せる実装を解説します。

BuildConfigに定数を乗せる実装

buildTypes(debug/release)ごとに定義する場合は以下でOKです。

  • defaultConfigに初期値を設定する。
  • buildTypesごとに定数の値を上書き設定する。
android {
  // ...
  defaultConfig {
      // buildTypesで上書きがなかった場合に設定される値
      buildConfigField "String", "AD_UNIT_ID", "\"とりあえずな広告ID\""
  }
  // ...

  buildTypes {
    release {
      // 本物の広告IDを上書きして定義
      buildConfigField("String", "AD_UNIT_ID", "\"本物の広告ID\"")
      ...
    }
    // とりあえずな広告IDをdefaultConfigに書いていますが、
    // debugブロックに書いて、defaultConfigは空文字などを指定しても問題ありません。
  }
}

あとは上記の定数を使いたいところで、以下ように参照すれば値を取得することができます。

const val AD_UNIT_ID_SIDE_BAR = BuildConfig.AD_UNIT_ID

まとめ

今回は、BuildConfigに定数を持たせる実装について解説しました。同じアプリでも開発環境やクライアントに見せる版、一般公開版など種別がさまざまになることが多いと思います。このような機能を利用して、ソースコード上に煩わしい実装が増やさないように心がけましょう。

参考

2023年7月27日木曜日

Android キーイベントを発行する実装方法を解説

どうも。どっことです。今回はキーイベントを発行する実装方法を解説します。

キーイベントを発行する

例えば方向キーのキーイベントを発行する場合、以下をつかいます。

KeyEventを生成して、dispatchKeyEventで投げるだけ。これだけです。

dispatchKeyEvent(KeyEvent(ACTION_KEY_DOWN, KEYCODE_DPAD_UP))

気をつけないといけないのは、KeyEventコンストラクタに渡している第一引数には ACTION_KEY_DOWN を指定することです。

そもそもACTION_KEY_DOWNはキー開始(画面やキーが押され始めた)、ACTION_KEY_UPはタッチ終了(画面やキーから離れた)を表すイベントですが、第一引数にACTION_KEY_UPを指定しても、OS側で制御してくれません。というのもOSからしてみれば

「押されてないのに離れたとか何事!?怖いから無視しとこ…」

という形に整理されていそうだからです。そのため、どうしてもACTION_KEY_UPでやりたかったら、ACTION_KEY_DOWNによりキーが押され始めたイベントも一緒に発行する必要があります。(どれだけ頑張る必要があるかは知らない…)ACTION_KEY_UPに思い入れしなければ、非常に簡単ですね。

まとめ

今回はキーイベントを発行する実装方法を解説しました。カスタムキーボードなどに役立てていただければと思います。

参考

2023年7月26日水曜日

Android Activityのスタックをクリアする

Intent.FLAG_ACTIVITY_CLEAR_TOPIntent#setFlag(int)でセットするだけ。

fun showTop(View view) {
  let intent = Intent(getApplication(), MainActivity::class.java)
  intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  startActivity(intent);
}

参考

2023年7月24日月曜日

iOS Xcode ドキュメンテーションコメントを追加するショートカットキーを紹介

メソッドの上部で option + command + / を入力すると、自動でドキュメンテーションコメントが追加されます。

あとで見返すときのために、「何をするメソッドか」「引数にはどんな値が必要か、どういう値が想定されているか」「戻り値は何か」などを記入しておきましょう。

参考

2023年7月23日日曜日

Android 共有ボタンのデータを受け取る、送信する

どうも。どっことです。今回は共有ボタンの実装について解説します。

X(旧:Twitter)でのポストやブログの記事など、Webにはいたる所に共有ボタンがあります。今回は共有ボタンによりデータが送信された時にアプリ側でそれを受け取るための実装と、アプリで共有ボタンを用意する実装をそれぞれ解説していきます。

受け取る側の実装

よくあるBroadcastReceiverの実装パターンです。2つやることがあります。

1つは、AndroidManifest.xmlに共有ボタンによるデータ送信のintentを受け取る宣言を明記します。

<activity android:name=".MainActivity" >
  <intent-filter> 
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
  </intent-filter>
</activity>

そしてもう一つは、実際に受け取った時の処理です。受け取るActivityBroadcastReceiverに対してインテントフィルターを設定します。Activityでは、メンバ変数として持っているintentを、BroadcastReceiverではBroadcastReceiver#onReceivedの引数として渡されるintentを参照することで、詰められているデータにアクセスすることができます。

override fun onCreate(savedInstanceState: Bundle?) {
  if (intent?.action == Intent.ACTION_SEND) {
    val text = intent.getStringExtra(Intent.EXTRA_TEXT)
    // 良しなにする
  }
}

送る側の実装

こちらもよくあるstartActivity(Intent)の実装です。

ただintentの作り方だけちょっと独特で、共有したいデータを詰めたintentIntent#createChooserに渡します。そうして返されたintentstartActivityで投げます。

val temp: Intent = Intent().apply {
  action = Intent.ACTION_SEND
  putExtra(Intent.EXTRA_TEXT, "テストのテキストです")
  type = "text/plain"
}
startActivity(Intent.createChooser(temp, null))

まとめ

今回は共有ボタンに関するAndroidの実装を解説しました。特に共有ボタンからデータを受け取る処理はアプリを利用してもらう契機として重要なので、参考にしていただけると幸いです。

参考

2023年7月22日土曜日

Swift CocoaPods導入手順をメモ書き程度に紹介

Cocoapodsのインストール

sudo gem install cocoapods

または

sudo gem install -n /usr/local/bin cocoapods

Cocoapodsのセットアップ

インストール完了後、各プロジェクトで利用するためにCocoaPodをセットアップする。

pod setup

その後プロジェクトフォルダでコンソールを開いて以下コマンドを実行する。

pod init

これにより、Podfileが自動生成される。

ライブラリのインストール

自動生成されたPodfileに必要なライブラリの記述し以下を実行。

pod install

初回は上記、2回目以降は以下を使う。

pod update

これにより、.xcworkspaceという拡張子のファイルが生成される。Xcode でプロジェクトを開くときはこのファイルをクリックして開く。

参考

2023年7月21日金曜日

Mac スクリーンショットを撮影するためのショートカットを紹介

どうも。どっことです。Macでスクリーンショットを撮影したいけど、ショートカットキーを忘れて毎回検索しているので、備忘録として紹介します。

画面全体のスクショを撮影する場合

Shift + command + 3

Windowsの printscreen

範囲を指定してスクショを撮影する場合

Shift + command + 4

Windowsの Shift + win + s

ウインドウを指定してスクショを撮影する場合

Shift + command + 4 → Space

Windowsの alt + printscreen

iOS 数値を0埋めしてテキストとして表示する

Androidでは、String.format(text, value)としてテキストを出力させるが、iOSの場合もほぼ同じでStringのコンストラクタに入れるだけ。

let number = 3
let numberString = String(format: "%03d", number) // 003

参考

2023年7月20日木曜日

iOS アラートダイアログを表示するチートシート

iOSでよくあるアラートダイアログを表示する。

let alert = UIAlertController(title: "タイトルテスト", message: "メッセージテスト", preferredStyle: .alert)
// ボタンを追加する
let action = UIAlertAction(title: "ボタン", style: .default, handler: { action in
    /** do something **/
})
// 以下で、ボタンとそのタップ処理を追加する。ボタンは複数追加できる。
alert.addAction(action)
// 表示
present(alert, animated: true)

presentがライフサイクル上でコールするのが早すぎると無視されるので、viewDidAppear()以降のタイミングで呼ぶこと。

参考

2023年7月19日水曜日

iOS Enumの全要素を配列で取得する

enumの定義に、CaseIterable を実装してあげるだけ。

enum Size: CaseIterable {
  case maximum
  case big
  case medium
  case small
  case minimum
}

ってやると、allCasesにアクセスできるようになる

Size.allCases.map { /* do something. */ }

firstIndexでインデックスもとれるようになる

Size.firstIndex(of: .big)!

参考

2023年7月18日火曜日

Android Applicationクラスの実装方法について解説

どうも。どっことです。今回はApplicationクラスの実装方法について解説します。

Applicationクラスとは

アプリが動作するとき、一番最初に動き出すクラスです。このクラスにアプリの初期設定といった各種処理を実装します。外部のライブラリを利用する場合なども、ApplicationクラスのonCreateメソッド内に処理を実装することが多いので、遅かれ早かれAndroidアプリエンジニアの誰もが通る道かと思います。今回はそんなApplicationクラスの実装方法について解説します。

Applicationクラスの実装方法

以下の手順で実装します。

  1. Applicationクラスを継承したCustomApplicationクラスを追加する
  2. AndroidManifest.xmlCustomApplicationクラスを登録する

順番に解説していきます。

Applicationクラスを継承したCustomApplicationクラスを追加する

Applicationクラスを継承したCustomApplicationを追加します。クラス名に制約は無いので、分かりやすい名前を命名してください。

class CustomApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        ... // ライブラリの初期化やテーマの設定などやりたいことをやる
    }
}

AndroidManifestにCustomApplicationクラスを登録する

前手順で追加したCustomApplicationを登録します。AndroidManifest.xmlにあるapplicationタグにname属性で、クラス名を設定してください。

<application
    ....
    android:name=".CustomApplication">
    <-- よくあるよしなに -->
</application>

あとはビルド+インストールし、アプリを起動してあげると、CustomApplication#onCreate()の処理が最初に呼ばれます。Logなどを仕込んで、確認してみてください。

まとめ

今回はApplicationクラスについて解説しました。テーマの設定や各種ライブラリの初期化など、アプリをよりカスタマイズするにあたり必要になっていくクラスなので、使えるようにしておきましょう。


2023年7月17日月曜日

Android 子Viewをはみ出して表示させたい

「表示する領域のViewGroup」に以下を設定する

android:clipChildren="false"

気を付けないといけないのは、領域外に押し込まれるViewの親ではなく、押し込まれた領域のViewGroupに設定する必要があるということ。つまり以下。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:clipChildren="false" <!-- 押し込まれる領域にclipChildren:falseを設定 -->
   android:layout_height="match_parent">
   <FrameLayout
     android:layout_width="250dp"
     android:layout_height="250dp"
     android:layout_gravity="center">
    
     <View
       android:layout_width="150dp"
       android:layout_height="50dp"
       android:layout_marginTop="-25dp" <!-- 親のFrameLayoutの領域外 -->
       android:background="@android:color/black"
       android:gravity="center" />
  </FrameLayout>
</FrameLayout>

言われてみればそうか、と思うけどちょっと気持ち悪いと思うのは私だけだろうか。

参考

2023年7月16日日曜日

Swift 引数付きStringイニシャライザ

引数付きString(format:, _ arguments)のイニシャライザの使い方

let month = "12"
let day = "31"
let text = String(format: "%@月%@日", month, day)
print(text)

2023年7月15日土曜日

CI/CD 最近考えていること リリース手順が面倒くさい

毎回毎回、

署名キー探して、
設定値を入力してビルドして
aabファイル生成して
プレイストアにアップロードして

ってフローが面倒になった。どうにかならないものか。

少なくても、署名キー探す~aabファイル生成するまでを自動化できることは知っているけど、例えばGitHubActionでやるなら生成したaabファイルや署名キーを秘匿にしないといけない都合でリポジトリも秘匿にしないといけないし、でもそれを秘匿にするのは正直あまりうれしくない(できるだけパブリックにしておきたい)。どうにかならないものか。

って考えたときに、「ソースを管理する領域(GitHub)とaabファイル生成をする領域を別にすればいいんじゃん。そうすればソースはパブリックのままでいいし、署名キーやら生成したaabファイルやらも秘匿の状態を保てる。とても幸せやん。」と気づいた。

上記を整理すると以下のフローとなる。

  1. ローカル環境で開発
  2. GitHubに変更をプッシュ
  3. GitHubはGitHubActionのジョブを回す
    • テストを回したり
    • Readme更新したり
    • CI/CDサービスに登録してあるジョブのトリガーを引いたり ★1
  4. 前項のトリガーを受けて、CI/CDサービスはaabファイルを生成する。
    1. GitHubからソースコードをクローンしてくる。
    2. aabファイルを生成し、成果物として管理。
  5. CI/CDサービスはプレイストアにaabファイルをアップロードする。 ★2
    1. どこまでできるか次第だが、リリースノートなども合わせてアップロードできればなお嬉しい。
  6. CI/CDサービスはすべての処理が完了したことを通知する。
  7. 私はそれを確認して、特に問題なければリリース。何かあれば適宜修正。

素敵だ。上記フローを実現するために必要な調査は★1,2の内容。

  • ★1:登録したジョブを実行するトリガーを外部から引くことはできる?(CI/CDのサービス次第?)
  • ★2:aabファイルをプレイストアにアップロードするクチ(=IF)は用意されている?どこまでの情報をアップロードできる?

まあ、「そんなのありません」ってことはないんだろうけどね。特に★1については「サービスとしてはあるけど、有料会員様限定のサービスなんすよ」かもしれないし。

というわけで、考えていることでした。調査した結果とか、更新があればまた改めて投稿します。

2023年7月14日金曜日

iOS UILabel/UITextField/UITextViewの違い

  • UILabel:AndroidのTextViewと同じ。テキストを表示するだけのView。
  • UITextField:singleLine限定のEditText。入力は一行のみ。
  • UITextView:AndroidのEditTextと同じ。入力は複数行。

2023年7月13日木曜日

iOS よくわからないエラー集「failed to get the task for proces」

Xcodeのプロジェクトで設定した証明書ファイル群にデバッグビルドによるビルドができるような設定になっていない。

参考

2023年7月12日水曜日

Android OnClickListener はXMLレイアウトファイルからでも設定できる

どうも。どっことです。今回はXML上でクリックリスナーを設定する実装方法について解説します。

XMLでクリックリスナーを設定する

dataBindingでは、XMLでクリックリスナーを設定する処理はよくある実装ですが、実はdataBindingを使わない通常のケースでも似たような実装をすることができます。

解説

XMLでクリックリスナーを設定するには、任意のViewに対して android:onClickを設定してあげます。たとえば、TextViewに設定するときは以下のような形です。

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="テスト"
   android:onClick="testClicked" />

あとはこのXMLを読み込むクラスに、testClicked(View v)関数を実装します。

override fun testClicked(v: View) {
  Log.d("TAG", "TextView テスト is clicked")
}

今回設定したonClickの値はtestClickedなのでそれに合わせていますが、実際に利用する際はTextViewがどんな表示をしているかなどを表現した関数名にしてあげると後々にコードを追いやすくなるかと思います。

まとめ

今回はXMLでクリックリスナーを設定する実装方法について解説しました。dataBindingでも似たような実装方法があると思いますが、そうでなくてもシンプルに利用することができるので、参考にしていただけると幸いです。

参考

2023年7月11日火曜日

Android ダークモードを実装するときの実装手順について解説

どうも。どっことです。今回はダークモード対応に関する実装について解説します。

ダークモードを実装する

アプリをいい感じに見せたいのであれば、ダークモード対応は今や必須の項目の一つではないでしょうか。ユーザへの見せ方をライト/ダークでそれぞれ管理する必要があるのは大変ですが、それ以上にユーザに好印象を与えることができるアプローチになります。今回はそんなダークモード対応をする時のアプローチ方法を解説します。

実装手順

必要最小限でダークモードを対応する場合、以下の手順になります。

  • ダークモードにした時にどのように見えるか確認
  • 修正が必要な箇所について色指定を修正
  • ライト/ダークモードの切り替え動線を用意

順番に解説していきます。

ダークモードにした時にどのように見えるか確認

まずは以下をApplication#onCreate()で呼び出してみましょう。

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)

これを呼び出すとアプリがダークモードで表示されます。この状態でアプリがどのように見えるか確認しましょう。

修正が必要な箇所について色指定を修正

テキストが背景と同色で見えなくなっているなど、残念な表示となっている箇所は適宜修正しましょう。values-nightフォルダを作ってそこにcolors.xmlを用意し、色指定を追加してください。このときvalues/colors.xmlでの定義とvalues-night/colors.xmlで定義するnameは同じものを指定してください。そうすれば、ライト/ダークの切り替え時にそれぞれの色指定を参照してくれます。逆に異なるものを指定すると、ビルド時に警告表示されるはずです。

ライト/ダークモードの切り替え導線を用意

あとはユーザがライト/ダークモード(+端末の設定に従う設定)を切り替えられるよう、切り替えスイッチのような動線を用意しておきましょう。アプリ内に設定画面などがあれば、そちらに用意するのが適切かと思います。

切り替え時の処理

ユーザがスイッチなどで設定を変えた場合の処理が必要です。ユーザの切り替え設定に従い、テーマを更新します。また、設定した値はアプリ固有領域(例えばSharedPreferenceなど)に保持しておきましょう。以下はSharedPreferenceに保存した時のサンプルです。

/** 
 * ユーザが設定したテーマ。
 * AppCompatDelegate.MODE_NIGHT_YES, 
 * AppCompatDelegate.MODE_NIGHT_NO など 
 **/
val value = AppCompatDelegate.MODE_NIGHT_YES
AppCompatDelegate.setDefaultNightMode(value)
getSharedPreferences("sample",  MODE_PRIVATE)
  .edit().putInt("theme_setting", value).apply()

アプリ起動時の処理

アプリを起動した時に、ユーザの設定に合わせてテーマを更新する必要があります。先ほど実装したApplication#onCreate()の実装を以下に変更してください。

val value = getSharedPreferences("sample", MODE_PRIVATE)
            .getInt("theme_setting", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
AppCompatDelegate.setDefaultNightMode(value)

まとめ

今回は、アプリにダークモードを実装する時の実装手順について解説しました。ダークモードはユーザに良い印象を提供するためには必須の項目となっているので、今回の内容を参考にして是非対応してみてください。

参考

2023年7月5日水曜日

OutlookでリンクをクリックするとEdgeが開くんだが

以下の設定を更新する。

ファイル > オプション > 詳細設定 > リンク処理

移行予定

どうも。どっことです。 タイトルの通りですが、諸事情により GitHubPage に移行予定です。 https://mkt120.github.io/ この備忘録に記載の内容を転記しつつ、今後はこちらのページを更新していく予定です。