watabee's blog

プログラミング関連のブログです

【Android】UnitTestでLiveDataから値を取得する

UnitTest で LiveData から値を取得するために、iosched のプロジェクトでは以下のリンクのように LiveDataTestUtil を実装しているようです。

github.com

これを Kotlin Coroutines で書き直してみました。

gist.github.com

これで以下のような形でテストを書くことができます。

class MyViewModel : ViewModel() {
    val isLoading = MutableLiveData(false)

    ...
}

class MyViewModelTest {

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    @Test
    fun testSample() = runBlocking {
        val viewModel = MyViewModel()
        Truth.assertThat(viewModel.isLoading.awaitValue).isFalse()
    }
}

【Android】ViewPagerでページ切り替え時にFragmentのonResumeが呼ばれるようになった!

androidx.fragment の 1.1.0-alpha07 から ViewPager のページを切り替えたタイミングで Fragment の onResume() が呼ばれるようになりました🎉

ViewPager ではとあるページを表示した際にデフォルトで両隣のページの Fragment も自動的に生成されるため、今までは画面に Fragment が表示されていないのにも関わらず隣のページの Fragment の onResume() が呼ばれてしまっていました。

個人的にはこの機能は欲しかったやつで、Google Analytics で画面表示時にスクリーンイベントを送りたい、といった場合に簡単に実装することができるようになるので嬉しい限りです!!

(今までは setUserVisibleHint() を使ってごにょごにょしてました...)

使い方

サンプルプロジェクトを こちら にアップしています。

この機能を有効にする方法ですが、FragmentPagerAdapterFragmentStatePagerAdapter に新しくコンストラクタが追加されました。

// FragmentPagerAdapter
public FragmentPagerAdapter(@NonNull FragmentManager fm, @Behavior int behavior)

// FragmentStatePagerAdapter
public FragmentStatePagerAdapter(@NonNull FragmentManager fm, @Behavior int behavior)

上記の behavior に設定できる値は現時点で2つあります。

  • USE_SET_USER_VISIBLE_HINT
  • RESUME_ONLY_CURRENT_FRAGMENT

前者は今まで通り、ページを切り替えるたびに Fragment の setUserVisibleHint() メソッドが呼ばれる挙動になります。

後者を設定することによって、先ほど紹介したページを切り替えるたびに Fragment の onResume() メソッドが呼ばれる挙動になり、setUserVisibleHint() メソッドは呼び出されなくなります。

(ちなみに setUserVisibleHint() メソッドは Deprecated になりました)

【Android】DownloadManagerでDLしたファイルが知らぬ間に削除される!?

DownloadManager でダウンロードしたファイルが気づいたら削除されてしまっている、という現象が発生していて原因が全く不明でした。

色々と調べていたところ、StackOverflow でこのような投稿を見つけました。

どうやら DownloadIdService なるものがダウンロードしたファイルを削除しているようです。

コードを見るとこの削除処理が行われる条件は、以下の2つとも当てはまる場合のようです。

改めてコードを見てみると、DownloadManager.Request を設定している箇所で setVisibleInDownloadsUi(false) となっていました...😰

setVisibleInDownloadsUi(true) にすればファイルが知らぬまに削除されることはなくなるはず...!

(もう少しドキュメントに詳しく書いてくれないとハマる気が...)

【Android】OkHttpのInterceptorで全ての通信リクエストに一括でパラメーターを追加する

実装メモ。

例えばリクエストパラメーターにアプリのバージョンを追加する場合。

class AddRequestParamsInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val newRequest = when (val method = request.method()) {
            "GET" -> {
                val originalUrl = request.url()
                val newUrl = originalUrl.newBuilder()
                    .addQueryParameter("version", BuildConfig.VERSION_NAME)
                    .build()
                request.newBuilder().url(newUrl).build()
            }
            else -> {
                val requestBuilder = request.newBuilder()
                val buffer = Buffer()

                FormBody.Builder()
                    .add("version", BuildConfig.VERSION_NAME)
                    .build()
                    .writeTo(buffer)
                
                request.body()?.let {
                    buffer.write("&".toByteArray())
                    it.writeTo(buffer)
                }
                    
                val requestBody = RequestBody.create(
                    MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"),
                    buffer.readUtf8()
                )

                requestBuilder.method(method, requestBody).build()
            }
        }

        return chain.proceed(newRequest)
    }
}

Github の便利機能 ~Applying suggested changes~

TwitterGithub の Tips 記事の紹介が流れていたのでチェックしてみたら、Applying suggested changes がめっちゃ便利そうでした。

紹介記事は以下になります。

プルリクで修正内容を提案することができ、修正内容に問題がなければ Github 上でコミットができるようです。 ただこの機能は2019年1月時点ではまだベータ版のようです。

typo などの修正はこれですぐに行えそうなので積極的に使っていきたいと思いました。

【Android】Activityの再生成時にFragmentを再生成させない

Activity 再生成時に Fragment を再生成させたくなかったのですが、その方法がわかったのでメモしておきます。

ちなみに Fragment は Support Library の Fragment を使用していることが前提です。

以下のコードを実装するだけで、Fragment が再生成されなくなります。

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState?.apply { remove("android:support:fragments") })
    
        ...
    }

    ...
}

こちらの StackOverflow の回答 を参考にしています。

AppCompatActivity が継承している FragmentActivity では android:support:fragments というタグ名で Fragment の状態を Bundle に保存しているため、Activity の onCreate で保存された Fragment の情報を削除してしまえば Fragment は再生成されません。

【Android】Gradleでライブラリの依存関係の記述を共通化する

Android 開発において、Unit Test と Instrumented Test で使用するライブラリは、Unit Test では testImplementation、Instrumented Test では androidTestImplementation を使って以下のように build.gradle を記述できます。

dependencies {
    // Unit Test で使用するライブラリ
    testImplementation "junit:junit:4.12"
    testImplementation "com.nhaarman:mockito-kotlin:1.5.0"
    testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0"

    // Instrumented Test で使用するライブラリ
    androidTestImplementation "com.nhaarman:mockito-kotlin:1.5.0"
    androidTestImplementation "com.squareup.okhttp3:mockwebserver:3.12.0"
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test:rules:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

この場合、mockito-kotlinmockwebserver が Unit Test と Instrumented Test の両方で使用するので2回記述されています。

新たに sharedTestImplementation という名前で Configuration を作成し、1回だけ記述するだけで Unit Test と Instrumented Test の両方で使えるようにするには以下のように記述します。

configurations {
    [testImplementation, androidTestImplementation]*.extendsFrom sharedTestImplementation
}

dependencies {
    // Unit Test & Instrumented Test で使用するライブラリ
    sharedTestImplementation "com.nhaarman:mockito-kotlin:1.5.0"
    sharedTestImplementation "com.squareup.okhttp3:mockwebserver:3.12.0"
    
    // Unit Test のみで使用するライブラリ
    testImplementation "junit:junit:4.12"

    // Instrumented Test のみで使用するライブラリ
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test:rules:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

上記のほかに、例えば特定の Flavor でのみ使用したいライブラリに対しては以下のように記述できます。

android {
    ...

    productFlavors {
        dev {}
        staging {}
        production {}
    }
}

configurations {
    [devImplementation, stagingImplementation]*.extendsFrom leakCanary
}

dependencies {
    ...

    // dev, staging の Flavor に対して LeakCanary を使用する
    leakCanary "com.squareup.leakcanary:leakcanary-android:1.6.2"
    productionImplementation "com.squareup.leakcanary:leakcanary-android-no-op:1.6.2"
}