2022年8月20日土曜日

Android deprecatedとされたsetTargetFragmentを推奨先に変える方法を解説

どうも。どっことです。今回はdeprecatedとなっているFragment#setTargetFragmentの移行対応について解説します。

setTargetFragmentの移行対応について

複数のFragmentを使って画面遷移を実装するとき、遷移先の画面で決めたことを遷移元の画面に教えてあげたいときがあります。たとえば

  • 設定画面で設定の要素をタップしたらそれに関する設定値の一覧が表示される。
  • ユーザは一覧から設定項目を選択する。
  • 選択した値を設定画面に返してあげて、設定画面にはユーザが選択したものを最新の設定値として表示する。

などですね。

これまではFragment間のデータのやりとりはtargetFragmentを使うことで実現できました。呼び出し元のFragmentを遷移先から参照できるようにして、その参照から呼び出しもとにデータを返してあげる。という具合です。

さて、「これまでは」と書いた通り、このメソッドdeprecated(非推奨)になってしまいました。これの扱いが簡単だったので個人的に非常にありがたかったのですが、Google様のお達しなので、今後は推奨されるやり方で実装する必要があります。今回は備忘録も含め、新しい実装方法を載せたいと思います。

解説

以下の順に解説していきます。

  1. 呼び出される側の実装
  2. 呼び出す側の実装
    1. Activity → Fragmentの場合
    2. Fragment → Fragmentの場合

呼び出される側の実装

parentFragmentManager.setFragmentResult("request_key", Bundle())    

呼び出される側の実装はシンプルで、(自身を管理している)parentFragmentManagerに渡したいデータを設定します。 通知したい情報をBundleに詰める必要があるため、上記の例は必要最低限のスニペットです。

呼び出す側の実装

Activity -> Fragment の場合

supportFragmentManager.setFragmentResultListener("request_key", this) { _, result ->
    // result を参照して結果を処理する
}

呼び出すFragmentActivityが持っているsupportFragmentManagerが管理しているので、それに対してResultListenerをセットしてあげます。ActivityDialogFragmentを表示するときは上記の実装になります。

Fragment -> Fragment の場合

childFragmentManager.setFragmentResultListener("request_key", this) { _, result ->
  // result を参照して結果を処理する
}

一方で Fragment -> Fragment の場合はちょっとだけ注意が必要です。呼び出すFragmentを呼び出し元のFragmentが持つFragmentManager管理している場合、こちらの実装パターンが利用できます。例えばFragmentが、自身が持っているchildFragmentManagerを使ってDialogFragmentを表示するなどの場合は、こちらの実装が使えます。

ただし、表示するDialogFragentActivityが持っているsupportFragmentManagerが表示する場合、このパターンではなく Activity -> Fragmentのパターンで実装することになります。表示するDialogFragmentをどのFragmentManagerが管理するかが違うためです。

まとめ

今回はdeprecatedとなっているFragment#setTargetFragmentの移行について解説しました。開発期間が長いアプリケーションでは、まだまだsetTargetFragmentFragment間での結果のやり取りを処理しているものも多いのではないでしょうか。deprecatedとなっているメソッドのため、なるべく早めに解消しておきたいですね。

Kotlin Collectionsをシャッフルするときの実装について解説

どうも。どっことです。今回はKotlinによる実装で、Collections派生の要素をシャッフルする実装について解説します。

Collectionsの要素をシャッフルする時の実装

Javaでいう所の以下のメソッドのことです。

Collections.shuffle(List)

Kotlinでは、以下のように実装します。

// シャッフル対象となるList
var mutableList = mutableListOf<Any>() 
Collections.shuffle(mutableList)

ポイントはシャッフルするCollectionsがMutableである点です。

Kotlinでは同じCollectionsでも、要素を加工できるMutableという概念があります。要素が加工できるCollectionsは、クラス名の接頭語にMutableが付与されています(MutableList, MutableHashMapなど)。逆に要素を加工できないものはMutableが付与されていません。そのため、Kotlinに慣れていないJavaユーザがいざListなどのインスタンスを作っても「要素の追加や削除ができない...」といったトラブルが発生しがちです。

上記の経緯から、シャッフル対象のCollectionsはMutableにする必要があります。という話でした。

まとめ

今回はCollections派生の要素をシャッフルする実装について解説しました。JavaユーザがKotlinに移行するときのすごくよくあるハマりポイントだと思うので、ぜひ参考にしてくださいね。

2022年8月13日土曜日

Android deprecatedとなったstartActivityForResult()の修正方法を解説

どうも。どっことです。今回は、deprecatedとなってしまったActivity#startActivityForResult()の修正方法について解説します。

deprecatedとなったstartActivityForResult()の対応

一昔前のAndroidアプリ開発であればActivity#startActivityForResult()による画面間のデータやり取りは鉄板中の鉄板として解説されていたネタですが、残念なことにこのメソッドがdeprecatedとなってしまいました。初級者でも使い勝手がよい背景から、さまざまなプロジェクトで使用されていたこのメソッドですが、いざdeprecatedとなっても修正できず困っている人も多いと思います。今回は、実は簡単にできるstartActivityForResult()の修正方法について解説します。

修正方法

修正方法は以下の手順です。

  1. registerForActivityResult()launcherを用意する。
    • Activityが終了したときの結果をここで実装しておく。
  2. Activityを起動するためのIntentを生成する。
  3. launcherを使ってIntentlaunchする。
順に解説していきます。

registerForActivityResult() でlauncherを用意する

Activityに新しく用意されているregisterForActivityResult()を使って、launcherを用意します。この時に、Activityから渡される結果を制御するための処理を実装しておきます。

val launcher = registerForActivityResult(StartActivityForResult()) { result ->
    // onActivityResultに対応するコールバック
    if (result.resultCode != Activity.RESULT_OK) {
        // Activity.RESULT_OK 以外
        return@registerForActivityResult
    }
    // RESULT_OK時の処理 
    // setResultで設定したintentから値を取得する
    val value = result.data.getStringExtra(key)
    // 取得した値を良しなにする
}

Activityを起動するためのIntentを生成する

ここまで実装すれば、あとはstartActivityForResult()の時と同じです。Intentを作成します。

val intent = Intent(applicationContext, NextActivity::class.java)

launcherを使ってIntentをlaunchする

作成したintentlauncherlaunchしてあげれば、修正できます。

launcher.launch(intent)

まとめ

今回はActivity#startActivityForResultのdeprecated解消方法について解説しました。いまだにstartActivityForResultを使っている開発者も少なからずいるかと思いますので、どんどん新しい機能を使えるよう情報をアップデートしていきたいですね。

参考

2022年8月4日木曜日

Android DialogFragmentをフルスクリーン化する実装方法を解説

どうも。どっことです。今回はDialogFragmentをフルスクリーン化する実装方法を解説します。

DiagloFragmentをフルスクリーンで表示する

DialogFragmentはダイアログを表示するためのクラスです。シンプルなダイアログを表示するために使うのはもちろんですが、自分で色々カスタマイズできる拡張性の高いクラスでもあります。今回は、そんなDialogFragmentをカスタマイズするために画面いっぱいに表示する実装(フルスクリーン化)を解説します。

実装方法

今回はまとめてスニペットを載せたいと思います。ポイントはonStart()です。
class CustomDialogFragment : DialogFragment() { 
  
  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return Dialog(requireActivity()).also { dialog ->
      // 表示したいViewをここで生成します。LayoutInflaterなどを使ってもOK
      val view = View(requireContext()) 
      dialog.setContentView(view)
    }
  }

  override fun onStart() {
    super.onStart()
    // onStart() で以下の処理をします。
    dialog?.window?.also {
       // ①背景色を背景透過にするよう設定(完全にフルスクリーンにするなら必要)
       it.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
       // ②画面サイズに合わせて領域を広げる
       it.setLayout(
          ViewGroup.LayoutParams.MATCH_PARENT,
          ViewGroup.LayoutParams.MATCH_PARENT
       )
    }
  }
}

onStart①背景色を透過にする②表示領域を画面いっぱいに広げるように設定します。これにより表示するダイアログをフルスクリーン化することができます。

まとめ

今回はDialogFragmentのフルスクリーン化について解説しました。DialogFragmentは拡張性が高い反面、使いこなすのが非常に難しいクラスです。ただ正しく理解さえできれば一つ一つの処理は難しいものでは無いので、どんなカスタマイズでもできるようにマスターしていきたいですね。


Android ProgressBarで進捗を表示する

横に伸びるProgressBarを表示する。

レイアウトファイル

    <ProgressBar
        android:id="@+id/progress_bar"
        style="?android:attr/progressBarStyleHorizontal"
        android:max="100"
        android:min="0"
        android:layout_width="240dp"
        android:layout_height="30dp"/>

コードで進捗を更新する。

val progressBar = it.findViewById<ProgressBar>(R.id.progress_bar)
progressBar.progress = 0 // アニメーション無し
progressBar.setProgress(progressBar.progress + 50, true) // アニメーション有り

// 滑らかなアニメーション
val animation = ObjectAnimator.ofInt(progressBar, "progress", progressBar.progress + 50)
animation.setDuration(1000); // 1000ms = 1sec の時間でアニメーションさせる
animation.setInterpolator(new DecelerateInterpolator());
animation.start();

2022年8月3日水曜日

Android ディレクトリを作成する

どうも。どっことです。今回はアプリ固有のファイル領域にディレクトリを生成する実装方法を解説します。

さっそくですが、実装サンプルです。階層構造のあるディレクトリを生成するケースとして、階層ごとに生成するケースとまとめて生成するケースを紹介します。

ディレクトリ階層ごとにディレクトリを生成する

階層ごとに生成するケースを紹介します。最初にディレクトリを生成したあと、そのディレクトリを親ディレクトリとして指定する形で子ディレクトリを生成します。

val parent1 = File(requireContext().filesDir, "parent1")
// ディレクトリ(parent1)を作成する
parent1.mkdir()
val parent2 = File(parent1, "parent2")
// 作成したparent1ディレクトリを親にして、子ディレクトリ(parent2)を作成する
parent2.mkdir()

複数のディレクトリ階層があってもまとめて生成する

こちらはまとめて生成するケースです。ディレクトリ名の指定に親ディレクトリを含めることで、その親ディレクトリもまとめて生成してくれる。というわけです。唯一注意が必要なのは、ディレクトリを生成する関数が前のサンプルではmkdir()をつかっていましたが、今回はmkdirs()を使っている点でしょうか。

val parents = File(requireContext().filesDir, "parent1/parent2")
parents.mkdirs()

2022年6月18日土曜日

HTML/CSS 画面上の文字や画像を選択状態にできないようにするCSS設定を紹介

どうも。どっことです。今回はWebページ上の文字や画像を選択できないようにするCSSについて解説します。

テキストや画像が選択状態にできないようにする

テキストにクリックリスナーを設定した際、そのテキストが選択状態になってしまい、シンプルにダサいです。今回はそんな経緯から、テキストや画像が選択状態にできない設定を紹介します。

設定方法

設定はシンプルで、該当のテキスト・画像のタグに以下を設定します。

user-select: none;

例えば画像に設定する場合はimgタグなので以下となります。

img { 
    user-select: none;
}

簡単ですが、以上です。

参考

2022年6月17日金曜日

Dart コンストラクタについて整理

サンプル1 finalなメンバ変数無し

class Sample {
  Sample();
}

サンプル2 finalなメンバ変数あり

class Sample {
  final String value;
  Sample(this.value);
}
  • Sampleクラスを使う側が value の値を設定しなければならない。

サンプル3 finalなメンバ変数あり + 初期値あり

class Sample {
  final String value;
  Sample({this.value = "initial string"});
}
  
  • Sampleクラスを使う側は value の値を設定する必要はない。
  • 設定しなければ初期値の値が設定される。

サンプル4 finalなメンバ変数あり + Nullable

class Sample {
  final String? value;
  Sample({this.value});
}
  • Sampleクラスを使う側は value の値を設定する必要はない。
  • 設定しなければnullが設定される。

2022年3月22日火曜日

Flutter ダイアログを表示する実装方法を解説

何も考えないなら以下をコピペして呼び出す。 

static void showDialog(BuildContext context, String title, String content, String positiveButton, String negative,
      {VoidCallback? onPositiveButtonPressed, VoidCallback? onNegativeButtonPressed}) {
      showDialog<int>(
        context: context,
        barrierDismissible: true, // キャンセル
        builder: (BuildContext context) {
          return AlertDialog(
              title: Text(title),
              content: Text(content),
              actions: [
                TextButton(child: Text(positiveButton), onPressed: onPositiveButtonPressed),
                TextButton(child: Text(negative), onPressed: onNegativeButtonPressed)
              ]);
        });
}

2021年5月10日月曜日

Android AdMob、FirebaseとEUユーザからの同意の取得について

経緯

AdMobとFirebase あたりから読み進めたので整理。

AdMobについて

さまざまなフォーマットオプションで広告を表示することができる。
  • バナー
  • インタースティシャル
  • ネイティブ
  • リワード

広告を表示するためには、Google Mobile Ads SDK を統合するための AdMob 登録アプリが必要。要はアプリをAdMobに登録してねってことかな。

    実装の流れ

    1. Firebaseにアプリを登録
    2. AdMobアカウントを作成
      1. アプリを登録
      2. ユーザに関する指標を有効にする
      3. AdMobアプリをFirebaseにリンクする
    3. プロジェクト依存関係の更新
      1. Google Mobile Ads SDK を追加
    4. 広告の実装

    まあ、これといって難しそうなところはなさそう。

    Androidスタートガイド

    AdMobアカウントでアプリを設定する

    1. アカウントを作成
    2. アプリを登録

    アプリをFirebaseにリンクする

    強く推奨らしい。AdMobアカウントのダッシュボードページで以下を設定する。

    1. ユーザに関する指標を有効化
    2. アプリをFirebaseにリンク

    ここからは具体的な実装の話

    1. アプリIDをAndroidManifest.xmlに追加
    2. Mobile Ads SDK をbuild.gradle追加
      1. MobileAds.initialize()をコールし初期化 (★気になる一文あり)
    3. ユーザに関する指標とアナリティクスデータ
      1. Mobile Ads SDK で初期化するとロギングが開始されるので、ちゃんと初期化できているか確認してね。と読み取れる。
    4. (省略可)GoogleAnalyticsやFirebaseの機能を使用
      1. 色々機能を使って、ユーザの利用数(エンゲージメント)を増やしてね。と読み取れる。
    5. アプリに実装する広告フォーマットの選択
      1. アプリに合わせて実装する広告のフォーマットを選択する。

      (★気になる一文あり)に以下の文言が。

      広告を読み込む前になんらかの操作を行う必要がある場合は、必ず Mobile Ads SDKを初期化する前に行ってください。初期化の前に必要なアクションの例を次に示します。
      欧州経済領域(EEA)のユーザーから同意を得る

      ヨーロッパって広告を出す前にユーザから同意を得ないといけないらしい。マジか。そういえば、どこかのサイトにアクセスするたびに「Cookie使うで?ええか?」という警告が出るようになったけど、あれってこれのせいか。スタートガイドとはいえ、こういうアナウンスをしてくれるのは優しいな。

      EU ユーザーからの同意の取得

      EUユーザに対しては以下のケースで同意を得る必要がある。

      • Cookieやローカルストレージの利用
      • 広告配信を目的とした個人データ(AdIDなど)の利用

      Googleではこの義務の遂行をサポートするためのConsent SDKを提供している。(実装が楽できる!うれしい!)Googleが配信する広告は「パーソナライズされている・いない」の2種類がある。

      • デフォルトは「されている」。「されていない」に変更することも可能。
      • EUユーザに対してはどちらを利用するにしても同意が必要。

      詳細

      前提としてAdMobアカウント上で、広告技術プロバイダを選択しておく必要がある。

      • build.gradleにConsent SDKを追加する
      • Consent SDKを使うときは、必ず事前に同意ステータスを更新しておく。
        • 同意ステータスは、ユーザが同意しているかどうか。
        • 「同意ステータスを更新する」は「同意ステータスを把握する」と読み取れる。
      • SDKに同意ステータスのアップデートをリクエストするAPIがあって、アプリを起動するときにそれをコールする。
        • ユーザが同意した後に、広告技術プロバイダが変わると、同意ステータスが取得できていない状態(不明ステータス)になる。

      メディエーションを利用しない場合・する場合について

      よくわからないメディエーションなるキーワードが出てきた。このへんによると、第三者広告ネットワーク?を使って収益向上を図る仕組みらしい。今度は第三者広告ネットワークが何者か分からないけど、このへんによると「収益性が良さげな広告を選んでくれる広告マネージャー」のことを指しているように見える。

      • 収益性が良さげな広告選んでくれるならええやん!ってなりそう

      メディエーションを利用しない場合

      Consent SDKで同意を得る実装方針は2つ

      • Google提供の同意フォームを掲示する
        • このフォームには開発者が選択した広告技術プロバイダのリストが表示される
      • すべての広告技術プロバイダのリストをAdMobから動的に取得し、開発者自身がそれらをユーザに掲示するための実装を行う。
        • こだわりがある人はこちら、という印象。
      • ユーザが回答したら、その回答情報をSDKに保存するように指示できる
        • これが完了したらGoogle Mobile Ads SDKに回答(同意)を送信できるようになる。
      • パーソナライズされていない広告に限って同意した場合は、それをGoogle Mobile Ads SDKで送信する必要がある。
        • 結局どちらの広告であってもSDKに送信が必要と読み取れる。

      AdMobメディエーションを利用する場合

      すべての広告技術プロバイダのリストをAdMobから動的に取得し、開発者自身がそれらをユーザに掲示するための実装を行う必要がある。Googleではメディエーションネットワークに関する同意の取得も処理もできない。

      • ただし一部のメディエーションネットワークに対する同意の転送方法についてのドキュメントは公開されているとのこと。
      • 何にせよ、便利なSDKがないならあまりやりたくない。

      同意ステータスの更新

      アプリ起動時に毎回実施する必要があるとのこと。具体的な同意ステータスの更新処理の実装方法は以下の通り。

      同意の取得

      Google提供の同意フォームの実装方法。以下のオプションを組み合わせてユーザに掲示できる。

      • パーソナライズ広告表示に同意
      • パーソナライズされていない広告表示に同意
      • 広告表示しない有料版を利用

      同意テキストを更新する場合はconsentform.htmlの内容を変更する。

      • ConsentFormオブジェクトを使って同意フォームを表示する。
        • プライバシーポリシーが必要!!
      • 同意を取得したら、同意の保存し情報を送信する。

      同意の変更や取り消し

      過去の同意を変更したり、取り消すことができる必要がある。

      同意年齢に満たないユーザ

      すべての広告リクエストにTFUAを設定する必要がある。

      • どうやって検知するんだろう。。。

      テスト

      EU内外の両方でアプリを簡単にテストできるように、Consent SDKにはデバッグオプションが用意されている。

      Google Mobile Ads SDK に同意の転送

      パーソナライズされていない広告を同意したケースの転送実装

      所感

      • AdMobメディエーションを利用すると色々大変そう。とりあえず利用しない方針で問題なさそう。
      • プライバシーポリシーのページを用意する必要がある。
      • 同意を変更する導線が必要。

      移行予定

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