How to test the reception of Broadcast Receivers? - android

I'm writing tests to verify the reception of the transmitting receivers but for some reason, the receiver is never registered or the intent is never sent.
I guess there should be a problem with the Context but, no luck yet finding it.
This is the BroadcastFactory.kt:
object BroadcastFactory {
private lateinit var intent: Intent
fun build(
action: String,
flag: Int? = null,
): BroadcastFactory {
intent = Intent().apply {
this.action = action
this.flags = flag ?: 0
}
return this
}
fun send(
context: Context
): Intent {
context.sendBroadcast(intent)
return intent
}
}
And this is the test file BroadcastTest.kt:
#RunWith(AndroidJUnit4::class)
#SmallTest
class BroadcastTest {
lateinit var intents: MutableList<Intent>
lateinit var latch: CountDownLatch
private lateinit var receiver: BroadcastReceiverTester
inner class BroadcastReceiverTester : BroadcastReceiver() {
override fun onReceive(p0: Context?, intent: Intent?) {
intent?.let {
intents.add(it)
latch.countDown()
}
}
}
private val context: Context = getInstrumentation().targetContext
#Before
fun setUp() {
intents = mutableListOf()
latch = CountDownLatch(1)
receiver = BroadcastReceiverTester()
LocalBroadcastManager.getInstance(context).registerReceiver(
receiver,
IntentFilter.create(
Constants.ACTION, "text/plain"
)
)
}
#Test
fun testBroadcastReception() {
BroadcastFactory
.build(Constants.ACTION, Constants.FLAG)
.send(context)
// assert broadcast reception (NOT WORKING)
latch.await(10, TimeUnit.SECONDS)
assertThat(intents.size).isEqualTo(1)
}
#After
fun tearDown() {
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver)
}
}
I'm using a CountDownLatch to wait 10 seconds for the receiver, plus, its value can be asserted. Besides, I set a list of Intents to check the number of registrations/receptions.
There is something I'm missing here? Different context provider? Robolectric runner?
Thanks

Is solved it by changing the receiver with this:
context.registerReceiver(
receiver,
IntentFilter(
Constants.ACTION
)
)
Thanks to #selvin and #mike-m for the help!

Related

SCREEN_ON and SCREEN_OFF broadcast not getting which are sent from PowerManager.WakeLock

I am trying to do some work when my screen goes off. The screen on and off function is related to the proximity sensor, but this method is not working for some samsung devices, I am currently using PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK. With this I'm able to turn the screen on and off with proximity behaviour but the problem is I'm not receiving any broadcast for screenn_on and screen_off so that I can start and stop some background work.
here's My Activity
private lateinit var sensorText : TextView
private lateinit var accuText : TextView
private lateinit var lightBtn : TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.sensor_test)
sensorText = findViewById(R.id.sensorInfo)
accuText = findViewById(R.id.accuracyInfo)
lightBtn = findViewById(R.id.lightBtn)
val proxMgr = ProximityMgr(this)
proxMgr.acquire()
val receiver = object : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
MediaPlayer.create(this#SensorTestAct, R.raw.btnclick).start()
// do required task here
}
}
val iFilter = IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_SCREEN_OFF)
}
registerReceiver(receiver, iFilter)
}
#SuppressLint("InvalidWakeLockTag")
class ProximityMgr(context: Context) {
private val powerManager: PowerManager = (context.getSystemService(POWER_SERVICE) as PowerManager?)!!
private val wakeLock: PowerManager.WakeLock
init {
wakeLock = powerManager.newWakeLock(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
"proximity")
}
fun acquire() {
if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
if (wakeLock.isHeld) {
wakeLock.release()
}
wakeLock.acquire(WAKE_LOCK_TIMEOUT_MS)
} else {
Log.w(TAG, "not supported")
}
}
fun release() {
if (wakeLock.isHeld)
wakeLock.release()
}
companion object {
private const val TAG = "ProximitySensor"
private const val WAKE_LOCK_TIMEOUT_MS: Long = 2 * 3600 * 1000
}
}
}```
If you suggest me any alternate method then it will be very helpful

How to call ViewModel functions from AppWidgetProvider?

I have an Retrofit2 API:
interface Api {
#POST("/my/url")
suspend fun function()
}
My ViewModel can call this HTTP function:
class MainViewModel : ViewModel() {
private val retrofiClient = APIClient.client!!.create(Api::class.java)
fun test() {
viewModelScope.launch {
retrofitClient.function()
}
}
}
No problem when i call this from my activity.
But my goal is to call this test() function from my app's widget.
My basic AppWidgetProvider looks like this:
class AppWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// Perform this loop procedure for each App Widget that belongs to this provider
appWidgetIds.forEach { appWidgetId ->
val intent = Intent()
intent.action = "custom-event-name"
intent.setClassName(
MainActivity::class.java.getPackage().name,
MainActivity::class.java.name
)
val pendingIntent = PendingIntent.getBroadcast(
context.applicationContext,
0,
intent,
PendingIntent.FLAG_CANCEL_CURRENT
)
val views: RemoteViews = RemoteViews(
context.packageName,
R.layout.appwidget
).apply {
setOnClickPendingIntent(R.id.test, pendingIntent)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
MainActivity:
class MainActivity : AppCompatActivity() {
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
println("INTENT RECEIVED")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
...
registerReceiver(receiver, IntentFilter("custom-event-name"))
...
}
The BroadcastReceiver does not receive the intent. Even if it does, can i call my app functions there?
How can i setup my app and/or widget, to call this viewModel.test() method when i click on a button in the widget?
You can use Channel(https://kotlinlang.org/docs/channels.html, for coroutines) or RxRelay (https://github.com/JakeWharton/RxRelay, for rxjava) instead of using broadcast receiver. Just put your Channel or Relay instance on your Application class so you can access it across your activities, fragments, etc.

Android - How to pass a function to an Activity?

I am attempting to create a Builder for an activity. The reason is because this activity can be started many different ways. I created a Builder class like this:
class ActivityBuilder {
private var showToolBar = false
private var postExecutable: (() -> Unit)? = null
fun showToolbar(boolean: Boolean) : ActivityBuilder {
this.showToolBar = boolean
return this
}
fun setPostExecutable(function: () -> Unit) : ActivityBuilder {
this.postExecute = function
return this
}
fun start(context: Context){
val intent = Intent(context, Activity::class.java)
context.startActivity(intent)
}
}
The idea is to call something like this and have access to these fields inside of the activity.
ActivityBuilder().showToolbar(false).setPostExecutable { { doSomething() } }.start(this)
I guess I could also use a companion object and that would serve the same purpose.
companion object Builder {
private var showToolBar = false
private var postExecute: (() -> Unit)? = null
fun showToolbar(boolean: Boolean) : Builder {
this.showToolBar = boolean
return this
}
fun setPostExecutable(function: () -> Unit) : Builder {
this.postExecute = function
return this
}
fun start(context: Context){
val intent = Intent(context, AuthActivity::class.java)
context.startActivity(intent)
}
}
The issue is coming mostly from the "postExecutable" field. I need to call the function at a certain point but it is not parcelable, so I cannot pass it through the intent when starting activity.
If anyone has a solution, I appreciate it!
This is one solution I found, may not be the most elegant. I created a broadcast receiver that I start at the same time as my activity using the parent context.
class ActivityBuilder(private val context: Context) {
private var postSuccessExecutable: (() -> Unit)? = null
...
private fun setupReceiver(){
val filter = IntentFilter()
filter.addAction("SUCCESS")
val receiver = object : BroadcastReceiver() {
override fun onReceive(c: Context?, intent: Intent?) {
context.unregisterReceiver(this)
if (intent?.action == "SUCCESS"){
Toast.makeText(context, "Successful", Toast.LENGTH_SHORT).show()
postSuccessExecutable?.invoke()
}
}
}
context.registerReceiver(authReceiver, filter)
}
...
}
When I want to trigger the function, I just send a broadcast:
private fun sendSuccessBroadcast(data: String){
val intent = Intent()
intent.action = "SUCCESS"
intent.putExtra("data", String)
requireContext().sendBroadcast(intent)
}

How to send context to BroadcastReceiver?

I want to use my BroadcastReceiver as sender of data into my activity. For this reason I'm using LocalBroadcastManager. This manager is used to register and unregister my receiver. Problem is that Context in onReceive method is different than Context in onStart and onStop method.
I need to pass activity context into my BroadcastReceiver or instance of LocalBroadcastManager initialized inside Activity. Because my receiver is not receiving any data.
Maybe it is not fault of this manager context but I don't know why it doesnt work since I implemented this manager.
class GPSReceiver: BroadcastReceiver(){
companion object{
const val GPS_PAYLOAD = "gps_payload"
}
override fun onReceive(context: Context, intent: Intent) {
try {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val int = Intent(GPS_PAYLOAD)
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
int.putExtra(GPS_PAYLOAD, true)
} else {
int.putExtra(GPS_PAYLOAD, false)
}
LocalBroadcastManager.getInstance(context).sendBroadcast(int)
} catch (ex: Exception) {
}
}
}
Registering receiver inside Activity:
private val gpsStatusReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
App.log("isGpsEnabled: onReceive")
val gpsStatus = intent?.extras?.getBoolean(GPS_PAYLOAD)
if (gpsStatus != null) {
if (gpsStatus){
App.log("isGpsEnabled: true")
hideGpsSnackbar()
} else {
App.log("isGpsEnabled: false")
showGpsSnackbar()
}
} else {
App.log("isGpsEnabled: null")
}
}
}
override fun onStart() {
super.onStart()
LocalBroadcastManager.getInstance(this).apply {
val filter = IntentFilter()
filter.apply {
addAction("android.location.PROVIDERS_CHANGED")
addAction(GPS_PAYLOAD)
}
registerReceiver(gpsStatusReceiver, filter)
}
}
I have seen your code. So there is not issue with context, but in the approach.
Your are registering your reciever with the same strings in which you are getting you data inside the Reciever.
So Send Your broadcast from Fragment/Activity
Send BroadCast Like
private fun sendSuccessfulCheckoutEvent() {
val intent = Intent("successful_checkout_event")
intent.putExtra("cartID", cartId)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
And Listen it in Activity/Fragment like this
1) Create broadcast Reciever
private val checkoutDoneReciever : BroadcastReceiver = object : BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
val cartNumbers = intent.getIntExtra("cartID", 0)
Log.d("receiver", "Got message: $cartNumbers.toString()")
}
}
2) Register it in onCreate()/onStart()
LocalBroadcastManager.getInstance(this).registerReceiver(cartUpdatedReceiver,IntentFilter("successful_checkout_event"))
3) Unregister it in onDestroy()
LocalBroadcastManager.getInstance(this).unregisterReceiver(cartUpdatedReceiver)

Broadcast Receiver receive information only once

I have a service which sends two broadcasts at the same time.
val i = Intent(PlayerService.INTENT_ACTION)
i.putExtra(EVENT_EXTRAS, PlayerEvent.PLAYER_READY.ordinal)
i.putExtra(DURATION_EXTRAS, mp.duration) //some duration
sendBroadcast(i)
val i1 = Intent(PlayerService.INTENT_ACTION)
i1.putExtra(EVENT_EXTRAS, PlayerEvent.ON_SECOND_CHANGED.ordinal)
i1.putExtra(DURATION_EXTRAS, player.duration) //another duration
sendBroadcast(i1)
The action of intents is the same, but the extras is different. Finally, I only get the answer from the second broadcast. Who knows what the cause is?
My Receiver Live Data:
class PlayerLiveEvent(val context: Context) : LiveData<Intent>() {
override fun onActive() {
super.onActive()
context.registerReceiver(receiver, IntentFilter(PlayerService.INTENT_ACTION))
}
override fun onInactive() {
super.onInactive()
context.unregisterReceiver(receiver)
}
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
postValue(intent)
}
}
}
Fragment where I observe these events:
PlayerLiveEvent(activity!!).observe(this, Observer {
it?.apply {
val event = PlayerEvent.values()[getIntExtra(EVENT_EXTRAS, 0)]
when (event) {
PlayerEvent.PLAYER_READY -> {
println("PLAYER_READY")
}
PlayerEvent.ON_SECOND_CHANGED -> {
println("ON_SECOND_CHANGED")
}
else -> println()
}
}
})
Your second onReceive is called before a postValue task from the first onReceive is executed on the main thread and hence the value set the second time is ignored. You can also see this from the implementation of postValue:
...
synchronized (mDataLock) {
// for your second call this will be false as there's a pending value
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
// so this is true and so the method returns prematurely
if (!postTask) {
return;
}
...
Thereof, use setValue because it sets the value immediately and is called from the main thread.
The problem is in LiveData, the events are not being transmitted as needed.
This document explains why postValue posts only once. That is why the solution would be the following:
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
value = intent
}
}

Categories

Resources