I'm new to android development and Java, and I'm slightly confused about the handling of backwards compatibility when using classes that have been introduced in the latest versions.
I've checked out the android support library and see the various XXXCompat classes available. For the ones I've looked at they appear to pretty much branch on VERSION.SDK_INT and call a new api or an old api.
I am using a support library (com.android.support:support-v4:27.1.1) version that is targeted for a newer api than my targetSdkVersion (25). I was under the impression this was the intended use case? to be able to write code with newer api's but have it work when targeting older sdk.
If so, how is this possible? For instance ContextCompat is has the following for startForegroundService
public static void startForegroundService(#NonNull Context context,
#NonNull Intent intent) {
if (VERSION.SDK_INT >= 26) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
However in the version I am targeting the Context doesn't have the method startForegroundService. If paste this if block into my code, it fails to compile with java: cannot find symbol ....
I can only assume that even if you compiled against a newer api (such that it could resolve the symbols), if those symbols don't exist at runtime, as long a they are not called it is not a problem?
So this is fine for api calls that are abstracted by the XXXCompat classes, but when using new classes like NotificationChannel. I can only import this if upping my targetSdkVersion to > 26. So assuming I do this, then this class is available. All uses of it that I have seen do
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
NotificationChannel channel = ...
}
Does this mean that at runtime, for lower Build.VERSION.SDK_INT the symbol NotificationChannel will not exist? and if I attempted to instantiate this class it on a lower android version, it would crash?
Before Oreo, you can just start your service. For Oreo and higher, the service needs to run in foreground and thus post a notification upon service start otherwise it gets destroyed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
To post notification in Oreo and above you need to create a notification channel, before Oreo you just add your channel id to the notification builder (no need to create a channel). Snippet code from service:
String channelId = "myAppChannel"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// build notification channel
createNotificationChannel(channelId, ...)
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, channelId)
// Build your notification
Notification notification = notificationBuilder
.setOngoing(true)
.setSmallIcon(icon)
.setCategory(Notification.CATEGORY_SERVICE)
.build()
// post notification belonging to this service
startForeground(NOTIFICATION_ID, notification)
When you create createNotificationChannel function, just annotate it with #RequiresApi(26).
Related
Okay, I know my title can be confusing so I'll explain a bit. I have access to an API and I would like that: every hour, my application, in the background, makes a request to this API and sends a notification if the API response contains a more or less recent date. To check if the answer is recent, I am already able to do it but what I would like to know is how to make this request in the background every hour and then how to send the data of this request in a notification (I already know how to create a notification, that's not the problem). I'm stuck for some time, I imagine that the answer will be something related to a server, a domain that I know absolutely nothing about
You can try AlarmManager and BroadcastReceiver with the repeating time 1 hour
Example:
val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else PendingIntent.FLAG_UPDATE_CURRENT
val intent = Intent(context, YourReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, flag)
val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
alarmManager.setRepeating(
AlarmManager.RTC,
timeInMillis,
1000 * 60 * 60, //1h in millis
pendingIntent
)
Then write your YourReceiver and override onReceive function
class YourReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//call api & check your requirements then push notification
}
}
Manifest:
<application>
...
<receiver android:name=".YourReceiver" />
</application>
Or you can try periodic work request:
https://medium.com/#sumon.v0.0/android-jetpack-workmanager-onetime-and-periodic-work-request-94ace224ff7d
If the API already exists, you don't need a server. You just need to schedule a job to be done on the device every hour. You can use WorkScheduler for that. If the API needs to be written, then yes you need a server and need to learn how to write a web service. But that's well beyond the size of a stack overflow question, you can google for a variety of tutorials on that.
I'm developing a system app, and when I attempt to start it up from the home screen in Android 31, it fails and an java.lang.IllegalArgumentException is thrown. Here's what the stack trace looks like:
java.lang.IllegalArgumentException: tech.[brand].[name]: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
at android.app.PendingIntent.checkFlags(PendingIntent.java:375)
at android.app.PendingIntent.getBroadcastAsUser(PendingIntent.java:645)
at android.app.PendingIntent.getBroadcast(PendingIntent.java:632)
at androidx.work.impl.utils.ForceStopRunnable.getPendingIntent(ForceStopRunnable.java:285)
at androidx.work.impl.utils.ForceStopRunnable.isForceStopped(ForceStopRunnable.java:158)
at androidx.work.impl.utils.ForceStopRunnable.forceStopRunnable(ForceStopRunnable.java:185)
at androidx.work.impl.utils.ForceStopRunnable.run(ForceStopRunnable.java:103)
at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:920)
The stack doesn't reach any of my source code and far as I know I'm not calling any intent in my code so far, pending or otherwise. Does anyone know what's going on?
The androidx.work in the stack trace is WorkManager. As per the WorkManager 2.7.0-alpha02:
Make PendingIntent mutability explicit, to fix a crash when targeting Android 12.
Therefore you should upgrade to the latest version, WorkManager 2.7.1 by adding an explicit dependency on that latest version to the dependencies block of your build.gradle file:
implementation "androidx.work:work-runtime:2.7.1"
PendingIntent is a flag in API 21+ .So either you can set it like this:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
}
else {
PendingIntent.FLAG_UPDATE_CURRENT
}
OR
According to the docs here , one should choose FLAG_IMMUTABLE. Use FLAG_MUTABLE in case if some functionality depends on the PendingIntent
val updatedPendingIntent = PendingIntent.getActivity(
applicationContext,
NOTIFICATION_REQUEST_CODE,
updatedIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT // setting the mutability flag
)
With this code, I want to click in a button and appear a notification.
The problem is when in click in my button doesn't appear any notification.
I search in youtube, in example codes and I don't see my error, can you see??
btNotify.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
notification();
}
});
public void notification() {
NotificationCompat.Builder builder = new
NotificationCompat.Builder(this);
builder.setSmallIcon(R.drawable.ic_baseline_access_time_24);
builder.setContentTitle("Time");
builder.setContentText("Isto é um relógio");
NotificationManager notifManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
notifManager.notify(1, builder.build());
}
You didn't mention the android version you are working with.
According to the official documentation you have to provide a channel to the builder constructor when using API level 26+ (Android 8 or higher).
var builder = NotificationCompat.Builder(this, CHANNEL_ID)
It is recommended to create the channel right on start-up of the app since you can not post notifications without it (again, this is valid for API level 26+).
Don't forget to assign a priority to the channel.
You can find the details about creating a channel here.
Anyways, notifications are usually used for providing information to the user while the app is NOT in use.
If you just want an instant notification following a button press, could you also consider using a Toast or a snackbar?
It can be as simple as:
Toast.makeText(yourContext, "Your message", Toast.LENGTH_LONG).show()
Update 2020/08/11
Update according to the android version you mentioned.
The android documentation states the following:
Notice that the NotificationChannel constructor requires an importance, using one of the constants from the NotificationManager class. This parameter determines how to interrupt the user for any notification that belongs to this channel—though you must also set the priority with setPriority() to support Android 7.1 and lower (as shown above).
by using the NotificationCompat builder, you get upward compatibility with android 8+ by creating a channel and providing it to the NotificationCompat constructor. For lower versions the channel is simply ignored.
A priority setting is strictly required for using notifications. For support with versions lower or equal to Android 7.1: you need to set the priority in the builder since the channel (which has a priority setting on its own) is ignored.
Try adding the required importance specification with setPriority in the builder:
val builder ...
.setPriority(NotificationCompat.PRIORITY_DEFAULT) // or any other priority level
Update 2020-08-17
How to create a Channel for API level 26+ as shown in the official documentation:
private fun createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = getString(R.string.channel_name)
val descriptionText = getString(R.string.channel_description)
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
description = descriptionText
}
// Register the channel with the system
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
I am using blescan with scanfilters to detect beacons it's working very fine in foreground and background up to oreo version but when it comes to android pie it's not able to send pending broadcast in background.
ScanSettings settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)).build();
final List<ScanFilter> scanFilters = new ArrayList<>();
scanFilters.add(getScanFilter());
BluetoothAdapter bluetoothAdapter;
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
Intent intent = new Intent(this.getApplicationContext(), MyBroadcastReceiver.class);
intent.putExtra("o-scan", true);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
bluetoothAdapter.getBluetoothLeScanner().startScan(scanFilters, settings, pendingIntent);
public class MyBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
int bleCallbackType = intent.getIntExtra(BluetoothLeScanner.EXTRA_CALLBACK_TYPE, -1);
if (bleCallbackType != -1) {
Log.d(TAG, "Passive background scan callback type: "+bleCallbackType);
ArrayList<ScanResult> scanResults = intent.getParcelableArrayListExtra(
BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT);
// Do something with your ScanResult list here.
// These contain the data of your matching BLE advertising packets
}
}
}
Android 9 introduces several behavior changes, such as limiting background apps' access to device sensors and Wi-Fi scans.
These changes affect all apps running on Android 9, regardless of target SDK version.
Sensors that use the continuous reporting mode, such as accelerometers and gyroscopes, don't receive events.
Android 9 Limited access to sensors in background:
Android 9 limits the ability for background apps to access user input and sensor data. If your app is running in the background on a device running Android 9, the system applies the following restrictions to your app:
Sensors that use the continuous reporting mode, such as accelerometers and gyroscopes, don't receive events.
Sensors that use the on-change or one-shot reporting modes don't receive events.
Solution:
If your app needs to detect sensor events on devices running Android 9 while the app is in the background, use a foreground service.
I an example test Android app using Oreo (API 26) and the the code above (slightly modified) to detect beacons. I am using the Pixel 3 XL (with Pie).
I think that the hard part about this is to know for sure if the code in onRecieve() in MyBroadcastReceiver is actually being run upon detection of a beacon when the device is running on battery only (disconnected from Android-studio and Logcat (USB)).
Using Volley (com.android.volley) to submit a HTTP request to a local http server, I was able to demonstrate that it works as documented - ie. I am able to receive the http request when beacon(s) are detected. However, Volley only sends these these requests when Android is awake or when it periodically wakes up and connects to the network - which in my simple tests was about every 15 minutes (plus some variation), but I did get all the beacon ScanResults on my HTTP server, just in delayed up to 15 minutes. I was even able to remove the app from the list of running apps (you know; swiping up to remove the app) and still see that the onRecieve() in MyBroadcastReceiver was receiving BLE ScanResults.
How do you know that the onRecieve() in MyBroadcastReceiver is being killed? I am very interested to know how you know this.
I have an example app that can to group notifications as in the Telegram messenger.
The build build tools is 26.0.2. Compile and target SDK verions are 27.
For API 26 and above I added some code in onViewCreated() method :
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannelGroup group = new NotificationChannelGroup(NOTIFICATION_GROUP, "messages");
mNotificationManager.createNotificationChannelGroup(group);
NotificationChannel channel = new NotificationChannel("channel_some", "channel_some", NotificationManager.IMPORTANCE_HIGH);
channel.enableLights(true);
channel.setGroup(NOTIFICATION_GROUP);
mNotificationManager.createNotificationChannel(channel);
}
And use other constructor with notification channel id to create NotificationCompat.Builder:
final NotificationCompat.Builder builder = new NotificationCompat.Builder(getActivity(), "channel_some")
But notifications are not grouped as it was in API 24 and 25. Which way is correct to group different notifications (example - Telegram messenger) in API 26 and above?
UPDATE:
It example works with compile and target SDK of 25 version. How to use it with SDK of 26 version and above?