AltBeacon/android-beacon-library

the altBeacon library does not update the Live Data object

Opened this issue · 5 comments

I have integrated the altBeacon library in my project and it does not update the live data object, it finds an IBeacon device if there are many IBeacon devices nearby, it does not identify them or update the live data. I also noticed this problem in the example application (beaconreferenceapplication) in the library (altbeacon). I noticed that the library depends on a version of other dependencies (libraries). and what libraries and versions are required? where can I read what versions I need for the required dependencies (libraries)? ### Expected behavior
to find each device and update live data.

Actual behavior

finds an ibeacon device and further finds nothing.

Steps to reproduce this behavior

integrate altbeacon library into project, start scanning with foreground service and read data from live data or compile examples from library and runazo having multiple ibeacon devices nearby (more than 1).

Mobile device model and OS version

google pixel 9 pro android 15 and samsung a50 android11

Android Beacon Library version

V2.20 and with V2.20.6 same result
IMPORTANT: This forum is reserved for feature requests or reproducible bugs with the library itself. If you need help with using the library with your project, please open a new question on StackOverflow.com.

Regarding this " I also noticed this problem in the example application (beaconreferenceapplication) in the library (altbeacon)"

Did you test with the official Kotlin reference app? You should see it give a callback to the ranging observer every ~ 1 second here:

https://github.com/davidgyoung/android-beacon-library-reference-kotlin/blob/master/app/src/main/java/org/altbeacon/beaconreference/BeaconReferenceApplication.kt#L140

The monitoring observer, however, is expected to get exactly one callback when the beacon appears, and one again when it disappears:

https://github.com/davidgyoung/android-beacon-library-reference-kotlin/blob/master/app/src/main/java/org/altbeacon/beaconreference/BeaconReferenceApplication.kt#L130

Is this not what you see?

yes i tested with the official library app and i get only one iBeacon device. in the diary I see that the monkeys ignore them, I think they are other types of devices, the only difference between my project and the official one is that for me it is not through a simple list but through an adapter. Below is the code from my project. package org.altbeacon.beaconreference

import android.app.*
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.lifecycle.Observer
import org.altbeacon.beacon.*
import org.altbeacon.bluetooth.BluetoothMedic

class BeaconReferenceApplication: Application() {
// the region definition is a wildcard that matches all beacons regardless of identifiers.
// if you only want to detect beacons with a specific UUID, change the id1 paremeter to
// a UUID like Identifier.parse("2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6")
private val bleUUID = "A134D0B2-1DA2-1BA7-C94C-E8E00C9F7A2D" //"2D7A9F0C-E0E8-4CC9-A71B-A21DB2D034A1"
var region = Region("all-beacons", Identifier.parse(bleUUID), null, null)

override fun onCreate() {
    super.onCreate()

    val beaconManager = BeaconManager.getInstanceForApplication(this)
    BeaconManager.setDebug(true)

    // By default the AndroidBeaconLibrary will only find AltBeacons.  If you wish to make it
    // find a different type of beacon, you must specify the byte layout for that beacon's
    // advertisement with a line like below.  The example shows how to find a beacon with the
    // same byte layout as AltBeacon but with a beaconTypeCode of 0xaabb.  To find the proper
    // layout expression for other beacon types, do a web search for "setBeaconLayout"
    // including the quotes.
    //
    //beaconManager.getBeaconParsers().clear();
    //beaconManager.getBeaconParsers().add(new BeaconParser().
    //        setBeaconLayout("m:0-1=4c00,i:2-24v,p:24-24"));


    // By default the AndroidBeaconLibrary will only find AltBeacons.  If you wish to make it
    // find a different type of beacon like Eddystone or iBeacon, you must specify the byte layout
    // for that beacon's advertisement with a line like below.
    //
    // If you don't care about AltBeacon, you can clear it from the defaults:
    //beaconManager.getBeaconParsers().clear()

    // Uncomment if you want to block the library from updating its distance model database
    //BeaconManager.setDistanceModelUpdateUrl("")

    // The example shows how to find iBeacon.
    val parser = BeaconParser().
    setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24")
    parser.setHardwareAssistManufacturerCodes(arrayOf(0x004c).toIntArray())
    beaconManager.getBeaconParsers().add(
        parser)

    // enabling debugging will send lots of verbose debug information from the library to Logcat
    // this is useful for troubleshooting problmes
    // BeaconManager.setDebug(true)


    // The BluetoothMedic code here, if included, will watch for problems with the bluetooth
    // stack and optionally:
    // - power cycle bluetooth to recover on bluetooth problems
    // - periodically do a proactive scan or transmission to verify the bluetooth stack is OK
    // BluetoothMedic.getInstance().legacyEnablePowerCycleOnFailures(this) // Android 4-12 only
    // BluetoothMedic.getInstance().enablePeriodicTests(this, BluetoothMedic.SCAN_TEST + BluetoothMedic.TRANSMIT_TEST)

    //setupBeaconScanning()
}
fun setupBeaconScanning() {
    val beaconManager = BeaconManager.getInstanceForApplication(this)

    // By default, the library will scan in the background every 5 minutes on Android 4-7,
    // which will be limited to scan jobs scheduled every ~15 minutes on Android 8+
    // If you want more frequent scanning (requires a foreground service on Android 8+),
    // configure that here.
    // If you want to continuously range beacons in the background more often than every 15 mintues,
    // you can use the library's built-in foreground service to unlock this behavior on Android
    // 8+.   the method below shows how you set that up.
    try {
        setupForegroundService()
    }
    catch (e: SecurityException) {
        // On Android TIRAMUSU + this security exception will happen
        // if location permission has not been granted when we start
        // a foreground service.  In this case, wait to set this up
        // until after that permission is granted
        Log.d(TAG, "Not setting up foreground service scanning until location permission granted by user")
        return
    }
    //beaconManager.setEnableScheduledScanJobs(false);
    //beaconManager.setBackgroundBetweenScanPeriod(0);
    //beaconManager.setBackgroundScanPeriod(1100);

    // Ranging callbacks will drop out if no beacons are detected
    // Monitoring callbacks will be delayed by up to 25 minutes on region exit
    // beaconManager.setIntentScanningStrategyEnabled(true)

    // The code below will start "monitoring" for beacons matching the region definition at the top of this file
    beaconManager.startMonitoring(region)
    beaconManager.startRangingBeacons(region)
    // These two lines set up a Live Data observer so this Activity can get beacon data from the Application class
    val regionViewModel = BeaconManager.getInstanceForApplication(this).getRegionViewModel(region)
    // observer will be called each time the monitored regionState changes (inside vs. outside region)
    regionViewModel.regionState.observeForever( centralMonitoringObserver)
    // observer will be called each time a new list of beacons is ranged (typically ~1 second in the foreground)
    regionViewModel.rangedBeacons.observeForever( centralRangingObserver)

}

fun setupForegroundService() {
    val builder = Notification.Builder(this, "BeaconReferenceApp")
    builder.setSmallIcon(R.drawable.ic_launcher_background)
    builder.setContentTitle("Scanning for Beacons")
    val intent = Intent(this, ListDevices::class.java)
    val pendingIntent = PendingIntent.getActivity(
            this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE
    )
    builder.setContentIntent(pendingIntent);
    val channel =  NotificationChannel("beacon-ref-notification-id",
        "My Notification Name", NotificationManager.IMPORTANCE_DEFAULT)
    channel.setDescription("My Notification Channel Description")
    val notificationManager =  getSystemService(
            Context.NOTIFICATION_SERVICE) as NotificationManager
    notificationManager.createNotificationChannel(channel);
    builder.setChannelId(channel.getId());
    Log.d(TAG, "Calling enableForegroundServiceScanning")
    BeaconManager.getInstanceForApplication(this).enableForegroundServiceScanning(builder.build(), 456);
    Log.d(TAG, "Back from  enableForegroundServiceScanning")
}

val centralMonitoringObserver = Observer<Int> { state ->
    if (state == MonitorNotifier.OUTSIDE) {
        Log.d(TAG, "outside beacon region: "+region)
    }
    else {
        Log.d(TAG, "inside beacon region: "+region)
        sendNotification()
    }
}

val centralRangingObserver = Observer<Collection<Beacon>> { beacons ->

    val rangeAgeMillis = System.currentTimeMillis() - (beacons.firstOrNull()?.lastCycleDetectionTimestamp ?: 0)
    if (rangeAgeMillis < 10000) {
        Log.d("BeaconReferenceApplication", "Ranged: ${beacons.count()} beacons")
        for (beacon: Beacon in beacons) {
            Log.d(TAG, "$beacon about ${beacon.distance} meters away")
        }
    }
    else {
        Log.d("BeaconReferenceApplication", "Ignoring stale ranged beacons from $rangeAgeMillis millis ago")
    }
}

private fun sendNotification() {
    val builder = NotificationCompat.Builder(this, "beacon-ref-notification-id")
        .setContentTitle("Beacon Reference Application")
        .setContentText("A beacon is nearby.")
        .setSmallIcon(R.drawable.ic_launcher_background)
    val stackBuilder = TaskStackBuilder.create(this)
    stackBuilder.addNextIntent(Intent(this, ListDevices::class.java))
    val resultPendingIntent = stackBuilder.getPendingIntent(
        0,
        PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE
    )
    builder.setContentIntent(resultPendingIntent)
    val channel =  NotificationChannel("beacon-ref-notification-id",
        "My Notification Name", NotificationManager.IMPORTANCE_DEFAULT)
    channel.setDescription("My Notification Channel Description")
    val notificationManager =  getSystemService(
        Context.NOTIFICATION_SERVICE) as NotificationManager
    notificationManager.createNotificationChannel(channel);
    builder.setChannelId(channel.getId());
    notificationManager.notify(1, builder.build())
}

companion object {
    val TAG = "BeaconReference"
}

} activity that starts scanning and displays them package org.altbeacon.beaconreference

import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.OnBackPressedCallback
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.Observer
import org.altbeacon.beacon.Beacon
import org.altbeacon.beacon.BeaconManager
import org.altbeacon.beacon.MonitorNotifier
import org.altbeacon.adapters.BleDevicesAdapter
import org.altbeacon.beacon.permissions.BeaconScanPermissionsActivity

class ListDevices : AppCompatActivity() {
//private lateinit var bluetoothServices: BluetoothServices
private lateinit var beaconReferenceApplication: BeaconReferenceApplication
private lateinit var recyclerView: RecyclerView
private lateinit var bleDevicesAdapter: BleDevicesAdapter
//private lateinit var iBeaconsView: IBeaconsView

@SuppressLint("CheckResult")
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list_devices)

//bluetoothServices = BluetoothServices(this)
recyclerView = findViewById(R.id.resultScannerDevices)
recyclerView.layoutManager = LinearLayoutManager (this)
bleDevicesAdapter = BleDevicesAdapter (this) { device ->
onDeviceClick(device)
}
recyclerView.adapter = bleDevicesAdapter
//iBeaconsView = ViewModelProvider(this)[IBeaconsView::class.java]
//iBeaconsView.beacons.observe(this) {deviceList ->
// bleDevicesAdapter.updateDevices(deviceList)
// }

    beaconReferenceApplication = application as BeaconReferenceApplication

    //I set up a Live Data observer for the signaling data
    val regionViewModel = BeaconManager.getInstanceForApplication(this).getRegionViewModel(beaconReferenceApplication.region)
    regionViewModel.regionState.observe(this, monitoringObserver)
    regionViewModel.rangedBeacons.observe(this, rangingObserver)

    //check if all permissions are accepted
    if (!BeaconScanPermissionsActivity.allPermissionsGranted(this, true)){
        // permissions are not supported and prompt the user
        val intent = Intent(this, BeaconScanPermissionsActivity::class.java)
        intent.putExtra("backgroundAccessRequested", true)
        startActivity(intent)
    } else {
        //permissions are accepted and start foreground service and scan
        if (BeaconManager.getInstanceForApplication(this).monitoredRegions.size == 0){
            (application as BeaconReferenceApplication).setupBeaconScanning()
            val beaconManager = BeaconManager.getInstanceForApplication(this)
            beaconManager.startMonitoring(beaconReferenceApplication.region)
            beaconManager.startRangingBeacons(beaconReferenceApplication.region)
        }
        if (BeaconManager.getInstanceForApplication(this).rangedRegions.size == 0){
            val beaconManager = BeaconManager.getInstanceForApplication(this)
            beaconManager.startRangingBeacons(beaconReferenceApplication.region)
            beaconManager.startMonitoring(beaconReferenceApplication.region)
        }
    }

    val stopScaning: Button = findViewById(R.id.stopScaning)
    stopScaning.setOnClickListener {
        val beaconManager = BeaconManager.getInstanceForApplication(this)
        beaconManager.stopRangingBeacons(beaconReferenceApplication.region)
        beaconManager.stopMonitoring(beaconReferenceApplication.region)
        startActivity(Intent(this, MainActivity::class.java))
        finishAffinity()
    }
    onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            val beaconManager = BeaconManager.getInstanceForApplication(this@ListDevices)
            beaconManager.stopRangingBeacons(beaconReferenceApplication.region)
            beaconManager.stopMonitoring(beaconReferenceApplication.region)
            finish()
        }
    })
}

private val monitoringObserver = Observer {state ->
if (state == MonitorNotifier.OUTSIDE){
Log.d("RESULT_SCAN", "nu este nimic în jur")
} else {
Log.d("RESULT_SCAN", "ceva este înapropriere")
}
}

private val rangingObserver = Observer<Collection> {beacons ->
if (BeaconManager.getInstanceForApplication(this).rangedRegions.size > 0){
//beacons.sortedBy { it.distance }
beacons .forEach {
Log.d("RESULT_SCAN", "Nume ${it.bluetoothName} mac adresa ${it.bluetoothAddress}")
//val iBeaconDevice
val iBeacon = IBeacon(it.bluetoothAddress)
iBeacon.uuid = it.id1.toString()
iBeacon.major = it.id2.toInt()
iBeacon.minor = it.id3.toInt()
iBeacon.rssi = it.rssi
bleDevicesAdapter.updateDevices(listOf(iBeacon))
}
}
}

private fun onDeviceClick(device: IBeacon) {

val beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.stopRangingBeacons(beaconReferenceApplication.region)
beaconManager.stopMonitoring(beaconReferenceApplication.region)
val intentConnecting = Intent(applicationContext, CommunicationWithTheDevice::class.java)
val selectedDevice = device.macAddress
intentConnecting.putExtra("connectingTo", selectedDevice)
startActivity(intentConnecting)
finishAffinity()

}

}

this is the display adapter from recyclerview package org.altbeacon.adapters

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import android.widget.Button
import org.altbeacon.beaconreference.IBeacon
import org.altbeacon.beaconreference.DataServices
import org.altbeacon.beaconreference.R

class BleDevicesAdapter (private val context: Context, private val devices: MutableList = mutableListOf(), private val onItemClick: (IBeacon) ->Unit) : RecyclerView.Adapter<BleDevicesAdapter.ViewHolder>() {
private val dataServices = DataServices()
inner class ViewHolder (itemView: View) : RecyclerView.ViewHolder (itemView) {
private val resultScannerDevices: Button = itemView.findViewById(R.id.resultScannerDevicesButton)
//private val resultScannerDevices: View = itemView.findViewById(R.id.resultScannerDevices)
fun bind(resultBleDevice: IBeacon) {
val categoryTextWidgets = dataServices.getNameByMajor(context, resultBleDevice.major.toString())
val numberTextWidgets = dataServices.getNumberByMinor(context, resultBleDevice.major.toString(), resultBleDevice.minor.toString())
val informationWidgets = dataServices.getInformationByNumber(context, resultBleDevice.major.toString(), resultBleDevice.minor.toString())
resultScannerDevices.text = context.getString(R.string.DeviceWidgetName, categoryTextWidgets, numberTextWidgets, informationWidgets)

        resultScannerDevices.setOnClickListener{
            onItemClick(resultBleDevice)
        }
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_device, parent, false)
    return ViewHolder(itemView)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val device = devices[position]
    holder.bind(device)
}

override fun getItemCount() = devices.size
fun updateDevices (newDevices: List<IBeacon>){
    val previousSize = devices.size
    devices.clear()
    devices.addAll(newDevices)
    if (previousSize <devices.size) {
        notifyItemRangeInserted(previousSize, devices.size - previousSize)
    } else if (previousSize > devices.size) {
        notifyItemRangeRemoved(devices.size, previousSize - devices.size)
    } else {

// notifyDataSetChanged()
}
}

}

Hey @G-Roman1988, I'd like to help, but this is not the proper forum for troubleshooting a custom app.

The comment above shows 354 lines of code, which is beyond the scope of what we can do here. Two suggestions:

  1. Can you please make a simpler test case to produce the problem that shows a smaller diff from the reference app (e.g. just a dozen or so lines of code changed?)

  2. If you can't do the above but need help troubleshooting a specific use case in a custom app, you may wish to post your question on StackOverflow.com.

Hei @davidgyoung Me too please help me today I will open an issue/question on StackOverflow.com. and when it's published I'll leave a link to the StackOverflow.com question here, if you could help me I'd be grateful.