日々是好日

プログラミングについてのあれこれ、ムダ知識など

AndroidでMiAuth実装してみた

リポジトリ

AndroidでMiAuthを実装しました。

手っ取り早くソース寄こせって人はこちらから。 developmentブランチです。

あと、かなりクラスを行ったり来たりするので、コード表示しながら読んでいただければ幸いです。

github.com

Misskey Hub

解説では明示されていませんが、カスタムスキーマは現在受け付けておらず、httpsに統一しているそうです(しゅいろママが言ってた)。

misskey-hub.net

トークン取得フロー

MiAuthは、次のようなイメージでアクセストークンを取得します。 OAuth2は未実装なので、詳細については言及しません。

さらに詳細を文字起こしにするとこんな感じ。

(一応MiAuth想定のフロー)
認証開始
 -> ViewModel#startAuth
 -> AuthUseCase#startAuth
 -> ユーザによりアプリ連携が許可される
 -> Activity#onNewIntentでコールバックURLのキャッチ
 -> RegisterAppCallback#onRegistered で受信する
 -> Interactor#requestToken
 -> トークンが返ってくる(成功の場合)
 -> ViewModel で実装し Interactor に渡されている AuthResultCallback を叩いてトークンを ViewModel に渡す
 -> ViewModel から PreferenceUseCase 等を通じてトークンを保存する

インターフェース定義

AuthActivityAuthActivityViewModelは認証フローの詳細を知らず、認証サーバとのやりとりはすべてAuthUseCaseを実装した各種インタラクタが実行します。 ViewModel はほとんどの場合、Activityからのイベントをインタラクタに通知するだけです。

また、MVVM を採用しているので、ViewModel は各種 UseCase を集約しています。

AuthUseCase

MiAuth以外にも対応できるようにsealed interfaceで定義しました。 sealed interfaceとすることで、AuthUseCaseを次のようにobject句で実装しようとしてもエラーになります。

また、どんな認証フローでもstartAuthを叩くことでフローを開始するように縛ります。

fun foo() {
  val XxxAuthInteractor = object : AuthUseCase {
    ...
  }
}
//-> This type is sealed, so it can be inherited by only its own nested classes or objects

AuthUseCaseは次のとおり定義します。

sealed interface AuthUseCase {
  fun startAuth(context: Context, ticket: AuthTicket, resultCallback: AuthResultCallback): RegisterAppCallback
  fun onDismiss()
  interface MiAuthUseCase: AuthUseCase
  interface OAuth2UseCase: AuthUseCase
}

//ユーザがアプリ連携を許可した際に必要なコールバック
//フローにActivity#onNewIntentが絡むため、何かしらの形でインタラクタに通知する仕組みが必要
interface RegisterAppCallback {
  suspend fun onRegistered(callbackIntent: Intent)
  suspend fun onFailed(err: Exception)
}

//トークンをリクエストした際のサーバのレスポンス
//取得に成功した場合、tokenが格納される
sealed interface AuthResultCallback {
  fun onAuthFinish(ticket: AuthTicket, token: Token)
  fun onFailed(err: Exception)
  interface MiAuthResultCallback: AuthResultCallback
  interface OAuth2ResultCallback: AuthResultCallback
}

AuthTicketは各種パーミッションやホスト情報等をまとめた単なるデータクラスです。 こちらもsealedで定義します。

github.com

MiAuthInteractor と OAuth2Interactor

AuthUseCaseを実装した具象クラス。 このように認証方式ごとにAuthUseCaseを実装したクラスを作ることで、startAuthを叩くだけで異なる認証フローを起動できるようにしています(たぶん)。

また、RegisnterAppCallbackInterector#startAuth にて、AuthResultCallback は ViewModel にて実装しています。

MiAuthInteractor github.com

AuthActivityViewModel github.com

雑なまとめ

  • 一部甘い部分(クラス変数にしている部分とか)があるけどそこはスルーで。
  • 別にMiAuthでトークン取得するテストなだけならここまでやる必要はない。。。

まとメモ プライバシーポリシー

まとメモ プライバシーポリシー

三者に個人を特定できる情報を提供することはありません。

個人情報の管理には最新の注意を払い、以下に掲げた通りに扱います。

サポート時

サポートメールに、問題解決のための端末種類、OSバージョン等が本文として記述されます。

個人を特定できる情報は一切送信されません。

データ解析

アプリの利便性向上のため、匿名で個人を特定できない範囲で最新の注意を払い、アクセス解析をしております。

例えばアプリのクラッシュ時にどんな原因でクラッシュしたかを匿名で送信して、バグの素早い修正に役立たせております。

免責事項

利用上の不具合・不都合に対して可能な限りサポートを行っておりますが、利用者が本アプリを利用して生じた損害に関して、開発元は責任を負わないものとします。

ICL手術レポ

眼内コンタクトレンズ(ICL: Implantable Collamer Lens)手術を受けてきたのでレポです。

  • ICLとは
  • 手術前の視力や費用、モチベーションなど
  • 今現在の状況
  • 手術前後の流れと経過
    • 手術前検査(2021年12月11日)
    • 手術当日(1日目)(2021年12月29日)
      • 手術前
      • 手術
      • 手術後
      • 帰宅後
    • 翌日検診(2日目)
    • 3日目
    • 4日目
    • 5日目
    • 6日目
    • 7日目(2022年1月4日)
    • 8日目
    • 9日目
    • 10日目
    • 1週間検診(11日目)
    • 12日目
    • 13日目(現在)
  • まとめ
続きを読む

特定のIdを含む全経路を取得する with 閉包テーブル

閉包テーブルにて「特定のIdを含む根~葉までの全経路」を取得しようとしたら、意外とめんどうだったのでメモ。

最適化全然出来ない。

  • サンプルデータ
  • クエリ
    • 分解
      • 部分木の葉を取得する
      • 全経路を取得する
  • 参考
続きを読む

Android Room における隣接リストから閉包テーブルへのMigration

隣接リストにて作成してしまった木構造のデータを、深さ付きの閉包テーブルにマイグレーションしたのでそのときの備忘録です。

そもそもの木構造の表現方法には触れず、あくまでMigrationの手順についてのみ記載しています。

  • モチベーション
  • サンプルデータ
  • 実装
    • 下準備
      • スキーマをエクスポートする
      • テストメソッドを作成
    • テストを実装する
      • テスト用データベースの初期化
      • Migrations.migrate1To2の実装
      • 実行結果の確認
    • 本番環境への移植
    • 所感
  • 参考としたページ
続きを読む

CoordinatorLayout+BottomNavigationView+NestedScrollView+その他諸々で画面構築してみた

次のぎじゅつを使って画面構築してみたのでメモ

  • 見た目はToolbar+コンテンツ表示Fragment+BottomNavigationViewの画面構成
  • コンテンツ表示Fragmentは、BottomNavigationViewの選択により入れ替える
  • Toolbarはコンテンツ表示Fragmentのスクロール動作により隠れるようにする
  • Toolbarのタイトルを、Navigationを使ってBottomNavigationViewに連動させる
  • BottomNavigationViewは固定する

BottomNavigationView & Toolbar & Navigation 間の連携に微妙なコツがあったり、 そもFrameLayoutであることを考慮したり、いろいろハマりまくった。

スクロールでBottomNavigationViewも隠したい場合は、カスタムBehavior定義して設定してやればおけ。 個人的には画面切り替えにスクロール動作が増えてしまうので、あえて固定する方法をとった。

  • 動作プレビュー
  • レイアウトXML
  • 実装編
    • NavigationとToolbar/BottomNavigationViewの連携の実装
      • コンテンツのレイアウトを作成する
      • BottomNavigationViewのMenuを作成する
      • Navigation graphを作成する
      • FragmentContainerViewにnav_graphをセット
    • スクロール時にToolbarを隠す動作の実装
      • 基本となる画面構造
    • NestedScrollView内にRecyclerViewを配置する場合の注意
  • 参考
続きを読む

時刻をパラメータに使用する場合の実装メモ

時刻を扱う場合に、テスト性を持たせるための実装メモ。

インターフェースを介して時刻を実装してやる。

// Kotlin
import java.util.*

interface SystemClock {
  fun getTimeMillis(): Long
  fun getLocale(): Locale
  fun getTimeZone(): TimeZone
}

// 使う側の例
import your.package.SystemClock

fun setTime(d: SystemClock) { ... }

setTime(object : SystemClock {
  override fun getTimeMillis(): Long {
    TODO("Not yet implemented")
  }

  override fun getLocale(): Locale {
    TODO("Not yet implemented")
  }

  override fun getTimeZone(): TimeZone {
    TODO("Not yet implemented")
  }
}

テストのときはエッジケースや定数を実装し、本番ではDate()等を実装すれば( ΦωΦ)σヨシッ!