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

Dagger Hilt: Deep Diveのメモ

近々Dagger Hiltについて話す機会があるので、少し調べています。
また、最近のGoogleの動画からいろいろインプットしていっています。
Deep Diveですが初心者にも分かりやすいのではと思いました。1時間半ある動画でしたが、かなり気になるところを色々話してくれています。個人的にDaggerの説明でよく使われるCoffeeShopの例よりも100倍分かりやすい例えで説明してくれているので、ぜひ見てみてください!
https://www.youtube.com/watch?v=4di2TTqeCrE

なぜDependency Injection?

データーベースなどがハードコードされている例

class MusicPlayer {
  private val db = SQLiteDatabase()
  private val codecs = listOf(CodecH264(), CodecFLAC())
  fun play(id: String) { ... }
}
fun main() {
  val player = MusicPlayer()
  player.play("...")
}

コンストラクタを呼び出して、play()を呼ぶだけ。シンプル。依存関係もハードコードされているので何もしなくて良い。

ただ、たくさんの問題が起こる。
MusicPlayerを再度使いたい場合、データベースがハードコードされているので別のMusicPlayerでは使えなかったり、違うコーデックが使えなかったりする。
またそれらを実行時や他のなにかでも交換できない。
テストも問題になる。データーベースがハードコードされているので、例えばメモリ内のDB(In memory database)を使いたい場合は不可能となる。
またこれはさまざまな依存関係やクラスの動作を一目で確認できないため、リファクタリングを難しくする。

Construction InjectionによるDependency Injectionの例

コンストラクタで依存を受け取るためConstruction Injectionと呼ばれる。setterで渡すsetter injectionもある。

class MusicPlayer (
  private val db: Database,
  private val codecs: List<Codec>
)
  fun play(id: String) { ... }
}

上記の場合、初期化時に明示的に依存関係が書かれているので、以下のように先に他のオブジェクトを作る必要がある。
このようになにかオブジェクトを作るときにはその依存関係を作る必要がある。

fun main() {
  private val db = SQLiteDatabase()
  private val codecs = listOf(CodecH264(), CodecFLAC())
  val player = MusicPlayer(db, codecs)
  player.play("...")
}

ただ、このようなMusicPlayerをなんども手動で依存関係も作る場合はボイラープレートとなりえる。

コンストラクションロジックとは = ただ、他の型やクラスを作るためのロジック
ビジネスロジックとは = アプリケーションの価値を作るもの

  • ビジネスロジックからコンストラクションロジックを分離するべき。
    コードを追ったり、読んだりするのを難しくする。またそれはクラスを読む人にとってあまり意味のないものである。
  • Dependency Injection(DI)では、DIコンテナがこの役割。
    DIではコンストラクションロジックはビジネスロジックから分離される。
    separation of concerns(関心事の分離)やsingle responsibility principle(単一責任の原則)があるが、この責任のあるクラスがDIコンテナとなる。

DIについてまとめ

  • 良い利益があるので、GoogleはDependency Injectionを推奨
  • ボイラープレートを避けるためにDIライブラリが推奨される。

なぜAndroidではDIが難しいのか?

FrarmeworkのクラスがAndroidのSDKの中で作られてしまうため。
API 28ではFactoryが追加されているが、現実的ではない。これがDaggerやHiltをおすすめする理由。

Daggerについて

去年はDaggerをおすすめしたが、今はHiltがおすすめ。
Daggerはおすすめしたが、もっといい解決策を探した。Daggerは設定が難しい。またDaggerでは同じことをする方法が複数存在する。
49%の開発者が、より良いDIの方法を探していた。

どのような解決策を望んでいたか?

  • Opinionated(意見を持った。)
    決めるのを楽にして、使うのを楽にする (後述)
  • セットアップが簡単(DaggerはAndroidではなく、Java用だったので、Daggerは設定が難しかった)
  • 重要な部分にフォーカスできる

これがメインのHiltが生まれた理由。

Dagger Hilt

Dagger HiltはDaggerの上に構築されたライブラリ。
Daggerの良いところを使うことができる。またAndroidXチームとDaggerチームで共同で作られている。

  • AndroidにおけるDIの標準化
    どのようにAndroidでDIするのかを標準化(standardize)する。
  • Daggerの上に構築されている。
  • アノテーションベース
    Hiltに対して、アノテーションで何をしたいのかを伝える。
  • ツールサポート
    Android Studio 4.1以降では左側にガターアイコンが表示される。例えば依存関係がどこから来たのかがわかる。
  • AndroidX Extension
    ViewModelとWorkManagerで使える。他のライブラリも追加予定。

Setup

どうやってMusicPlayerを組み立て方をHiltに伝えるのか?
コンストラクタを変更できるのであれば、以下のように @Injectをコンストラクタにつける。これが基本的なHiltへこのオブジェクトの作り方の教え方となる。このクラスの依存関係は、以下のコードでは現状はなにもない。何も依存がないため、Hiltはこのクラスを明らかに作ることができる。

class MusicPlayer @Inject constructor() {
  fun play(id: String) { ... }
}

そして、別のアノテーション @HiltAndroidAppはApplicationクラスにつける。これはHiltとAnnotion Processorに"このアプリはHiltを使うぞ"と教える。そして、これはdependency injection container(DIコンテナ)をアプリケーションクラスに追加する。DIコンテナはどのようにオブジェクトを作るのかのロジックとインスタンスを持っている。

@HiltAndroidApp
class MusicApp : Application()

もしHiltをActivityで使いたい場合。
Activityではコンストラクタにアクセスできないということを言ったが、そのため@AndroidEntryPointをActivityにつける。
これによって、3つHiltに教える。

  • "このActivityはInjectionを行う。このActivityはDependencyInjectionを使う"
  • "別のdependency contaienrをこのActivityに追加。"
@AndroidEntryPoint
class PlayActivity : AppCompatActiivty() {
  • HiltからDependencyを持ってくる
    変数に@Injectアノテーションを付けている。これはHiltからInjectされることを意味する。
    "Actiivtyが作られたときに、MusicPlayerをInjectして" と言っていることになる。
    そして、onCreateメソッドはMusicPlayerを呼ぶなど好きなことができる。
@AndroidEntryPoint
class PlayActivity : AppCompatActiivty() {
  @Inject lateinit var player: MusicPlayer

  override fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    player.play("...")
  }

もう少し複雑な例

MusicPlayerがMusicDatabaseに依存しているとする。
Hiltが、MusicPlayerを作るためには、どのようにMusicDatabaseを作ることができるのかを知っている必要がある。
なぜならMusicDatabaseはMusicPlayerの依存となるため。

class MusicPlayer @Inject constructor(
  private val db: MusicDatabase
) {
  fun play(id: String) { ... }
}
  • どのようにHiltにMusicDatabaseを作るかを教えるか?

→ もしMusicDatabaseのコンストラクタを変えられるのであれば、MusicPlayerと同じように コンストラクタで@Injectを使うことで解決できる。

しかしこのDatabaseがJetpackライブラリのRoomから作られたりなど別のところから来る場合はどうだろうか?
Roomの場合はコンストラクタにアクセスすることができず、@Injectアノテーションを付けることができない。

→ この場合はモジュールを使う必要がある。モジュールはただのクラスで、@Moduleアノテーションを付ける。Installingと呼ばれるアノテーションを付ける。
モジュールにはメソッドを追加する。
モジュールのメソッドはレシピだと考えれば分かりやすい、この型をビルドするレシピはこれ!みたいな。基本的にこれはこのモジュールでレシピをHiltに教えている。
そして、この情報をApplicationComponentに置く(= Application LevelのDependency Containerに置く)

@Module
@InstallIn(ApplicationComponent::class)
object DataModule {
...
}

@Providesがついたメソッドが書いてある。ここではHiltにどのようにMusicDatabaseを作るのかを教えている。
これによってHiltがDatabaseの作り方を知る必要があるときに、レシピの情報がここにあるので、ここに来て、このコードを実行して、依存関係を提供する。

@Module
@InstallIn(ApplicationComponent::class)
object DataModule {
  @Provides
  fun provideMusicDB(@ApplicationContext context: Context) =
    Room.databaseBuilder(context, MusicDatabase::class.java, "music.db")
      .build()
}

Component

ApplicationComponentなどが登場しましたが、これについてもう少し見てみよう。
Application ComponentなどはDIコンテナとして考えられる。
説明したようにこのコンテナはどのようにオブジェクトをビルドするのかのロジックをハンドルする。
例えばMusicPlayerにはMusicDatabaseが必要になるなど、Componentはインスタンスの生成の順序などのロジックを持っている。

これがどのように内部での仕組みになっているのか?
たくさんのファクトリーパターンに従ったファクトリーがある。そのファクトリーはどのようにインスタンスを作るかを知っている。
例えば、MusicDatabaseのファクトリーを持っていて、他にMusicPlayerのファクトリーもある。そして、それをつなげて一緒に動くようにする。
このファクトリーはHiltによって明らかに小さくなった。そしてファクトリーが必要になるまでは使われない最適化もされる。また、スコープなどによってインスタンスを使い回すロジックもある。

HiltはなぜOpinionedなのか?

Dagger HiltにはComponent、DIコンテナが付随するため。
ApplicationComponentやConfigrationChangeでも生き残るActivityRetainedComponent、ActivityComponent、そしてFragmentComponentが付随する。
ApplicationやActivityはフレームワークのコンポーネントなので、ライフサイクルがある。そしてApplicationComponentはApplicationクラスのライフサイクルに従う。AcitivtyComponentはActivityのライフサイクルに従う。
そしてサービスやViewといった特定の部分のComponentもある。

スコープ

(端折り気味)
現状、MusicDatabaseのインスタンスがInjectされるたびに、ModuleのprovideMusicDBによって配布されてしまうため、2つActivityがあって両方でMusicDBがInjectされていた場合、合別々のインスタンスが保持されてしまう。しかしConnectionを共有したかったり、同期だったりで一つのインスタンスを共有したかったりする。

@Module
@InstallIn(ApplicationComponent::class)
object DataModule {
  @Provides
  fun provideMusicDB(@ApplicationContext context: Context) =
    Room.databaseBuilder(context, MusicDatabase::class.java, "music.db")
      .build()
}

これをどのようにHiltに伝えればよいのか?
@Singletonアノテーションを付ける必要がある。クラスか、コンストラクターか、モジュールの関数で使う。これがスコーピングアノテーションとなる。
これを使うことでインスタンスが共有される。このようなスコープのアノテーションは@Singletonの他に@ActivityRetained@ActivityScopedなどそれぞれのコンポーネントが違ったComponentを持っている。

@Module
@InstallIn(ApplicationComponent::class)
object DataModule {

  @Provides
  @Singleton // **←**
  fun provideMusicDB(@ApplicationContext context: Context) =
    Room.databaseBuilder(context, MusicDatabase::class.java, "music.db")
      .build()
}

Entry Point

Hiltに寄って作られた依存関係とHiltの依存関係以外の部分から取得するのは直接Hiltではサポートされていない。
そういうところにEntry Pointが使われる。
基本的に依存関係のグラフやDIコンテナににアクセスすることに使える。
例えば他のアプリとのコンテンツの共有に使われるContentProviderはApplicationのonCreateより先に呼ばれてしまったり、少しライフサイクルが特殊だったりするので、Hiltでサポートされていない。しかしContentProviderでMusicDatabaseを使いたい場合は存在する。MusicDatabaseはApplicationComponentから取得できることがわかっている。このような場合にEntryPointが使われる。
EntryPointはただのinterfaceでEntryPointのアノテーションがついており、どこから情報を取得するかをInstallInで指定する。
基本的にHiltはfun getMusicDatabase(): MusicDabaseに対してコードを生成してくれる。

class PlaylistContentProvider: ContentProvider() {
  @EntryPoint
  @InstallIn(ApplicationComponent::class)
  interface PlaylistCPEntryPoint {
    fun getMusicDatabase(): MusicDabase
  }
}

そして実行時は、EntryPointAccessorsを使って、このサンプルではApplicationコンポーネントを使って、Interfaceのクラスを渡して、Hiltが生成したinterfaceの実装を取得できる。そしてgetMusicDatabaseを使ってHiltの中にある依存関係を取得できる。

class PlaylistContentProvider: ContentProvider() {
  @EntryPoint
  @InstallIn(ApplicationComponent::class)
  interface PlaylistCPEntryPoint {
    fun getMusicDatabase(): MusicDabase
  }

  override fun query(...) {
    val entryPoint = EntryPointAccessors.fromApplication(
      context,
      PlaylistCPEntryPoint::class.java
    )
    val database = entryPoint.getMusicDatabase()

    // ...
  }
}

質問コーナー

内部的にSubComponentかdependenciesかどちらが使われるか?

(DaggerはComponentの依存関係を作る方法が2パターンある)
SubComponentのほうが技術的に楽なので、SubComponentが使われている。
Dynamic Feature Moduleでは依存関係が逆になるこれまでDaggerではGoogleはdependant moduleを推奨していた。
Hiltを使っている場合も、そのDFMの部分はDaggerで書く必要がある。
https://developer.android.com/training/dependency-injection/hilt-multi-module?hl=ja

上記の問題を解決する予定ある?

Dynamic Feature Moduleとアノテーションプロセッサーが相性が良くない。他のライブラリでも同じ問題が起こっている。例えばSafeArgsやNavigation。メタデータをexportするなどを考えているが現状はできない。

1つのモジュールではなくモジュールを分ける必要ある?

スケールしたり、他のチームと一緒に仕事したり、関心事の分離など。

AndroidX Extension

Dagger HiltはActivityやFragmentと一緒に動くが、他のComponentとも動くようになっている。例えばViewModelやWorkManager。ViewModelを例に見ていく。
ViewModelではコンストラクタにアクセスできるが、ViewModelでは直接インスタンスを作らず、Provider、Factoryなどを通じてインスタンスを作成する。なぜならConfiguration changeを通じて生き残るためである。
HiltはこのためのProviderなどのコードを自動生成する。そのため、ただ一つの@ViewModelInjectアノテーションが必要となる。プロセスが死んだり、Activityの再生成でsaved state handleを使いたいとき、通常フレームワークが作るので持っていない。しかしHiltでは @Assistedアノテーションを付けることで何もする必要はなく必要なときに利用できる。

class PlayViewModel @ViewModelInject contructor(
  @Assisted val savedStateHandle: SavedStateHandle
  val db: MusicDatabase
) : ViewModel() {
...
}

ViewModelを使うときには @AndroidEntryPointがついたAndroidのコンポーネントでktx extensionを使っているとby viewModels()を使うことができ、それを自由に使うことができる。AndroidX extensionと簡単に連携ができる。

@AndroidEntryPoint
class PlayActivity: AppCompatActivity() {
  val viewModel: PlayViewModel by viewModels()
}

Testing

Hiltがどのようにテストをシンプルにするのか?
テストをするためにはPlayerを作る必要がある。そのためその依存関係のDatabaseを作る必要があり、そのときにはメモリに保持するDBがほしい。基本的にはそのインスタンスをただ作って、MusicPlayerに渡せばテストができる。なぜならこれでMusicPlayerのテストするためのインスタンスができるため。

@RunWith(AndroidJUnit4::class)
class MusicPlayerTest {
  @Test
  fun testPlay() {
    val db = Room.inMemoryDatabaseBuilder(...)
    val player = MusicPlayer(db)
    player.play("...")
    assertTrue(player.isPlaying())
  }

依存関係が1つしかないため、今はこれで大丈夫だが、依存関係が20個ある場合はどうだろうか?そしてそれぞれの依存関係が推移的に依存を持っている場合(依存が他のものに依存している)、それをすべて作る必要がある。
それは問題となりえ、依存関係を切るためにmockやfakeを作り始める。しかしそれには良し悪しがある。

Hiltは基本的にすべてのコンストラクションロジックを持っていて、テストでも持っているため、基本的に行うことができる。
HiltAndroidRuleを使って、コンポーネントの作成とそれらのコンポーネントの管理を行う。そしてActivityや他の場所と同じようにinjectを使うことができる。MusicPlayerをInjectできる。

@HiltAndroidTest
@RunWith(AndroidJunit4::class)
class MusicPlayerTest {

  @get:Rule
  val rule = HiltAndroidRule(this)

  @Inject
  lateinit var player: MusicPlayer

  @Before
  fun setup() {
    rule.inject(this)
  }

  @Test
  fun testPlay() {
    player.play("...")
    assertTrue(player.isPlaying())
  }
}

しかし、このままではInMemoryDatabaseに切り替えることができない。
そのため、HiltにDatabaseを作るレシピを忘れさせ、メモリのDBを使うようにする。
@UninstallModuleによってDataModuleの内容を忘れさせて、Databaseの作り方を忘れさせる。

@HiltAndroidTest
@UninstallModule(DataModule::class)
@RunWith(AndroidJunit4::class)
class MusicPlayerTest {

そしてここでMusicDatabaseの情報を教える必要があるので、テストの中でModuleを作る。このテストのための依存関係が追加できる。もしこのモジュールを外に持っていくとこれは、すべてのテストに適応される。

@HiltAndroidTest
@UninstallModule(DataModule::class)
@RunWith(AndroidJunit4::class)
class MusicPlayerTest {
  @Module
  @InstallIn(ApplicationComponent::class)
  object TestMoudle {
    @Provices
    fun provideDB(app: Application) =
      Room.inMemoryDatabaseBuilder(...).build()
  }

テストランナーなどの設定が必要なので、ドキュメントを確認すること。

マイグレーション

Dagger HiltはDaggerやDagger Androidとともに動く。
HiltはマイグレーションAPIが存在する。
マイグレーションガイドもある
https://dagger.dev/hilt/migration-guide

将来の変更

  • Android Gradle module以外でも動くように。
  • Assisted Inject
    view moduleにidを渡したいときなど
  • 他のAndroidXのコンポーネントとの連携 navigationなど

質問

stableになるには何が足りないのか?

いくつかがある。
Android Gradle Plugin外で動くようにしたり、Assited Injectionなどをstableにする前に入れたい。
プロダクションで準備はできている。なぜなら実際にGoogleのアプリでは使われているため。そしてオープンソースのアプリをhiltにマイグレーションしている。例えばsun flower、google io、tv from Chris Banesで使われている。

パフォーマンス、kaptを使わないといけないのでスピードが出ないのでは?

Hiltによる追加の時間は多くない。Hiltは基本的にレイヤーが薄く、Dagger上にあるため。
HiltはDaggerのためのコードを生成するが、生成するコードの量は多くはない。
kaptが遅いのは分かっていて、Daggerチームはannotation processorの代わりにksp(Kotlin Simbol Processing)を使う方法を探っている。しかし、それがいつできるかはわからない。
現在コードの生成にJavaPoetやそれに似たものを使っていて、同じ文法でkspとjavaコード両方を書くことができるようにしたいと考えている。ただJavaとKotlinを両方同時にサポートするため。これがHiltでkspを使う前にしたいことになる。

なぜHiltにGradle Pluginが必要なのか

Gradle Pluginは任意で、技術的ドキュメントを確認すればGradle Pluginなしで適応する方法が書いてある。@AndroidEntryPointアノテーションのためにある。
実際HiltはMusicPlayerActivityがあればHiltMusicPlayerActivityをバイトコードトランスフォーメーションで生成し、それをMusicPlayerActivityで継承するようにする。もし、Gradle Pluginを使わない場合はそのクラスを作る必要がある。

それってコンパイル時間かかる?
この処理ははやい

インクリメンタルビルドについては?
すでにincremental buildと一緒に動作する。

KoinやKodeinからマイグレートするべき?

KoinやKodeinは解決策で、Googleは何かを推薦するが、あなたが使いたいものを使うことができる。しかし、もし、KoinやKodeinでうまく言っていたときに他の人がプロジェクトに入ってきたときに、Hiltが標準の解決策なので、もしHiltであればプロジェクトを移動したときに同じ解決策でできるので新しい技術を学ぶ必要がない。しかしマイグレーションはあなたやチームによるので、これは必須ではない。

DaggerにHiltのための機能追加とかした?

していない。

なぜFragmentRetainedComponentがActivityRetainedComponentと同じようにないのか?

これは非常に複雑な質問。ダイヤモンド問題が起こる。
ActivityRetained <- Activity <- Fragment
のようになっていて、Fragment retained componentを追加したいが、Fragment retained componentはActivity retained componentやActivity comopnentを継承したいが、実際にはそこに循環が発生し、避けられない。
Fragment factoryを作るためにFragmentRetainedComponentを考えた。ダイヤモンドを作成し、リンクを切るというもの、しかし、現状ActivityComponentとActivityRetainedComponentなど複数の親を持てないのが問題になる。

square/anvilについて(同じようなことができるライブラリ)

Dagger Hiltは2つの部分からなる

  1. @InstallInアノテーションによるモジュールのAuto discoverabillity
  2. @AndroidEntryPoint コンポーネントを自分で作る必要がなく、自動的にInjectされる

amvilでは1つ目のみを行う。
チームもこの2つのアーティファクトに分離することは考えている。

Hiltは大きなアプリでも使えるか?

実際Googleのアプリは大きいがHiltを使っている。DaggerとHiltはスケールする。Dagger Hiltで対応するコンポーネントがあるため対応もかんたん。

@ViewModelInjectみたいにViewModelのInjectはできるが、FragmentはFactory作れて、Construction Injectionできるけど、やらないの?

まだ実装されていない。ダイヤモンド問題がある。また他にも問題があり、Fragmentを作るときにFragment Factoryが必要になるが、これはonCreate前には使われない。そしてFragmentを作るときにFragmentのインスタンスを使うことができなくなる。
またActivityComponentにFragment Factoryを実装すると、Fragment FactoryはFragmentの情報を使えないが、Fragmentでは使えることになるので、混乱を生む。
Fragment Factoryとの連携のエッジケースが起こらない堅牢な方法を探している。

lateinitや@InjectアノテーションだとNullPointerException起こる可能性あるけど消すプランはある?

Kotlinでlateinitをとても使っている。たぶん今そのようなFeature requestがないので、それをfileし、property delegateなどの方法を考えることはできる。

HiltでFileにアノテーションを付けてtop level functionで依存関係を配布とかできたりする?

いいアイデアだと思う。Feature reqeustにfileしたほうが良さそう。Daggerは長い間Javaのライブラリとして存在しており昔のメンテナは他のプロジェクトに移っており新しい人々が貢献している。Kotlinと共によく動くようにしていこうとしている。

Android以外でどのぐらいDaggerって使われているの?
むずかしい。わからない。

Dagger 3.0みたいなの考えている?

この年動き始めるかも。個人的にHiltかDIをComposeに入れるために。
Jetpack Composeに今使えるAmbientはとてもサービスロケーターっぽい。そのため、どのように適切にDIを行うのか。Hiltをその中で使いたいと考えている。

ViewModelにidとか渡せる?

今はサードパーティのAssitedInjectで行う。sunflowerでもAssisted Injectを使っている。
今後なおしたいと考えている。

(省略)

Multibindingを楽にできない?

難しいけど、エスケープハッチのように拡張性を作ってくれる。

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

アプリ開発でSwagger定義があるならPrismを使おう

スマホアプリの開発でバックエンドの人がSwagger定義(Open API Specともいう)を使ってくれてたらPrism使ったら良いよ、というお話。

Prismとは

https://stoplight.io/open-source/prism/

  • Stoplightという会社が作っている。APIの開発をスムーズに進めていくためのツールを色々提供している。
  • Prismはそのツールのうちの1つで、いわゆるモックサーバー。OSSとして提供されている。
  • 別のサービスだと本家のSwagger UIもレスポンスをモックすることができるし、API Sproutなんかもある。

インストール&使い方

$ npm install -g @stoplight/prism-cli
$ prism mock swagger.yaml

これだけ。簡単。デフォルトで127.0.0.1:4010のポートを使い、読み込んでいたswagger定義ファイルを更新すると自動でリロードもしてくれる。(これがSwagger UIのモックサーバには無い)

レスポンスのカスタマイズ

応答をさせたいSwagger定義にexampleを書くとそれを下にレスポンスを返してくれる。
わからなくなったら本家のDocを読むべし。
https://swagger.io/docs/specification/adding-examples/

方法1: 個別にexampleを設定

(略)
components:
  schemas:
    Pet:
      allOf:
        - $ref: '#/components/schemas/NewPet'
        - type: object
          required:
          - id
          properties:
            id:
              type: integer
              format: int64
              example: 123  # <-- これ

    NewPet:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          example: foo  # <-- これ
        tag:
          type: string
          example: bar  # <-- これ

方法2: まとめてexampleを設定

(略)
components:
  schemas:
    Pet:
      example:  # <-- これ
        id: 999
        name: hoge
        tag: moge
      allOf:
        - $ref: '#/components/schemas/NewPet'
        - type: object
          required:
          - id
          properties:
            id:
              type: integer
              format: int64

    NewPet:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        tag:
          type: string

注意点

方法1、2両方設定するとどうなる?

Prismの場合は方法2の定義が優先されます。

レスポンスの定義に違反するexampleの値はエラーになる?

Prismの場合はエラーになりません。気をつけましょう。

余談

最初はAPI Sproutを使おうとしていたところ、$refを使っている定義を上手くハンドリングできないバグがあるようで、Githubのissueが1年以上放置されているので諦めました。
https://github.com/danielgtaylor/apisprout/issues/40

以上、誰かの役に立てば幸いです。

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

10. 【Android/Kotlin】Toast

はじめに

DreamHanksのMOONです。

前回は動的にViewを追加する方法について説明しました。
9. 【Android/Kotlin】動的にViewを追加

今回はライブラリを追加する方法について説明していきます。

ライブラリを追加する方法

Android Studioにはライブラリを追加する方法が三つがあります。
 1.jarファイルをlibsフォルダに貼り付ける
 2.Project Structureを利用する
 3.gradleに直接経路を書き込む

今回はライブラリ一つを上記の方法で追加していきます。

1.jarファイルをlibsフォルダに貼り付ける

2.PNG
①jarファイルをプロジェクトの「app/libs」パース以下に入れます。

3.png
②入れたライブラリを右クリックし、「Add As Library」をクリックします。

4.PNG
③「OK」ボタンをクリックします。

5.PNG
④「③」の作業が終わったら、build.gradleファイルの「dependencies」に写真の青色のように入られます。

⑤以降からはライブラリを使用することができます。

2.Project Structureを利用する

6.png
①Android Studioで「File⇒Project Structure」をクリックします。

7.png
②「Modules」カテゴリーをクリックし、「+」ボタンをクリックします。

8.PNG
③項目中に「Import .JAR/.AAR Package」をクリックして「Next」をクリックします。

9.PNG
④ライブラリのパースを選択し、Subproject nameを作成します。

10.PNG
⑤ライブラリのModuleが追加されたことを確認し、OKボタンをクリックします。

12.PNG
⑥追加したModuleに対するbuild.gradleファイルが生成されたことを確認します。

⑦以降からはライブラリを使用することができます。

3.gradleに直接パースを書き込む

11.png
①build.gradleファイル内のdependenciesにgradleに対するライブラリのパースを作成します。
②「Sync Now」ボタンをクリックしてビルドします。
③以降からはライブラリを使用することができます。

この三つの方法中に「3.gradleに直接パースを書き込む」が一番便利な方法です。

終わりに

今回はライブラリを追加する方法について説明しました。

次回はToastという通知メッセージについて説明していきます。
11. 【Android/Kotlin】Toast

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

10. 【Android/Kotlin】ライブラリを追加

はじめに

DreamHanksのMOONです。

前回は動的にViewを追加する方法について説明しました。
9. 【Android/Kotlin】動的にViewを追加

今回はライブラリを追加する方法について説明していきます。

ライブラリを追加する方法

Android Studioにはライブラリを追加する方法が三つがあります。
 1.jarファイルをlibsフォルダに貼り付ける
 2.Project Structureを利用する
 3.gradleに直接経路を書き込む

今回はライブラリ一つを上記の方法で追加していきます。

1.jarファイルをlibsフォルダに貼り付ける

2.PNG
①jarファイルをプロジェクトの「app/libs」パース以下に入れます。

3.png
②入れたライブラリを右クリックし、「Add As Library」をクリックします。

4.PNG
③「OK」ボタンをクリックします。

5.PNG
④「③」の作業が終わったら、build.gradleファイルの「dependencies」に写真の青色のように入られます。

⑤以降からはライブラリを使用することができます。

2.Project Structureを利用する

6.png
①Android Studioで「File⇒Project Structure」をクリックします。

7.png
②「Modules」カテゴリーをクリックし、「+」ボタンをクリックします。

8.PNG
③項目中に「Import .JAR/.AAR Package」をクリックして「Next」をクリックします。

9.PNG
④ライブラリのパースを選択し、Subproject nameを作成します。

10.PNG
⑤ライブラリのModuleが追加されたことを確認し、OKボタンをクリックします。

12.PNG
⑥追加したModuleに対するbuild.gradleファイルが生成されたことを確認します。

⑦以降からはライブラリを使用することができます。

3.gradleに直接パースを書き込む

11.png
①build.gradleファイル内のdependenciesにgradleに対するライブラリのパースを作成します。
②「Sync Now」ボタンをクリックしてビルドします。
③以降からはライブラリを使用することができます。

この三つの方法中に「3.gradleに直接パースを書き込む」が一番便利な方法です。

終わりに

今回はライブラリを追加する方法について説明しました。

次回はToastという通知メッセージについて説明していきます。
11. 【Android/Kotlin】Toast

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

[Android/Kotlin]コードからtextViewに取り消し線を付ける

結論

kotlin
textView.paint.flags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG // 打ち消し線
xml
<TextView
    android:id="@+id/textView"/>

textView.paint.flags = Paint.STRIKE_THRU_TEXT_FLAGだけだとアンチエイリアスが切れてギザギザになる

アンチエイリアスは別途指定し直しても良さそう

kotlin
textView.paint.flags = Paint.STRIKE_THRU_TEXT_FLAG // 打ち消し線
textView.paint.isAntiAlias = true // アンチエイリアス

paint.flagspaintFlagsでもいいらしい

kotlin
textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG // 打ち消し線

その他

STRIKE_THRU_TEXT_FLAGのところ変えれば下線とかもできる

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

[Android/Kotlin]コードからtextViewに打ち消し線を付ける

結論

kotlin
textView.paint.flags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG // 打ち消し線
xml
<TextView
    android:id="@+id/textView"/>

textView.paint.flags = Paint.STRIKE_THRU_TEXT_FLAGだけだとアンチエイリアスが切れてギザギザになる

kotlin
textView.paint.flags = Paint.STRIKE_THRU_TEXT_FLAG // 打ち消し線
textView.paint.isAntiAlias = true // アンチエイリアス

とかでも良さそう

その他

STRIKE_THRU_TEXT_FLAGのところ変えれば下線とかもできる

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

[Android/Kotlin]コードから背景色を変更する

結論

val view: View = findViewById(R.id.constraint)
val backgroundColor = getDrawable(R.color.colorAccent)
view.background = backgroundColor

全体

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

        val constraint: View = findViewById(R.id.constraint)
        val backgroundColor = getDrawable(R.color.colorAccent)
        constraint.background = backgroundColor

    }
}
activity_main
<?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:id="@+id/constraint"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        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" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プロキシツールmitmproxyで、httprequest・httpresponseの内容をチェックする

ソフトウェア開発でhttpリクエストを送ることはよくあると思うが、その中身をチェック・デバッグするにはどうするか。

もちろん開発しているツールで中身を見てもいいし、ログを仕込んでもいいと思うが、場合によっては少し手間がかかったりもするしリアルタイムで把握しづらかったりもする。

通信の間にプロキシを介在させればHttpリクエスト、レスポンスの中身がプロクシにより取得できるので便利である。

色々とツールはあるが無料で機能が豊富なmitmproxyを使う。これをmac上で動かし、別のiOSデバイス(iPhoneなど)からインターネットへのアクセスを仲介してみる。実際に試してみたが、手軽に素早くリクエスト・レスポンスをチェックできるため、デバッグの速度が上がり何よりストレスが減った。1

他のツールを探したい場合 -> アプリのAPIリクエストのトレースはどうするのが効率的か?

インストール

Homebrewというツールを使いインストールする。

brew install mitmproxy

mac以外のデバイス上で動かすときは-> Installation - mitmproxy

起動

ターミナルで以下のコマンドを叩くと、mitmproxyが起動する。

mitmproxy

後で使うので、macのプライベートネットワーク内のIPアドレスを調べておく。

ifconfig

以上のように入力すると、出力のen=0という欄にipアドレスが記載されている。例えば、192.168.100.107など。詳細

iOSデバイス側

「設定」アプリを開く。Wi-Fi > 現在使用しているWi-fiの横にある「i」ボタン > HTTPプロクシ > 手動

「サーバー」の欄に、macのipアドレスを入力する。
「ポート番号」の欄には、mitmproxyのポート番号(特に何も指定しなければ、デフォルトでは8080)を入力する。

次にiOSデバイスにmitmproxyの証明書をDLする。

Safariからhttp://mitm.itにアクセスし、Appleのりんごのマークをタップする。
「設定」アプリを開き、一般 > プロビジョニング でmitmproxyのプロファイルが追加されているはずなので、タップして認証する。

以降、iOSデバイス側からのHTTPリクエスト、およびiOSデバイスが受け取るHTTPレスポンスがmitmproxyに一覧表示される。

なお、App storeからアプリをダウンロードするにはプロキシがあるとできないようなので、その前にプロクシの設定を一度解除する必要がある。

各リクエストの詳細

クリックすると各リクエストの詳細が見れる。

「q」キーで一覧画面に戻る。

「F」キーで最新のリクエストを自動で追いかける。

フィルタリング

「f」キーで、一定の文字列でフィルタリングを行う。

保存

「b」キーで、リクエスト・レスポンスなどをファイルに保存できる。

https://mondai-to-kotae.hatenablog.com/entry/2019/12/01/155413

参考

mitmproxy docs
モバイルアプリ開発者のための mitmproxy 入門
iOS実機のSSL通信をプロキシによって傍受したり改ざんする方法

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

【Android】ContentResolverのinsertがnullになる場合

ContentResolverのinsertがnullになる

そのファイルが存在しているのにinsertをかけようとすると返り値がnullになります。

val values = ContentValues().apply{
    put(MediaStore.Images.Media.DISPLAY_NAME, "hoge.jpg")
    put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/fuga")
    put(MediaStore.Images.Media.MIME_TYPE, "image/jpg")
}
val uri = contentResolver.insert(
    MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
    values
)
Log.d("log",uri.toString())
---
2020-09-19 22:23:48.688 29801-29801/com.example.sample D/log: content://media/external_primary/images/media/7173
2020-09-19 22:23:50.858 29801-29801/com.example.sample D/log: null

idを取得してupdateする

insertしてnullだった場合、そのファイルを検索してidを取得し、そこからUriを作成してupdateなりdeleteなりよしなにすればOKです。
1. query
2. Uri.withAppendedPath
3. update

contentResolver.query(
  MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
  null,
  "${MediaStore.Images.Media.DISPLAY_NAME}=?",
  arrayOf("hoge.jpg"),
  null)?.use{
    if(it.moveToNext()){
      val id = it.getColumnIndexOrThrow(BaseColumns._ID)
      val uri = Uri.withAppendedPath(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        id.toString()
      )
    }
}

実ファイルが存在しないのにinsertがnullになる場合

なんらかの契機でたまに発生します。MediaStoreのdeleteではなく、ファイルマネージャ等から削除するとか・・・?
MediaStoreのDBにおそらくレコードが残ってしまっていて、実際の状態と不整合を起こしています。

AndroidはMediaScannerがなんらかのタイミングで走り、保存されたファイルをMediaStoreのDBに登録しています。(=フォトから見れるようになる)
なので、こうなってしまった場合は端末を再起動するか、しばらく時間を置くと自然と治っている事が多いです。

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