How can I display a notification if the battery becomes less than 10% in an Android studio by Kotlin using a broadcast receiver?
Create a new Android project with this in the
Add the AndroidX support library to your module's build.gradle (where the other implementations are:
dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
}
MainActivity.kt Imports:
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat
import kotlin.concurrent.thread
MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
thread {
val mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val mChannel = NotificationChannel("default", "default", NotificationManager.IMPORTANCE_DEFAULT)
mNotificationManager.createNotificationChannel(mChannel)
while (true) {
val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { ifilter ->
this.registerReceiver(null, ifilter)
}
val batteryPct: Float = batteryStatus?.let { intent ->
val level: Int = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale: Int = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
level * 100 / scale.toFloat()
} ?: 100f
if (batteryPct <= 10) {
val notif = NotificationCompat.Builder(this, "default").setContentText("Low Battery: ${batteryPct.toInt()}%").setSmallIcon(R.drawable.ic_launcher_foreground).build()
mNotificationManager.notify(1, notif) // re-use notification in tray
}
Thread.sleep(20000) // check every 20 seconds
}
}
}
}
Add permission in manifest. Register your broadcast receiver. Read more in this tutorial
https://developer.android.com/training/monitoring-device-state/battery-monitoring
I have implemented SMSBroadcast Receiver functionality in native android (Kotlin) code.
The functionality is like whenever a new text message (SMS) is received I need to call an api (even the app is in background).
When i run as standalone android application its working fine. But how to integrate this with my react-native app?
The below is the code i wrote natively.
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.telephony.SmsMessage
import android.util.Log
import android.widget.Toast
import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil
import com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier
class SMSBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.i(
TAG,
"Intent received: " + intent.action
)
if (intent.action === SMS_RECEIVED) {
val bundle = intent.extras
var data = ""
if (bundle != null) {
val pdus =
bundle["pdus"] as Array<Any>?
val messages =
arrayOfNulls<SmsMessage>(pdus!!.size)
for (i in pdus.indices) {
messages[i] =
SmsMessage.createFromPdu(pdus[i] as ByteArray)
}
if (messages.size > -1) {
data = messages[0]!!.messageBody
Log.i(
TAG,
"Message recieved: $data"
)
}
}
Toast.makeText(context,"Message received : $data", Toast.LENGTH_LONG).show()
// var accessToken = AsyncLocalStorageUtil.getItemImpl(ReactDatabaseSupplier.getInstance(context).get(), "");
// if (accessToken != null){
//
// }
if(listener!= null)
{
listener?.onSmsReceive(data)
}
}
}
companion object {
private const val SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
private const val TAG = "SMSBroadcastReceiver"
var listener : OnCustomSmsListener ?= null
}
}
** Package suggestions are also welcome **
This is the eventbus implementation using Broadcast channel. (RxJava2 not allowed :( )
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.*
import kotlin.coroutines.CoroutineContext
class EventBus(override val coroutineContext: CoroutineContext
= Dispatchers.Default) :CoroutineScope {
#ExperimentalCoroutinesApi
private val channel = BroadcastChannel<Any>(1)
#ExperimentalCoroutinesApi
suspend fun send(event: Any) {
channel.send(event)
}
#ExperimentalCoroutinesApi
fun subscribe(): ReceiveChannel<Any> =
channel.openSubscription()
#ExperimentalCoroutinesApi
inline fun <reified T> subscribeToEvent() =
subscribe().let {
produce<T>(coroutineContext) {
for (t in it){
if(t is T)
send(t as T)
}
}
}
}
And here is my testing code.
#Test
fun testEventBus(){
val bus = EventBus()
var i = 1;
var isFinish = false
CoroutineScope(Dispatchers.IO).launch{
println("launching_subsc")
bus.subscribe().consumeEach {
println("received $it")
assert(it == i++)
isFinish = (it == 5)
}
withContext(bus.coroutineContext){
}
}
bus.launch {
delay(500)
for (j in 1..5) {
println("sending $j")
bus.send(j)
sleep(500)
}
}
while (!isFinish)
sleep(50)
}
This test works good, but I want to remove the delay(500) and yet expect the test to work.
If I remove the delay(500) now, the output is
launching_subsc
sending 1
sending 2
received 2
...
or
sending 1
launching_subsc
sending 2
received 2
...
My actual need is that in actual project scenario I want the data to be published after the subscription, again, if there is no subscriber, the events need to be dropped.
So if there is a subscription call before publishing call(irrespective of Dispatchers) the subscriber must receive all the events.
I have tried using the same scope/dispatcher, nothing is working.
So I wrote a Flutter App about 4 Months ago. Now I wanted to do a small change, but I can't compile the app anymore, because GeneratedPluginRegistrant.registerWith(this) doesn't work anymore, I didn't change the Kotlin code only the Dart code.
The "this" in "GeneratedPluginRegistrant.registerWith(this)" shows me this error:
Type mismatch.
Required: FlutterEngine!
Found: MainActivity
The MainActivity Class:
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.view.FlutterMain
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this) // here is the error: Type mismatch. Required: FlutterEngine! Found: MainActivity
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "helloFromNativeCode") {
val greetings = helloFromNativeCode()
result.success(greetings)
}
}
}
private fun helloFromNativeCode(): String {
return "Hello from Native Android Code"
}
companion object {
private const val CHANNEL = "flutter.native/helper"
}
}
And if is use:
import io.flutter.embedding.android.FlutterActivity
instead of
import io.flutter.app.FlutterActivity
I can use
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
but have trouble with:
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "helloFromNativeCode") {
val greetings = helloFromNativeCode()
result.success(greetings)
}
}
because i get an error on flutterView:
Unresolved reference: flutterView
The code would look like this:
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.view.FlutterMain
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result -> // here is the error
if (call.method == "helloFromNativeCode") {
val greetings = helloFromNativeCode()
result.success(greetings)
}
}
}
private fun helloFromNativeCode(): String {
return "Hello from Native Android Code"
}
companion object {
private const val CHANNEL = "flutter.native/helper"
}
}
I hope someone can help me.
Instead of flutterView use flutterEngine.getDartExecutor().
I spent days trying to figure out how to add a Flutter UI to my existing Android App. The biggest challenge was getting the MethodChannel to work with FlutterActivity being called from MainActivity. I know this is a little different than the question asked here, but this post was returned when I did searches for 'Android FlutterActivity MethodChannel'. After going through many resources on how to do this, I finally found my solution here:
https://github.com/flutter/samples/tree/master/add_to_app/android_using_plugin/app/src/main/java/dev/flutter/example/androidusingplugin
Initially, in Android Studio, with the existing app opened, I tapped File, New, New Module, Flutter Module. I received an error and had to perform manual steps.
My objective is to launch FlutterActivity (opens main.dart in the flutter_module) in MainActivity - onCreate, then develop Flutter 'screens' leveraging as much native Flutter code as possible, with limited Platform calls using the MethodChannel. As I develop replacement Flutter code, I will continue to comment on the existing Android Code.
Here is what finally worked for me:
../App_Project/Android/Existing_Android_App/settings.gradle
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(settingsDir.parentFile, '../flutter_module/.android/include_flutter.groovy'))
include ':flutter_module’
project(':flutter_module’).projectDir = new File('../../flutter_module’)
rootProject.name=‘existing_android_app’
../App_Project/Android/Existing_Android_App/app/build.gradle
dependencies {
…
implementation project(':flutter')
}
../App_Project/Android/Existing_Android_App/app/src/main/AndroidManifest.xml
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize" />
../App_Project/Android/Existing_Android_App/app/src/main/java/com/existing_android_app/MainActivity.java
package com.existing_android_app;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends AppCompatActivity {
final String ENGINE_ID = "1";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FlutterEngine flutterEngine = new FlutterEngine(this);
flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine);
MethodChannel channel = new MethodChannel(flutterEngine.getDartExecutor(), "com.existing_android_app/myMethodChannel");
channel.setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
#Override
public void onMethodCall(#NonNull MethodCall call, #NonNull MethodChannel.Result result) {
String url = call.argument("url");
if (call.method.equals("openBrowser")) {
openBrowser(url);
}
else {
result.notImplemented();
}
}
});
startActivity(FlutterActivity.withCachedEngine(ENGINE_ID).build(this));
}
void openBrowser(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.startActivity(intent);
}
}
../App_Project/flutter_module/lib/home_page.dart
class AppHomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<AppHomePage> {
static const platform = const MethodChannel(‘com.existing_android_app/myMethodChannel’);
Future<void> _openBrowser() async {
try {
final int result = await platform.invokeMethod('openBrowser', <String, String> { 'url': "http://bing.com" });
}
catch (e) {
print('***** _openBrowser error: ' + e.toString());
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: CustomAppBar(),
body: Column(
children: <Widget>[
RaisedButton(
label: Text('Search',
style: TextStyle(fontSize: 18.0),
),
onPressed: () { _openBrowser(); },
) // RaisedButton.icon
], // Widget
) // Column
) // Scaffold
); // SafeArea
}
You should use
import io.flutter.embedding.android.FlutterActivity;
and declare your patformChannel in
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
// Note: this method is invoked on the main thread.
// TODO
}
);
}
for more you can check the documentation: https://flutter.dev/docs/development/platform-integration/platform-channels
You can use method channel and flutter engine like this.
private val CHANNEL = "adb"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler { call, result ->
//You can use your custom function for example:
if (call.method.equals("checkingadb")) {
checkingadb(call, result)
} else {
result.notImplemented()
}
}
}
private fun checkingadb(call: MethodCall, result: MethodChannel.Result) {
if (Settings.Secure.getInt(this.getContentResolver(), Settings.Secure.ADB_ENABLED, 0) === 1) {
// debugging enabled
result.success(1)
} else {
// debugging is not enabled
result.success(0)
}
}
To pass flutter Engine value we can use provideFlutterEngine(this) method. And for flutterView we can use flutterEngine.dartExecutor. Here is the url for code snippet ,have answered it earlier
https://stackoverflow.com/a/67698834/11887774 .
Hello I'm using RxAndroidBLE to detect a BLE device. On android 6 >= everything seems to work okay but not on a 4.3 device.
My app can only discover the desirable BLE device only once at start. After the device has been discovered no more new discoveries at all until I restart the app. Any advice would be highly appreciated.
Below minimum (not)working code example:
MainActivity
import android.content.Context
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.util.Log
import com.polidea.rxandroidble.RxBleClient
import com.polidea.rxandroidble.exceptions.BleScanException
import com.polidea.rxandroidble.scan.ScanResult
import com.polidea.rxandroidble.scan.ScanSettings
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startLeScan(applicationContext)
}
private var rxBleClient: RxBleClient? = null
private var scanSubscription: Subscription? = null
private var handler: Handler? = null
private var timer: Timer? = null
private var timerTask: TimerTask? = null
private var delay: Int = 0
private fun isScanning(): Boolean {
return scanSubscription != null
}
fun startLeScan(context: Context) {
rxBleClient = MyaPP.getRxBleClient(context)
if (isScanning()) {
scanSubscription?.unsubscribe()
} else {
scanSubscription = rxBleClient?.scanBleDevices(
com.polidea.rxandroidble.scan.ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build())
?.observeOn(AndroidSchedulers.mainThread())
//?.doOnNext(this::newDevicesFound)
?.doOnUnsubscribe(this::clearSubscription)
?.subscribe(this::newDevicesFound, this::onScanFailure)
}
if(handler == null) {
handler = Handler()
timer = Timer(false)
timerTask = object : TimerTask() {
override fun run() {
handler?.post {
if (delay > 7) {
delay = 0
val service = Executors.newSingleThreadExecutor()
service.submit(Runnable {
//startLeScan(context)
})
} else {
delay = delay + 1
}
}
}
}
timer?.scheduleAtFixedRate(timerTask, 0, 300)
}
}
private fun newDevicesFound(devices: ScanResult) {
Log.d("WHYY??", devices.bleDevice.name)
}
fun stopScan() {
scanSubscription?.unsubscribe()
destroy()
}
private fun clearSubscription() {
scanSubscription = null
}
private fun onScanFailure(throwable: Throwable) {
if (throwable is BleScanException) {
handleBleScanException(throwable)
}
}
private fun handleBleScanException(bleScanException: BleScanException) {
val text: String
when (bleScanException.reason) {
BleScanException.BLUETOOTH_NOT_AVAILABLE -> text = "Bluetooth is not available"
BleScanException.BLUETOOTH_DISABLED -> text = "Enable bluetooth and try again"
BleScanException.LOCATION_PERMISSION_MISSING -> text = "On Android 6.0 location permission is required. Implement Runtime Permissions"
BleScanException.LOCATION_SERVICES_DISABLED -> text = "Location services needs to be enabled on Android 6.0"
BleScanException.SCAN_FAILED_ALREADY_STARTED -> text = "Scan with the same filters is already started"
BleScanException.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> text = "Failed to register application for bluetooth scan"
BleScanException.SCAN_FAILED_FEATURE_UNSUPPORTED -> text = "Scan with specified parameters is not supported"
BleScanException.SCAN_FAILED_INTERNAL_ERROR -> text = "Scan failed due to internal error"
BleScanException.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES -> text = "Scan cannot start due to limited hardware resources"
BleScanException.UNDOCUMENTED_SCAN_THROTTLE -> text = String.format(
Locale.getDefault(),
"Android 7+ does not allow more scans. Try in %d seconds",
secondsTill(bleScanException.retryDateSuggestion)
)
BleScanException.UNKNOWN_ERROR_CODE, BleScanException.BLUETOOTH_CANNOT_START -> text = "Unable to start scanning"
else -> text = "Unable to start scanning"
}
Log.w("EXCEPTION", text, bleScanException)
}
private fun secondsTill(retryDateSuggestion: Date?): Long {
if (retryDateSuggestion != null) {
return TimeUnit.MILLISECONDS.toSeconds(retryDateSuggestion.time - System.currentTimeMillis())
}
return 0
}
private fun destroy() {
timer?.cancel()
handler?.removeCallbacks(timerTask)
handler = null
timerTask = null
timer = null
}
}
MyaPP
import android.app.Application
import android.content.Context
import com.polidea.rxandroidble.RxBleClient
import com.polidea.rxandroidble.internal.RxBleLog
class MyaPP: Application() {
private var rxBleClient: RxBleClient? = null
companion object {
fun getRxBleClient(context: Context): RxBleClient? {
val application = context.applicationContext as MyaPP
return application.rxBleClient
}
}
override fun onCreate() {
super.onCreate()
rxBleClient = RxBleClient.create(this)
RxBleClient.setLogLevel(RxBleLog.DEBUG)
}
}
build.gradle
compile "com.polidea.rxandroidble:rxandroidble:1.5.0"
implementation 'io.reactivex:rxandroid:1.2.1'
manifest
<application
android:name=".MyaPP"
Your code looks a lot like the library's sample app (version 1.5.0, branch master-rxjava1). I have checked that recently on Android 4.4.4 which is the oldest I have and it worked fine. There were no API changes between 4.3 and 4.4.
What you may be experiencing is a behaviour specific to your device (feel free to share your phone model) in which it only callbacks for the first time it scans a particular peripheral. There are some threads about this topic already like this one.