I am developing an app for scanning devices with bluetooth.
From the Android studio's documentation I found how scan devices and perform an action when a device is discovered:
override fun onCreate(savedInstanceState: Bundle?) {
...
// Register for broadcasts when a device is discovered.
val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)
registerReceiver(receiver, filter)
}
// Create a BroadcastReceiver for ACTION_FOUND.
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action: String = intent.action
when(action) {
BluetoothDevice.ACTION_FOUND -> {
// Discovery has found a device. Get the BluetoothDevice
// object and its info from the Intent.
val device: BluetoothDevice =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
val deviceName = device.name
val deviceHardwareAddress = device.address // MAC address
}
}
}
}
The question is: if I would to create a class for scanning devices, how I can, for each device discovered, pass it to my main class (MainActivity) ?.
I was thinking to use Observer and Observe a specific variable in scanning class, but i dont know if this is a good pratice.
You can use ViewModel class and save all your data here. You can get your ViewModel in any class.
https://developer.android.com/topic/libraries/architecture/viewmodel
Related
I will send step count:
Foreground Service -> Broadcast Receiver -> Fragment
Since I need to keep tracking step count on background and when app is off, I've created two Broadcast Receiver, one for notification to keep showing step count on background and one for fragment UI which will be unregistered on Destroy.
So this process is for the latter.
Foreground Service
: I send "stepCount" value to broadcast.
Intent().also { intent ->
intent.setAction(ACTION_STEP_COUNTER_NOTIFICATION)
intent.putExtra("stepCount", "$todayTotalStepCount")
sendBroadcast(intent)
}
BroadcastReceiver
open class StepCountBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
if(intent!!.action == ACTION_STEP_COUNTER_NOTIFICATION) {
var intent = Intent()
var stepCount = intent.getStringExtra("stepCount")
var sendMyDiary = Intent(context!!, MyDiaryFragment::class.java)
sendMyDiary.putExtra("stepCount", stepCount)
context.startActivity(sendMyDiary)
}
}
}
when custom Action is triggered, this will get 'stepCount' from Service and here I will send it to Fragment named MyDiaryFragment.
So I send it by using Intent().putExtra.
Fragment
private val stepCountBroadcastReceiver: BroadcastReceiver = StepCountBroadcastReceiver()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
addAction(ACTION_STEP_COUNTER_NOTIFICATION)
}
LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(stepCountBroadcastReceiver, filter)
Here is my question.
I register this StepCountBroadcastReceiver in onCreate.
But I don't know how to get stepCount coming from broadcast in Fragment whenever Broadcastreceiver is called.
It that same process like just adding this below line at the bottom?
var stepCount = requireActivity().intent.getStringExtra("stepCount")
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
addAction(ACTION_STEP_COUNTER_NOTIFICATION)
}
LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(stepCountBroadcastReceiver, filter)
var stepCount = requireActivity().intent.getStringExtra("stepCount")
In your broadcast receiver, add this.To Send a LocalBroadCast
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
if(intent?.action == ACTION_STEP_COUNTER_NOTIFICATION) {
var stepCount = intent.getStringExtra("stepCount")
var localBroadCastIntent = Intent(LOCAL_BROADCAST_KEY)
localBroadCastIntent.putExtra("stepCount",stepCount?:"")
Handler(Looper.getMainLooper()).post {
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
}
}
const val LOCAL_BROADCAST_KEY = "step_counter_local_broadcast"
In your Fragment Call this register function.And in onCreateView and make sure to call unregisterReceiver in ondestroyview of Fragment.
private fun registerBroadCastReceiver() {
LocalBroadcastManager.getInstance(applicationContext).registerReceiver(
receiver,
IntentFilter(LOCAL_BROADCAST_KEY)
)
}
}
Add this BroadCast Receiver,in Fragment Class globally.
private var receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
val stepCount = it.getStringExtra("stepCount")
}
}
}
Note:
Here Flow will be like this.
Service -> BroadCast Receiver -> LocalBroadCastReceiver ->
UpdateFragment UI.
You can also directly call this localbroadcast from service based on your usecase. So it will be like below.
Service-> LocalBroadCastReceiver -> UpdateFragment UI.
So only when fragment is alive, the local broadcast receiver will receive messages and update ui.
I want to pass data from service to activity from another app. I already did activity to service exactly same as Bound Services using Messenger in developer.android.com. However they didn't do service to activity in Kotlin, and I didn't see similar solution in Kotlin. There are java solutions but they didn't work for me. Probably Messenger or Broadcast should work but I want to know how.
I think activity should have IncomingHandler and service should have onServiceConnected so can send message like mService?.send(msg) , but I didn't find how to bind them.
Update
Service
val intent = Intent("TO_SALE_APP")
intent.putExtra("RESPONSE_CODE", responseCode)
intent.putExtra("STATUS", status)
intent.putExtra("PAYMENT_TYPE", paymentType)
LocalBroadcastManager.getInstance(this.applicationContext).sendBroadcast(intent)
Update
Activity
private val mMessageReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val responseCode = intent.getStringExtra("RESPONSE_CODE")
val status = intent.getIntExtra("STATUS",0)
val paymentType = intent.getIntExtra("PAYMENT_TYPE",0)
Log.d("myTAG","data receive on activity!")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
...
LocalBroadcastManager.getInstance(this).registerReceiver(
mMessageReceiver, IntentFilter("TO_SALE_APP")
)
}
Yes you can send data from Services to Activity
I had done it by sending Location Coordinated from Background Service class to Activity Periodically
In the service, class add this code
val intent = Intent("GPSLocationUpdates")
intent.putExtra("latitude", location.latitude.toString())
intent.putExtra("longitude", location.longitude.toString())
LocalBroadcastManager.getInstance(this#LocationService).sendBroadcast(intent)
In your Activity receives this data by , write this before onCreate
private val mMessageReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
// Get extra data included in the Intent
val latitude = intent.getStringExtra("latitude")
val longitude = intent.getStringExtra("longitude")
}
}
and in the onCreate method mention this
LocalBroadcastManager.getInstance(this).registerReceiver(
mMessageReceiver, IntentFilter("GPSLocationUpdates")
);
NOTE: the EventListener GPSLocationUpdates should be the same in Service and Activity
I have a broadcast receiver for wifi scan results as a data source and I'd like to make it in coroutine way. I found an answer for suspend function here:
https://stackoverflow.com/a/53520496/5938671
suspend fun getCurrentScanResult(): List<ScanResult> =
suspendCancellableCoroutine { cont ->
//define broadcast reciever
val wifiScanReceiver = object : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) {
if (intent.action?.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) == true) {
context.unregisterReceiver(this)
cont.resume(wifiManager.scanResults)
}
}
}
//setup cancellation action on the continuation
cont.invokeOnCancellation {
context.unregisterReceiver(wifiScanReceiver)
}
//register broadcast reciever
context.registerReceiver(wifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
//kick off scanning to eventually receive the broadcast
wifiManager.startScan()
}
This is fine for signle emit, but if I want to get results while scanning is going then I'll get crash because cont.resume() could be called only once. Then I decided to try Flow. And here is my code:
suspend fun getCurrentScanResult(): Flow<List<ScanResult>> =
flow{
val wifiScanReceiver = object : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) {
if (intent.action?.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) == true) {
//context.unregisterReceiver(this)
emit(wifiManager.scanResults)
}
}
}
//setup cancellation action on the continuation
//register broadcast reciever
context.registerReceiver(wifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
//kick off scanning to eventually receive the broadcast
wifiManager.startScan()
}
But now Android Stuidio says Suspension functions can be called only within coroutine body for function emit(wifiManager.scanResults) Is there a way to use Flow here?
Please take a look at the callback flow which is specifically designed for this use case. Something like this will do the job:
callbackFlow {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) {
sendBlocking(wifiManager.scanResults) // or non-blocking offer()
}
}
}
context.registerReceiver(receiver, intentFilter)
awaitClose {
context.unregisterReceiver(receiver)
}
}
You also might want to share this flow with e.g. shareIn operator to avoid registering a new receiver for each flow subscriber.
Im implementing a simple blutooth classic scan from the official docs on my Redmi Note 4 (Bluetooth 4.1).
However I am not seeing a toast that should appear when the onReceive function on the broadcast receiver is called.
What could be wrong here?
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
defaultAdapter = BluetoothAdapter.getDefaultAdapter()
if (defaultAdapter == null) {
toast("no adapter")
return
}
if (!defaultAdapter!!.isEnabled) {
val enableBluetoothIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBluetoothIntent, REQUEST_ENABLE_BLUETOOTH)
}
searchButton.setOnClickListener { newDevicesList() }
val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)
Log.i("receiver", "registering receiver")
registerReceiver(receiver, filter)
}
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
toast("maybe i find new device who know")
when (intent.action) {
BluetoothDevice.ACTION_FOUND -> {
// Discovery has found a device. Get the BluetoothDevice
// object and its info from the Intent.
val device: BluetoothDevice? =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
val deviceName = device?.name
val deviceHardwareAddress = device?.address // MAC address
toast("device $deviceName was found")
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
context.unregisterReceiver(this)
}
}
if (defaultAdapter!!.isDiscovering) {
toast("scan already in progress")
} else {
toast("starting discovery")
defaultAdapter!!.startDiscovery();
}
}
How do i know that the onReceive function never gets called? cause this toast toast("maybe i find new device who know") never appears.
How to save string message from BroadcastReceiver and use saved variable in Activity? I'm finding only Toast.makeText examples.
What i'm actually have: working, registered BroadcastReceiver. My app running in DocumentActivity, when i'm hitting Scan button on my DataCollectionTerminal (DTC on Android 7.0), DTC takes message and toast it. I can catch messages from DTC in opened EditText and save it onClick save button.
But what i'm need: Scan button pressed => DTC takes barcode message => send to Activity and save it to some variable => and i can use this var.value in whole Activity, set it TextView, write it to the txt document and etc.
DocumentActivity
class DocumentActivity : AppCompatActivity() {
private val customBroadcastReceiver = CustomBroadcastReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(exp..R.layout.activity_document)
}
override fun onResume() {
super.onResume()
registerReceiver(
customBroadcastReceiver,
IntentFilter ("com.xcheng.scanner.action.BARCODE_DECODING_BROADCAST")
)
}
override fun onStop() {
super.onStop()
unregisterReceiver(customBroadcastReceiver)
}
fun saveMessage(mes: String){
var code = mes
Toast.makeText(applicationContext, code, Toast.LENGTH_SHORT).show()
...
}}
BroadcastReceiver
class CustomBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val type = intent.getStringExtra("EXTRA_BARCODE_DECODING_SYMBOLE")
val barcode = intent.getStringExtra("EXTRA_BARCODE_DECODING_DATA")
val sb = StringBuilder()
sb.append("Type: $type, Barcode:$barcode\n")
Toast.makeText(context, sb.toString(), Toast.LENGTH_SHORT).show()
// Save mes, doesnt work
DocumentActivity().saveCell(barcode)
}}
You are creating another object of DocumentActivity and saving the value in variable for that object, so it will not reflect in cuurent object. Try to make variable static and then update it from broadcasteceiver. For eg variable in DocumentActivity is barCodeVal so,
in DocumentActivity
companion object{
var barcodeVal = //some default value
}
Then from broabcastreceiver
DocumentActivity.barcodeVal = barcode