LifecycleTestUtils.kt
TLDR
This file provides utility functions and extension functions related to working with Android activity and fragment lifecycles during Android instrumentation testing.
Methods
activityTestRule
This method generates an ActivityTestRule
for a given activity class.
triggersOnActivityCreated
This method executes a block of code when an activity is created.
retained
This infix function executes a block of code and then returns the string representation of the object it was called on.
triggersOnActivityDestroyed
This method executes a block of code when the test activity is destroyed.
triggersOnFragmentCreated
This method executes a block of code when a fragment is created in the test activity.
triggersOnFragmentViewDestroyed
This method executes a block of code when a fragment's view is destroyed in the test activity.
waitForTriggered
This method waits for a trigger to occur and then executes a block of code.
getOnMainSync
This method executes a block of code on the main thread synchronously.
runOnMainSync
This method executes a block of code on the main thread synchronously.
installViewModel
This extension function installs and returns a ViewModel instance in a ViewModelStoreOwner.
addFragmentNow
This extension function adds a fragment to the activity's fragment manager and commits the transaction immediately.
replaceWithBackStack
This extension function replaces a fragment in the activity's fragment container view with back stack support.
removeFragmentNow
This extension function removes a fragment from the activity's fragment manager and commits the transaction immediately.
END
package leakcanary
import android.app.Activity
import android.app.Application
import android.os.Bundle
import android.os.Looper
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.Factory
import androidx.lifecycle.ViewModelStoreOwner
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicReference
import kotlin.reflect.KClass
import leakcanary.internal.friendly.noOpDelegate
interface HasActivityTestRule<T : Activity> {
val activityRule: ActivityTestRule<T>
val activity: T
get() = activityRule.activity!!
}
inline fun <reified T : Activity> activityTestRule(
initialTouchMode: Boolean = false,
launchActivity: Boolean = true
): ActivityTestRule<T> = ActivityTestRule(
T::class.java, initialTouchMode, launchActivity
)
fun <R> triggersOnActivityCreated(block: () -> R): R {
return waitForTriggered(block) { triggered ->
val app = ApplicationProvider.getApplicationContext<Application>()
app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
app.unregisterActivityLifecycleCallbacks(this)
triggered()
}
})
}
}
infix fun Any.retained(block: () -> Unit) {
block()
"" + this
}
fun <T : FragmentActivity, R> HasActivityTestRule<T>.triggersOnActivityDestroyed(block: () -> R): R {
return waitForTriggered(block) { triggered ->
val testActivity = activity
testActivity.application.registerActivityLifecycleCallbacks(
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (activity == testActivity) {
activity.application.unregisterActivityLifecycleCallbacks(this)
Looper.myQueue()
.addIdleHandler {
triggered()
false
}
}
}
})
}
}
fun <T : FragmentActivity, R> HasActivityTestRule<T>.triggersOnFragmentCreated(block: () -> R): R {
return waitForTriggered(block) { triggered ->
val fragmentManager = activity.supportFragmentManager
fragmentManager.registerFragmentLifecycleCallbacks(
object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentCreated(
fm: FragmentManager,
fragment: Fragment,
savedInstanceState: Bundle?
) {
fragmentManager.unregisterFragmentLifecycleCallbacks(this)
triggered()
}
}, false
)
}
}
fun <T : FragmentActivity, R> HasActivityTestRule<T>.triggersOnFragmentViewDestroyed(block: () -> R): R {
return waitForTriggered(block) { triggered ->
val fragmentManager = activity.supportFragmentManager
fragmentManager.registerFragmentLifecycleCallbacks(
object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
fragmentManager.unregisterFragmentLifecycleCallbacks(this)
triggered()
}
}, false
)
}
}
fun <R> waitForTriggered(
trigger: () -> R,
triggerListener: (triggered: () -> Unit) -> Unit
): R {
val latch = CountDownLatch(1)
triggerListener {
latch.countDown()
}
val result = trigger()
latch.await()
return result
}
fun <T> getOnMainSync(block: () -> T): T {
val resultHolder = AtomicReference<T>()
val latch = CountDownLatch(1)
InstrumentationRegistry.getInstrumentation()
.runOnMainSync {
resultHolder.set(block())
latch.countDown()
}
latch.await()
return resultHolder.get()
}
fun runOnMainSync(block: () -> Unit) = InstrumentationRegistry.getInstrumentation()
.runOnMainSync(block)
fun <T : ViewModel> ViewModelStoreOwner.installViewModel(modelClass: KClass<T>): T =
ViewModelProvider(this, object : Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T = modelClass.newInstance()
}).get(modelClass.java)
fun FragmentActivity.addFragmentNow(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.add(0, fragment)
.commitNow()
}
fun FragmentActivity.replaceWithBackStack(
fragment: Fragment,
@IdRes containerViewId: Int
) {
supportFragmentManager
.beginTransaction()
.addToBackStack(null)
.replace(containerViewId, fragment)
.commit()
}
fun FragmentActivity.removeFragmentNow(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.remove(fragment)
.commitNow()
}