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

Androidのログ出力ライブラリ”Timber”

どうも”おもり”です。

今年はOSS活動に精を出していこうと考えています。
Android開発のOSS活動。力不足の自分でもやっていけそうなissueを探している中、
以下のようなissueを見つけた。

image.png

https://github.com/uhooi/UhooiPicBook-Androidより

"good first issue"のタグが付いているので、いざやってみようと思ったのですが、
Timberとは何ぞや状態だったので、色々ネットを調べあさりました。
調べた情報をまとめて、Timberの良いところをサクッと学べるような記事を書きます。

Timberとは?

Timberとは、Androidのログ出力ライブラリです。

Androidのログ出力機能といえば、Androidのログ標準ライブラリ(Log)があります。

Log.v("MainActivity", "verbose")
Log.d("MainActivity", "debug")
Log.i("MainActivity", "info")
Log.w("MainActivity", "warn")
Log.e("MainActivity", "error")

※各ロギングの意味合い
Log.v:VERVOSE(すべてのログ情報)
Log.d:DEBUG(デバッグ情報)
Log.i:INFO(情報)
Log.w:WARN(警告)
Log.e:ERROR(致命的な問題)

しかし、ログ標準ライブラリを使うと問題があります。

  • リリースビルドなのにでバックログが出力される
  • デバックビルドなのにCrashlyticsに出力される

特に、リリースビルドでもでバックログが出力されることが大きな問題です。

Timberを使って嬉しいこと

  • デバックとリリースでログ出力を切り替えることができる。
    image.png
    ※リリースの場合は、自分でロギングクラスを作成する必要がある。

  • Logクラスの薄いラッパークラスなので導入が簡単

public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();

    Timber.plant(new DebugTree());
  }
}
簡単に導入できる。TreeをPlantして、初期化をする。
  • 書きやすい
Timber.tag("hoge").i("piyo=%s", "fuga");

Logライブラリの使い方と使用方法がそこまで違わないので、使うのが簡単。

  • リリース後のクラッシュログの収集ができる。

Crashlytics等を使ってリリース後のクラッシュログを収集することができるらしい。
自分のアプリを作る時に試してみよう。

参考資料

Androidのログ出力をいい感じにする
AndroidのTimber(ログ系)とFabric/Crashlytics(クラッシュレポート)を使ってみた。
[Android] Timber で debug ビルド時のみにログを出力する
(Android)ログ出力ライブラリ"Timber"を使ってみる
Logcatのソースへ飛ぶ機能をTimberに盛り込んでみた。
TimberをLogの代わりに使用して快適ログ確認

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

Android11から地味に変わったパーミッション

パーミッション is 何

Androidアプリはアクセスが制限されたサンドボックス内で実行されるため、アプリの外部にあるリソースや情報を使用する必要がある場合には、権限をリクエストする必要があります。この権限がパーミッションです。

Android5.1(APIレベル22)以前は、アプリのインストールと共に必要なパーミッションがアプリに付与されるため、特に権限のリクエストをする必要がありませんでした。

しかし、Android6.0(APIレベル23)以降からはガラッと変わり、パーミッションが必要なタイミングでユーザにリクエストをしなければいけなくなりました。例えば、現在地を取得する時に、端末に「位置情報の利用を許可しますか?」みたいな画面が出てくるようになりました。

Android11からまた変わったパーミッション

Android11(APIレベル30)から、アプリが

  • 位置情報
  • カメラ
  • マイク

に関するパーミッションをリクエストするたびに、「今回のみ」というオプションが表示されるようになりました。
ちなみに、iOSでは実はiOS13(正式公開日が2019/9/19)から追加されている機能ではあるので、既に見覚えがある方もいるのではと思います。

スクリーンショット 2021-01-21 22.07.48.png
参照:https://developer.android.com/training/permissions/requesting?hl=ja

とはいえ、実は変わったのはこれだけではありません。地味に変わった & 重要なところがもう一つあります。

パーミッションダイアログ表示の回数が一度だけに...

デバイスにインストールされたアプリがパーミッションダイアログを表示し、ユーザが「許可しない」を選択した場合、同じパーミッションダイアログが再度表示されることがなくなってしまいました。
例えば位置情報に対して「許可しない」をすると、他の位置情報関係のパーミッションリクエストのダイアログが表示されることはありません。

今までのパーミッションダイアログの表示は次のような形でした。
スクリーンショット 2021-01-21 22.38.29.png
しかし、Android11からは次のように変更されています。
スクリーンショット 2021-01-21 22.39.09.png
最初に表示されるダイアログの「許可しない(次回から表示しない)」が「許可しない」へ姿を変えています。

ユーザさんにとっては、「許可したく無いことに何度も出てくるダイアログうざい!」が解消されたのかもしれませんが、開発者側からすると、「許可の機会が減った!?」となりかねないような変更でした。

これからはユーザさんが選択する「許可しない(次回から表示しない)」のハードルが下がり、相対的に「許可する」のハードルが上がった形になると思います。
開発者からの「使って欲しいおすすめ機能」がパーミッション前提のものだと使ってもらえないなんてこともあるかもしれません。

まとめ

パーミッションの仕様が地味に変わった話でした。
Androidはこのような仕様変更がちょいちょいあるみたいですので、今後もしっかりと確認していきたいと改めて思いました。

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

KotlinでMapとListをfor文で操作しよう!

Key:Valueが1:1のMapをListにする

  // 1:1
  val map = mapOf("key1" to "val1", "key2" to "val2", "key3" to "val3")
  // mapのすべてのvalueが格納される
  val valueList = ArrayList(map.values)
  var list: MutableList<String>
  list = ArrayList()
  list.addAll(valueList)

1:1のMapを条件分岐でListに格納する

  val map = mapOf("key1" to "val1", "key2" to "val2", "key3" to "val3")
  val valueList = ArrayList(map.values)

  // key1のvalueがarray変数に格納される("val1")
  var araay = map.get("key1").toString()
  // itemは0からはじまる
  for (item in valueList.indices){
      // val1がvalueListにある場合はListに追加
      if (araay.contains(valueList[item])){
          list.add(valueList[item])
      }
  }

Mapのvalueを配列にしてネストさせる(1:n)

  // 1:n
  val mapArray = mapOf("keyX" to arrayOf("valA","valB", "valC").map { num -> num },
                       "keyY" to arrayOf("valD","valE").map { num -> num })
  val valueArrayList = ArrayList(mapArray.values)

1:nのMapを条件分岐でListに格納する

  // 1:n
  val mapArray = mapOf("keyX" to arrayOf("valA","valB", "valC").map { num -> num },
                       "keyY" to arrayOf("valD","valE").map { num -> num })
  val valueArrayList = ArrayList(mapArray.values)

  var araay = mapArray.get("keyX").toString()
  for (item in valueArrayList.indices) {
      // ネストしている配列の要素数だけループする
      for (i in valueArrayList[item].indices) {
          if (araay.contains(valueArrayList[item].get(i))) {
             list.add(valueArrayList[item].get(i))
          }
      }
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】加速度センサーの値を表示する方法

プログラミング勉強日記

2021年1月21日
Androidに搭載されている加速度センサーを使ってx軸、y軸、z軸の各軸にかかる加速度を測定する方法をまとめる。

加速度センサーについて

 加速度センサーは以下のようにx軸、y軸、z軸のそれぞれに対して単位Gの値として情報を素得できる。垂直で縦に持つ場合は、以下のようになり-y方向に重力加速度がかかるので、加速度センサーの値は(x, y, z)=(0, -1, 0)

image.png

出展: iOSイベント処理ガイド

加速度センサーの値を表示するプログラム

MainActivity.java
package sample;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.TextView;
import android.view.View;

public class MainActivity extends Activity implements SensorEventListener {

    private TextView myText;
    // SensorManagerインスタンス
    private SensorManager sma;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myText = findViewById(R.id.my_text);
        // SensorManagerのインスタンスを取得する
        sma = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        sma.registerListener(this, sma.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                SensorManager.SENSOR_DELAY_FASTEST);
    }


    // センサーの値が変化すると呼ばれる
    public void onSensorChanged(SensorEvent event) {
        double x = event.values[0];
        double y = event.values[1];
        double z = event.values[2];

        String str =   " X= " + x + "\n"
                     + " Y= " + y + "\n"
                     + " Z= " + z;
        myText.setText(str);
    }

    // センサーの精度が変更されると呼ばれる
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

}
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/my_text"
            android:layout_width="343dp"
            android:layout_height="119dp"
            android:paddingLeft="30dp"
            android:paddingTop="25dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

実行結果
image.png

参考文献

スマートフォンの加速度センサについて

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

Google Play App Signing に対応させる

概要

自分の個人アプリを Google Play App Signing に対応させた時の作業に基づき、署名移行時の具体的な作業内容について書きます。誤った内容があったらコメントいただければと思います。

詳細: https://developer.android.com/studio/publish/app-signing?hl=ja

基礎知識

Google Play App Signing とは

Androidアプリに対して付与する署名の新しい管理方式。アプリ署名とアップロード署名の2段階で署名され、安全な鍵管理が行えるようになります。
アプリ署名:Google Play側で管理され、登録時に勝手に署名されます。こちらから触れず、基本的には変わりません。
アップロード署名: ユーザ側で管理し、Google Playにアプリをアップロードする時にこれで署名されている必要があります。流出時などに申請で変更することができます。

対応期限

Google Playにアプリ登録するためには必ず署名が必要になり、現状では従来の署名方式と、Google Play App Signingのいずれかで登録できます。ただし、AAB形式で登録する上でGoogle Play App Signing対応が必須となり、新規アプリは2021年8月以降、既存アプリは2021年11月以降はAAB対応が必須となることから、そこまでに対応する必要があります。

作業内容

Google Play App Signing への登録作業

端的に書くと、鍵の登録作業は次のようになります。

  • 既存アプリが移行する場合
    • アプリ署名: 既存の署名をGoogle Playに登録する
    • アップロード署名: 新しく発行する
  • 新規アプリ
    • アプリ署名: Google Playでアプリ登録すると勝手に作られる (作業不要)
    • アップロード署名: 新しく発行する

既存アプリの移行

既存アプリのメニューから「アプリの署名」を開くと、次の画面が開きます。

ここでは、「Android Studioから鍵をエクスポートしてアップロードする」による手順について書きます。

  1. Android Studioから鍵をエクスポートします
    手順に従って、従来の署名からアプリ署名鍵ファイル(.pepk)を生成します。(これがアプリ署名になるので、厳重に扱います。)
  2. 秘密鍵をアップロード
    1で作成した鍵をアップロードします。
  3. セキュリティを強化するには、新しいアップロード鍵を生成してください
    (省略可)となっていますが、省略するとアプリ署名と同じ鍵が使われるようなので、セキュリティの観点から省略するべきではないと思います。作業としては、次のようになります。
    • 手順に従いAndroid Studioを使って新しいアップロード用のkeystoreファイルを作成します。
    • 表示されているコマンドに従い、pemファイルを作成します。
    • pemファイルをアップロードします。

以上で、移行が完了です。

新規アプリの登録

現在、Google Playで新規アプリを作成すると、アプリ署名は自動的に作成されるため、こちらで作成等の作業をすることはないと思います。
アップロード署名については、新しく署名したAAB/APKをGoogle Playにアップロードしてリリースすると、そこに使われている署名が自動的にアップロード署名として扱われ、以後はそれを運用してくようになります。

アプリの更新

アプリを更新する場合は、アップロード署名が付与されたAAB/APKをGoogle Playにアップロードします。すると、Google Playで自動的にアプリ署名がされ、それがストア上にリリースされます。
ちなみに、手元で動作確認をするために、アプリ署名されたアプリが欲しい場合、Google Play Consoleのメニューから、「App Bundle エクスプローラ」を開き、「ダウンロード」タブを開くと、APKがダウンロードできます。

その他

既存の署名の管理はどうするか

(新しいアップロード鍵の生成を省略しなかった場合は)恐らく今後扱うことは無いと思うので、触れないようにアプリ管理者が厳重保管しておくのが良いと思います。移行作業で作成したpepkファイルは削除してしまいましょう。CIなどに署名用に登録している署名ファイルは、アップロード署名に置き換えます。とにかく、人目に触れるところに残さないようにするべきです。
新しいアップロード鍵の生成を省略した場合は、これがそのままアップロード署名になるはずなので従来と同様に運用します。

アップロード署名の管理はどうするか

新しく発行する必要がありますが、従来の署名で使っていたパスワードと異なるパスワードを与えた方がいいと思います。こちらは手元でファイルを管理し、従来の署名と同様に扱います。

一つの開発者/企業で複数アプリを管理する必要がある場合、アプリ毎に発行するのではなく、共通のアップロード署名として管理する方法でも良いのかも知れませんが、この辺の正式な運用についてあまり知見がないので、よく分かっていません。
参考: Androidアプリ開発のキーストアとエイリアスについて – 時にはWEBの話っ!

署名の違いによる注意点

Google Map SDKなど、フィンガープリントを登録することで、APIを利用可能なアプリを制限するサービスを利用する場合、アップロード署名が付与されたアプリと、アプリ署名が付与されたアプリは、別のフィンガープリントになるため、それぞれの値を登録する必要がある点に注意です(アップロード署名版の方は無視してもいいかもしれませんが)。また、公開前にアプリの動作確認をする場合は、App Bundle エクスプローラからアプリ署名されたAPKをダウンロードし、それを使って動作を確認します。

まとめ

Google Play App Signingは、署名が2つに分かれたことで管理がやや面倒になりますが、今年中に対応が必須となりますし、鍵の紛失などに備えることができるため、早めに対応した方が良いかと思います。

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

Fragmentから他のフラグメントへの画面遷移

FragmentからFragmentへの遷移

画面遷移の動きは、以下のようになります。

画面収録-2021-01-21-10.13.11.gif

動きとしては、
・MainActivityにFragment01(WelcomeFragment)を表示
・Fragment01からFragment02(MainFragment)へ遷移
・Fragment02からFragment03(SubFragment)へ遷移


サンプルコード

WelcomeFragment
class WelcomeFragment: Fragment(R.layout.welcome_fragment) {

    private var _binding: WelcomeFragmentBinding? = null //
    private val binding: WelcomeFragmentBinding get() =  _binding!!

    override fun onViewCreated(view: View, saveInstanceState: Bundle?) {
        super.onViewCreated(view, saveInstanceState)
        this._binding = WelcomeFragmentBinding.bind(view)

        binding.next.setOnClickListener{
            findNavController().navigate(R.id.action_welcomeFragment_to_mainFragment)
        }
    }
}
MainFragment
class MainFragment:Fragment(R.layout.main_fragment) {

    private var _binding: MainFragmentBinding? = null //
    private val binding: MainFragmentBinding get() = _binding!!

    override fun onViewCreated(view: View, saveInstanceState: Bundle?) {
        super.onViewCreated(view, saveInstanceState)
        this._binding = MainFragmentBinding.bind(view)

        binding.nextTwo.setOnClickListener{
            findNavController().navigate(R.id.action_mainFragment_to_subFragment)
        }
    }
}
class SubFragment: Fragment(R.layout.sub_fragment) {
    private var _binding: SubFragmentBinding? = null
    private val binding: SubFragmentBinding get() = _binding!!

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        this._binding = SubFragmentBinding.bind(view)

        binding.nextLastButton.setOnClickListener {
            Toast.makeText(context,"よくできました",Toast.LENGTH_SHORT).show()
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        this._binding = null
    }
}

View bindを使用しているため、以下をbulidに追加する

android {
  ...
  buildFeatures {
      viewBinding true
  }
}

/* または
android {
  ...
  viewBinding {
      enabled = true
  }
}
*/

追記:Fragmentにコンストラクタで、以下を渡してますが、推奨されていないようです。

R.layout~

Reference:

https://developer.android.com/topic/libraries/view-binding?hl=ja

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

3 ヶ月掛けて 1 人で Android アプリをリプレイスした話

最近、現場で 3 ヶ月掛けてアプリのリプレイスを 1 人で行いました。
下記はプルリク時の修正ファイル数の画像です。

1.jpg

どのようなきっかけでリプレイスを行い、問題点は何だったか、具体的に何をやって結果どうなったかなどをまとめます。

ソースコード構成

コア機能を実装したモジュールがあり、各社ごとにアプリを作成し、各アプリがそのモジュールを参照する構成となっております。
リプレイス対象としてはモジュールと、ある会社向けにリリースしていたアプリの 2 つになります。

なぜリプレイスを行ったのか

きっかけはとある会社から直近で大幅な機能追加をしてほしいと要望があったことです。
ところが現行アプリは機能追加を想定した作りになっておらず、このまま追加するのが困難だと予想されました。
また、今後もさらに機能追加を行っていくとのことで、このままでは間違いなく立ち行かなくなることが容易に想像できました。
1 人で行ったのは私がチームの中で 1 番 Android の知見があったのと、単純に人手が足りなかったためです。

問題点

独自アーキテクチャ

アーキテクチャには MVC や MVVM などありますが、そのどれでもない独自のアーキテクチャとなっていました。
もちろん深い理由があって独自にしているのなら仕方ないですが、特に独自にしないといけない理由は無いようでした。
また、独自アーキテクチャなのでなぜその実装なのか実装者に聞かないとわからなく、完全に属人化してしまっていました。

グローバル変数、グローバルメソッドの多さ

使えるグローバル変数やグローバルメソッドが多く、各クラスの依存関係がカオスな状態でした。
また、どこからアクセスされても大丈夫なように過剰なパラメータチェックがあり、仕様が複雑化する要因となっていました。

Fat View

View の中にビジネスロジックが書かれている箇所が多々あり、Fat View になっていました。

テストコードが実装できない

上述した問題があったため、とてもテストコードが実装できる状態ではありませんでした。
代わりに別途 DB を無理やり書き換えるテストクラスを作ったり、ボタンを押して各機能を試せるデバッグアプリがありました。
ですが、DB を無理やり書き換えたりデバッグアプリで試したとしてもテストとしては怪しいです。

非同期処理に Thread や AsyncTask が使われている

言語は全て Kotlin でしたが、非同期処理に Thread や AsyncTask が使われていました。
Thread は Coroutine と比べてパフォーマンスが悪いですし、AsyncTask は Android 11 以降で非推奨となるので、 Coroutine に置き換える必要がありました。

やったこと

アーキテクチャの大幅見直し

アーキテクチャを MVVM に変更することとしました。
理由はアプリ アーキテクチャ ガイドで推奨されていたためです。
推奨されているアーキテクチャを使うことで、Android Jetpack をフル活用して効率よく開発ができると考えました。
そして MVVM 適用後の全体図を作成し、チーム全員で話し合った結果、これで行こうとなりました。

実装

肝となる実装です。
実装に関してはトライアンドエラーで、上述した全体図をベースに実装しては動かして調整を繰り返しました。
同時に上述した問題点も潰していきました。
ただし、いきなり古いコードを全削除して書き直すのではなく、古いコードは基本的に残しておくことで影響範囲を極力抑えるようにしました。
そして、機能追加が必要なアプリのみ、新しいコードを参照するように書き換えました。
実装にあたって、主に下記のライブラリを使用しました。

テストコードの実装

実装で DI を導入するなどしてテスト可能な状態になったので、テストコードも実装しました。
過去に作っていたテストクラスやデバッグアプリで行っていた内容をテストコードで実装し、品質をある程度担保できるようにしました。
また、CI のためにビルドとテストコードを実行するためのシェルスクリプトを作成しました。
テストコードの実装にあたって、主に MockK を使用しました。

リプレイス後のソースについて説明会を適宜実施

上述した通り、私 1 人でリプレイスを行っていたため、他メンバーは私が具体的にどうリプレイスしたか把握していません。
そのため説明会を適宜実施しました。
使用したライブラリの説明や各クラスの役割についての説明などを行いました。

各機能のシーケンス図を作成

ソースコードだけでは他メンバーが把握するのに情報不足なため、別途各機能のシーケンス図を作成しました。
現場では図を書くのにLucidchartを使用しており、手書きするのが煩わしかったのでマークアップを使用して作成しました。
PlantUMLのような感覚で使えるので、慣れればサクッと作成できます。

良かった点

機能追加がしやすくなった

UI やビジネスロジックが分離されることで、どこに何を実装するべきかが明瞭になりました。
また、新しく機能を実装する際にも他の機能の実装を見様見真似で実装できるようになりました。

仕様がシンプルになり、把握しやすくなった

無駄な仕様を省くことで仕様がシンプルになりました。
また、データの流れが View -> ViewModel -> Repository -> API or DB という 1 本の形となって仕様が把握しやすいと他メンバーからも言われるようになりました。

パフォーマンス向上によりメモリリークしなくなった

非同期処理を全部 Coroutine に置き換えたことで、今まで長時間稼働させていたときにメモリリークするケースがあったのを解消できました。

Android Jetpack の知識が増えた

今まで Android Jetpack を使ったことがあまりなかったのですが、導入から一通りやってみることで Android Jetpack の知識や便利さを実感できました。
こんなに便利なのに何で今まで使ってこなかったんだろうって思いました。
今後の開発で使う機会があればバンバン使っていきたいです。

課題

他メンバーへの教育

説明会を適宜実施したり、各機能のシーケンス図を作成したりしましたが、完璧に理解できているかというとそうではないと思っています。
ここからは他メンバーにバグを修正してもらうなどして実際に触れてもらい、コードレビューの中で教育していけたらと考えています。

古いコードをいつ削除するか

影響範囲を極力抑えるため、古いコードは基本的に残しましたが、いずれは他のアプリも新しいコードを参照するようにして古いコードを削除していく必要があります。
まずはリプレイスしたアプリに関して全機能テストを行っていき、バグを全部潰したタイミングで他のアプリも同じようにリプレイスしていくことを考えています。
もちろん直近でリリースしなければならないアプリについてはリリース優先でリプレイスせず、タイミングを見る必要があります。
また、「どのコードが削除対象なのか分からない」という指摘があったので、別途削除対象のコードを一覧化して共有しました。

CI/CD ツールの導入

CI 用にシェルスクリプトを作成しましたが、いちいち手動で実行しないといけない分面倒です。
CI/CD ツールを是非導入したいのですが、会社の都合上できていない状況なので打診してみます。

まとめ

現場で 3 ヶ月掛けてアプリのリプレイスを 1 人で行った話をまとめました。
Android Jetpack や Coroutine などのライブラリを駆使することで、無駄なコードが減り、効率の良い開発やパフォーマンス向上などの恩恵を受けることができました。
また、リプレイス後の内容を他メンバーにキャッチアップしてもらうための工夫も重要です。
まだ課題も残っているので、そこを早く潰していきたいです。
本来こういう経験はなかなかできないので、かなり勉強になりました。

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

AndroidからAWS Cognitoのユーザプールをさっくり使う

スクリーンショット 2021-01-21 8.25.15.png

サインアップやサインイン、パスワード再発行など、ユーザ管理がないアプリケーションを見かけない日はありません
とはいえ自前でバックエンドのシステムを作るのも大変...
でもAmplifyはちょっとリッチすぎる気がする...

そんな私のための備忘録です

下記のことができます

  • メールアドレス、パスワードによるサインアップ、ログアウト
  • メールアドレス認証
  • パスワード再発行

メールアドレスやパスワードはガッツリ個人情報です
その辺りの管理をAWSにお願いできるのはかなり気楽ですよねー

前準備

SES (Simple Email Service)に配信用のメールアドレスを登録する

0_SESにメールアドレスを登録し、送られたURLをクリックしてVerifyする.PNG

AWSのSESに、認証コードを送信する用のメールアドレスを登録、認証しておきます
登録したメールアドレスに確認用のURLが届くのでクリックすると認証できます
Verification Statusが"verified"になっていれば使用可能です

1_登録したメールアドレスでテストメールを送信する.PNG

"Send a Test Email"ボタンで自分自身にテストメールを送り、無事受信できればOKです

Cognitoにユーザプールを作成する

2_CognitoUserPoolを作成する.PNG

AWS Cognitoのダッシュボードにアクセスし、UserPoolを作成します
送信元のEメールアドレスに、先ほど認証したメールアドレスを登録し、Amazon SESによるEメール配信を”はい”に指定します

3_アプリクライアントの認証はとりあえずこんな漢字.PNG

アプリクライアント側の設定はこんな感じです

Androidクライアントを実装する

依存関係

依存関係に、下記を追加します

build.gradle
implementation 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.19.0'

いよいよ実装

初期化

val userPoolId = "ap-northeast-1_xxxxxxxx"
val clientId = "yyyyyyyyyyyyy"
val clientSecret = "zzzzzzzzzzzzzz"
val userPool = CognitoUserPool(context, userPoolId, clientId, clientSecret, Regions.AP_NORTHEAST_1)

先ほど作成したCognitoUserPoolのUserPoolId, ClientId, ClientSecretを使ってインスタンスを作成します
以後、このuserPoolインスタンスを使って各種アクセスをおこないます

サインアップ

サインアップをするには2つのメソッドが必要です

  1. signUpInBackground : userPoolにユーザを作成する
  2. confirmSignUpInBackground : cognitoユーザを認証する

まずはsignUpInBackgroundです
この時点でuserPoolにユーザが追加され、本人確認用コードが送信されます
ただしUNCONFIRMEDの状態で何もできません

fun signUp(username: String, 
           password: String,
           successCallback: () -> Unit, 
           failureCallback: (exception: Exception?) -> Unit
) {
    val userAttributes = CognitoUserAttributes()

    userPool.signUpInBackground(username, password, userAttributes, null,
            object : SignUpHandler {
                override fun onSuccess(user: CognitoUser?, signUpResult: SignUpResult?) {
                    successCallback()
                }

                override fun onFailure(exception: java.lang.Exception?) {
                    failureCallback(exception)
                }

            }
    )
} 

このままだとサインインも何もできないので、続いて本人確認をする必要があります
送信された認証用コードを使ってconfirmSignUpInBackgroundを呼ぶとCONFIRMED状態になります

fun confirmSignUp(username: String, 
                  verifyCode: String,
                  successCallback: () -> Unit,
                  failureCallback: (exception: Exception?) -> Unit
) {
    userPool.getUser(username).confirmSignUpInBackground(verifyCode, false,
        object : GenericHandler {
            override fun onSuccess() {
                successCallback()
            }

            override fun onFailure(exception: Exception?) {
                failureCallback(exception)
            }
        })
}

サインイン

サインインにはgetSessionInBackgroundを使用します
一度サインインに成功すると端末内にセッションとして状態が保持されるようです
今回は2段階認証を有効化していないので、getMFACodeなどはコールされない(はず)

fun signIn(username: String, 
           password: String,
           successCallback: () -> Unit,
           failureCallback: (exception: Exception?) -> Unit
) {
    userPool.getUser(username).getSessionInBackground(
        object : AuthenticationHandler {
            override fun onSuccess(userSession: CognitoUserSession?, newDevice: CognitoDevice?) {
                successCallback()
            }
            override fun getAuthenticationDetails(
                authenticationContinuation: AuthenticationContinuation?,
                userId: String?
            ) {
                val authenticationDetails = AuthenticationDetails(userId, password, null)
                authenticationContinuation!!.setAuthenticationDetails(authenticationDetails)
                authenticationContinuation.continueTask()
            }

            override fun getMFACode(continuation: MultiFactorAuthenticationContinuation?) {
                /* should not be called */
            }

            override fun authenticationChallenge(continuation: ChallengeContinuation?) {
                /* should not be called */
            }

            override fun onFailure(exception: Exception?) {
                failureCallback(exception)
            }
        })
}

パスワード再発行

パスワードの再発行にはforgotPasswordInBackGroundを使います
実行するとパスワード再発行用の認証コードがメールアドレスに送られ、getResetCodeコールバックが返ってきます
その時に一緒に得られるcontinuationを使って、新しい認証コードとパスワードを設定すると完了です

認証周りはサインアップと似たようなものかと思いきや、少し違いますね

fun forgotPassword(username: String,
                   successCallback: () -> Unit,
                   verifyCallback: () -> Unit,
                   failureCallback: (exception: Exception?) -> Unit
) {
    userPool.getUser(username).forgotPasswordInBackground(
        object: ForgotPasswordHandler {
            override fun onSuccess() {
                successCallback()
            }

            override fun getResetCode(forgotPasswordContinuation: ForgotPasswordContinuation?) {
                continuation = forgotPasswordContinuation
                verifyCallback()
            }

            override fun onFailure(exception: Exception?) {
                failureCallback(exception)
            }
        })
}

fun verifyPasswordReset(password: String, verifyCode: String) {
    continuation?.apply {
        setPassword(password)
        setVerificationCode(verifyCode)
        continueTask()
    }
}

まとめ

cognito側の仕様にはあまり触れていませんが、これでさっくり使えます!

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

ダークモードにまつわる誤解を解いておく

Githubがダークモードに対応したのが昨年12月。調べて見るところ我々開発者には「目に優しい」と認識されている一方、一般層には単に「格好いいモード」と思われていたりなようです。更にそれだけでもないという記事を見つけました。

概要

以下、流行ってるな~と思ったので、まとめました。「ダークモード」のメリット、デメリット。本当のところ。GitHubをダークモードで使う
image.png
ダークモードに対応したデザイン は2020年始めからトレンドとして指摘されていました。
Redmineでダークモードを使う なんてこともできるようだ。

ダークモードのメリット (とされているもの)

  • 画面全体の輝度が下がるので目の負担が減る、疲れにくい。
  • 特に暗い環境ではコントラストをあげられるので、見やすい。
  • 有機ELパネルのディスプレイスマホなら消費電力が下がって電池が長持ちする。
  • 「格好いい」感じがするとよく言われる。

image.png
引用: https://blog.weekdone.com/why-you-should-switch-on-dark-mode/

デメリット (とされているもの)

  • スマホでは、未対応のアプリと画面の明るさの差が大きくなる。
  • 細かく配慮されていない画面ではアイコンやボタンが見にくいものがある。
  • 夜中にスマホの光を懐中電灯代わりにできない(?)。

実際は

「ダークモード」は、本当に“目に優しい”のか? 5つの観点から科学的に検証した結果 が詳しい。以下サマリ。

目の疲れを軽減する?

「確かな証拠を、わたしはまだ見たことがありません」。最善の解決策は、コンピューターの利用を控えることである。

集中力が高まる?

疑問あり。実験参加者に視力検査と校正作業を行ってもらったところ、白い背景に黒い文字のほうが読む速度が速く、見つけた誤字脱字の数も多かった。またTwitterは、ダークモードを有効にしているユーザーのほうが、アプリに費やす時間が長くなることを発見した。だが、これは集中力が増すためではなく、ベッドに入ったまま画面をスクロールするには、ダークモードのほうが不快感が少ないからかもしれない。

「ダークモードは集中力を高め、わたしたちの気持ちを落ち着かせると謳っているアプリはたくさんある。しかし、このモードを搭載する本当の動機は、単にユーザーの利用時間を長くできるからなのかもしれない。」

就寝前はダークモード?

No. 最も効果の高い方法は、眠りにつく1~2時間前には画面を見るのを完全にやめることである。

理想形

文献は記事最後に記載してあります。ダークモードがどこでも何でも良しということはなく、格好いいとか、好みの問題でもないようです。スマホをダークモードにする欠点|逆に目疲れの原因に?、と、子どもには逆に有害とすらする記事も。

例えば、明るい部屋や直射日光の当たる場所では、暗い背景に置かれたテキストが読みづらくなることもある。画面を専ら明るい昼間の屋外で使用したり、屋内・屋外を頻繁に行き来するユースケースでは、例外的な配慮が必要といえる。例えば日没、日の出時刻、自動検知による自動切り替え機能が必要になる。

「バッテリー寿命を延ばす?」という説については、True。
有機ELディスプレイを採用したAndroidのスマートフォンをダークモードにして「Google マップ」のスクリーンショットを表示したところ、消費電力は通常モードより63パーセント少なかったという。(「ダークモード」は、本当に“目に優しい”のか? 5つの観点から科学的に検証した結果 )

目には、休息が一番。このような小話ばかり読んでいないで休むときにはしっかり休みましょう。

ダークモードを搭載する例

OS

Windows 10
Mac OS
Android
IOS

ブラウザ

Google Chrome
Mozilla Firefox

チャットアプリ

Facebook Messenger
Whatsapp
Skype
Slack

SNS

Twitter
facebook

ほか

VSCode
IntelliJ
Eclipse
Github
Todoist
Trello

参考

ダークモードのメリット、デメリットはなんですか?
2020年流行するであろうwebデザインのトレンド
The pros and cons of Dark Mode: Here's when to use it and why
The Benefits of Dark Mode: Why should you turn off the lights?
「ダークモード」は、本当に“目に優しい”のか? 5つの観点から科学的に検証した結果
スマホをダークモードにする欠点|逆に目疲れの原因に?

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