I have been researching how to schedule an alarm targeting devices Oreo +.
My current approach is using AlarmManager, a Ringtone service and broadcast receiver. I got the alarm to ring and display notification but the notification and the alarm does not fire for Oreo + devices.
I learned that using JobIntentService is a good practice due to Doze and other background limitations. Currently, when using a JobIntentService, I get an error:
java.lang.RuntimeException: An error occurred while executing doInBackground()
Am I doing this wrong? Do I really need to implement an AsyncTask?
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val alarmStatus: String = intent!!.getStringExtra("alarmStatus")
val serviceIntent = Intent(context, RingtoneService::class.java)
serviceIntent.putExtra("alarmStatus", alarmStatus)
//context!!.startService(serviceIntent)
RingtoneService.enqueueWork(context, serviceIntent)
}
}
class RingtoneService : JobIntentService() {
// Enqueing work to do in this service.
companion object {
val SERVICE_JOB_ID = 1
fun enqueueWork(context: Context?, work: Intent) {
if (context != null) {
enqueueWork(context, RingtoneService::class.java, RingtoneService.SERVICE_JOB_ID, work)
}
}
}
override fun onHandleWork(intent: Intent) {
onHandleIntent(intent)
}
private fun onHandleIntent(intent: Intent) {
//handling of notification goes here
}
}
you can try to update your support libraries to the latest and greatest and put some number > 1 (to avoid having two different intent services with the same Id). If it doesn't help you might want to go deeper to this rabbit hole: https://issuetracker.google.com/issues/63622293
One of the suggested fixes there is to use a wrapper class around the JobIntentService:
package android.support.v4.app;
import android.content.Intent;
public abstract class FixedJobIntentService extends JobIntentService {
#Override
GenericWorkItem dequeueWork() {
try {
return new FixedGenericWorkItem(super.dequeueWork());
} catch (SecurityException ignored) {
}
return null;
}
private class FixedGenericWorkItem implements GenericWorkItem {
final GenericWorkItem mGenericWorkItem;
FixedGenericWorkItem(GenericWorkItem genericWorkItem) {
mGenericWorkItem = genericWorkItem;
}
#Override
public Intent getIntent() {
if (mGenericWorkItem != null) {
return mGenericWorkItem.getIntent();
}
return null;
}
#Override
public void complete() {
try {
if (mGenericWorkItem != null) {
mGenericWorkItem.complete();
}
} catch (IllegalArgumentException ignored) {
}
}
}
}
Or you can try to use the new shiny WorkManager from the architecture components (may have issues if you have an app widget (the one for the android home screen) in your app).
https://developer.android.com/topic/libraries/architecture/workmanager/
Related
My scenario is the following:
I'm working on a chat application and I would like to implement some type synchronization service that starts itself when device recovers network connection. Anytime device has network connection again, unsent messages are going to be automatically sent. With independence of application state (foregorund, background or killed).
Options tried:
1. Broadcast Receiver with android.net.conn.CONNECTIVITY_CHANGE
This scenario only works when the application is active (Foreground or Backround) but stops working when app is killed.
2. Foreground service
A notification is going to be shown all the time which is not ideal. Also I want to avoid draining users' battery.
3. AndroidX.Work.Worker
PeriodicWorkRequest networkCheckingPeriodicWork = PeriodicWorkRequest.Builder.
From<ConnectivityChangeWroker>(repeatInterval:30, repeatIntervalTimeUnit: Java.Util.Concurrent.TimeUnit.Minutes, flexInterval:25, flexIntervalTimeUnit:Java.Util.Concurrent.TimeUnit.Minutes)
.SetConstraints(new Constraints.Builder().SetRequiredNetworkType(AndroidX.Work.NetworkType.Connected)
.SetRequiredNetworkType(AndroidX.Work.NetworkType.Unmetered).Build()).Build();
WorkManager.Instance.EnqueueUniquePeriodicWork("", ExistingPeriodicWorkPolicy.Replace, networkCheckingPeriodicWork);
public class ConnectivityChangeWroker : AndroidX.Work.Worker
{
public ConnectivityChangeWroker(Context context, WorkerParameters workerParameters) : base(context, workerParameters)
{
}
public override Result DoWork()
{
try
{
//Start synch service
return Result.InvokeSuccess();
}
catch (Exception)
{
return Result.InvokeFailure();
}
}
}
But in this case, I'm not achieving the desired behaviour. For my undestanding, I just set a periodic work that checks for network connection, and if there is one, runs DoWork() method.
-- EDIT --
4.JobService
Java.Lang.Class javaClass = Java.Lang.Class.FromType(typeof(ConnectivityChangeJob));
ComponentName component = new ComponentName(Application.Context, javaClass);
JobInfo jobInfo = new JobInfo.Builder(1, component)
.SetRequiredNetworkType(Android.App.Job.NetworkType.Any)
.SetOverrideDeadline(5000)
.SetPersisted(true)
.Build();
JobScheduler jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
jobScheduler.Schedule(jobInfo);
[Service(Name = "Extintores.ConnectivityChangeJob", Permission = "android.permission.BIND_JOB_SERVICE")]
public class ConnectivityChangeJob : JobService
{
private Intent startServiceIntent;
public ConnectivityChangeJob()
{
}
public override bool OnStartJob(JobParameters jobParams)
{
//Start synchService
return true;
}
public override bool OnStopJob(JobParameters jobParams)
{
return true; //Reschedule the job
}
}
But in this case, OnStartJob is only fired the first time the applicatio is opened and, apparently, never again.
Is there any way I can achieve what I'm aming for?
-- EDIT --
I want to achieve the same bahaviour as applications as WhatsApp. When it detects network connection again, automatically all unsent messages are going to be send.
I guess the AndroidX.Work.Worker is the best option.
In DoWork you should update databases and send requests.
Besides worker supports long-running workers
Example DownloadWorker:
class DownloadWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
override suspend fun doWork(): Result {
val inputUrl = inputData.getString(KEY_INPUT_URL)
?: return Result.failure()
val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME)
?: return Result.failure()
// Mark the Worker as important
val progress = "Starting Download"
setForeground(createForegroundInfo(progress))
download(inputUrl, outputFile)
return Result.success()
}
private fun download(inputUrl: String, outputFile: String) {
// Downloads a file and updates bytes read
// Calls setForeground() periodically when it needs to update
// the ongoing Notification
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = applicationContext.getString(R.string.notification_channel_id)
val title = applicationContext.getString(R.string.notification_title)
val cancel = applicationContext.getString(R.string.cancel_download)
// This PendingIntent can be used to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
// Create a Notification channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build()
return ForegroundInfo(notificationId, notification)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
// Create a Notification channel
}
companion object {
const val KEY_INPUT_URL = "KEY_INPUT_URL"
const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
}
}
Is there any method to check if my app can start foreground service in background on Android 12 before app actually try to do it and getting ForegroundServiceStartNotAllowedException?
Some method which checks that at least one of the conditions is fulfilled https://developer.android.com/guide/components/foreground-services#background-start-restriction-exemptions
I start my background service as foreground in activity
try {
context.bindService( // p.s. this context of application, it's bounded to app process instead of activity context
getServiceIntent(context),
object : ServiceConnection {
override fun onServiceConnected(
className: ComponentName,
service: IBinder
) {
// service is running in background
startForegroundService(context)
// service should be in foreground mode (at least it should be very soon)
(service as LocalBinder).getService().initialize()
context.unbindService(this)
bindServiceInProgress.set(false)
}
override fun onServiceDisconnected(arg0: ComponentName) {
bindServiceInProgress.set(false)
}
},
AppCompatActivity.BIND_AUTO_CREATE
)
} catch (e: Exception) {
e.printStackTrace()
bindServiceInProgress.set(false)
}
but onServiceConnected can fire too late when activity is not visible anymore but I still want to try start the service in foreground mode if it's allowed for this app, so I need some method to check
I guess we can try catch ForegroundServiceStartNotAllowedException
private fun makeServiceForeground(context: Context): Boolean {
val intent = getServiceIntent(context)
return try {
ContextCompat.startForegroundService(context, intent)
true
} catch (e: Exception) {
e.printStackTrace()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && e is ForegroundServiceStartNotAllowedException) {
// TODO: notification to disable battery optimization
}
false
}
}
I added a native Android home screen widget to my Flutter application.
In my AppWidgetProvider implementation, I'd like to call dart code in my onUpdate() method using a platform channel.
Is this possible? If so, how can this be achieved?
My current Android (Java) code:
package com.westy92.checkiday;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.util.Log;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.view.FlutterNativeView;
public class HomeScreenWidget extends AppWidgetProvider {
private static final String TAG = "HomeScreenWidget";
private static final String CHANNEL = "com.westy92.checkiday/widget";
private static FlutterNativeView backgroundFlutterView = null;
private static MethodChannel channel = null;
#Override
public void onEnabled(Context context) {
Log.i(TAG, "onEnabled!");
backgroundFlutterView = new FlutterNativeView(context, true);
channel = new MethodChannel(backgroundFlutterView, CHANNEL);
}
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.i(TAG, "onUpdate!");
if (channel != null) {
Log.i(TAG, "channel not null, invoking dart method!");
channel.invokeMethod("foo", "extraJunk");
Log.i(TAG, "after invoke dart method!");
}
}
}
Dart code:
void main() {
runApp(Checkiday());
}
class Checkiday extends StatefulWidget {
#override
_CheckidayState createState() => _CheckidayState();
}
class _CheckidayState extends State<Checkiday> {
static const MethodChannel platform = MethodChannel('com.westy92.checkiday/widget');
#override
void initState() {
super.initState();
platform.setMethodCallHandler(nativeMethodCallHandler);
}
Future<dynamic> nativeMethodCallHandler(MethodCall methodCall) async {
print('Native call!');
switch (methodCall.method) {
case 'foo':
return 'some string';
default:
// todo - throw not implemented
}
}
#override
Widget build(BuildContext context) {
// ...
}
}
When I add the widget to my home screen, I see:
I/HomeScreenWidget(10999): onEnabled!
I/HomeScreenWidget(10999): onUpdate!
I/HomeScreenWidget(10999): channel not null, invoking dart method!
I/HomeScreenWidget(10999): after invoke dart method!
However, my dart code does not seem to be receiving the invocation.
I also needed some native android widgets to communicate with my dart code and after some tinkering I managed to do this. In my opinion the documentation on how to do this is a bit sparse but with a bit of creativity I managed to get this to work. I haven't done enough testing to call this 100% production ready, but it seems to be working...
Dart setup
Go to main.dart and add the following top-level function:
void initializeAndroidWidgets() {
if (Platform.isAndroid) {
// Intialize flutter
WidgetsFlutterBinding.ensureInitialized();
const MethodChannel channel = MethodChannel('com.example.app/widget');
final CallbackHandle callback = PluginUtilities.getCallbackHandle(onWidgetUpdate);
final handle = callback.toRawHandle();
channel.invokeMethod('initialize', handle);
}
}
then call this function before running your app
void main() {
initializeAndroidWidgets();
runApp(MyApp());
}
this will ensure that we can get a callback handle on the native side for our entry point.
Now add an entry point like so:
void onWidgetUpdate() {
// Intialize flutter
WidgetsFlutterBinding.ensureInitialized();
const MethodChannel channel = MethodChannel('com.example.app/widget');
// If you use dependency injection you will need to inject
// your objects before using them.
channel.setMethodCallHandler(
(call) async {
final id = call.arguments;
print('on Dart ${call.method}!');
// Do your stuff here...
final result = Random().nextDouble();
return {
// Pass back the id of the widget so we can
// update it later
'id': id,
// Some data
'value': result,
};
},
);
}
This function will be the entry point for our widgets and gets called when our widgets onUpdate method is called. We can then pass back some data (for example after calling an api).
Android setup
The samples here are in Kotlin but should work with some minor adjustments also in Java.
Create a WidgetHelper class that will help us in storing and getting a handle to our entry point:
class WidgetHelper {
companion object {
private const val WIDGET_PREFERENCES_KEY = "widget_preferences"
private const val WIDGET_HANDLE_KEY = "handle"
const val CHANNEL = "com.example.app/widget"
const val NO_HANDLE = -1L
fun setHandle(context: Context, handle: Long) {
context.getSharedPreferences(
WIDGET_PREFERENCES_KEY,
Context.MODE_PRIVATE
).edit().apply {
putLong(WIDGET_HANDLE_KEY, handle)
apply()
}
}
fun getRawHandle(context: Context): Long {
return context.getSharedPreferences(
WIDGET_PREFERENCES_KEY,
Context.MODE_PRIVATE
).getLong(WIDGET_HANDLE_KEY, NO_HANDLE)
}
}
}
Replace your MainActivity with this:
class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler {
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, WidgetHelper.CHANNEL)
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"initialize" -> {
if (call.arguments == null) return
WidgetHelper.setHandle(this, call.arguments as Long)
}
}
}
}
This will simply ensure that we store the handle (the hash of the entry point) to SharedPreferences to be able to retrieve it later in the widget.
Now modify your AppWidgetProvider to look something similar to this:
class Foo : AppWidgetProvider(), MethodChannel.Result {
private val TAG = this::class.java.simpleName
companion object {
private var channel: MethodChannel? = null;
}
private lateinit var context: Context
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
this.context = context
initializeFlutter()
for (appWidgetId in appWidgetIds) {
updateWidget("onUpdate ${Math.random()}", appWidgetId, context)
// Pass over the id so we can update it later...
channel?.invokeMethod("update", appWidgetId, this)
}
}
private fun initializeFlutter() {
if (channel == null) {
FlutterMain.startInitialization(context)
FlutterMain.ensureInitializationComplete(context, arrayOf())
val handle = WidgetHelper.getRawHandle(context)
if (handle == WidgetHelper.NO_HANDLE) {
Log.w(TAG, "Couldn't update widget because there is no handle stored!")
return
}
val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(handle)
// Instantiate a FlutterEngine.
val engine = FlutterEngine(context.applicationContext)
val callback = DartExecutor.DartCallback(context.assets, loader.findAppBundlePath(), callbackInfo)
engine.dartExecutor.executeDartCallback(callback)
channel = MethodChannel(engine.dartExecutor.binaryMessenger, WidgetHelper.CHANNEL)
}
}
override fun success(result: Any?) {
Log.d(TAG, "success $result")
val args = result as HashMap<*, *>
val id = args["id"] as Int
val value = args["value"] as Int
updateWidget("onDart $value", id, context)
}
override fun notImplemented() {
Log.d(TAG, "notImplemented")
}
override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
Log.d(TAG, "onError $errorCode")
}
override fun onDisabled(context: Context?) {
super.onDisabled(context)
channel = null
}
}
internal fun updateWidget(text: String, id: Int, context: Context) {
val views = RemoteViews(context.packageName, R.layout.small_widget).apply {
setTextViewText(R.id.appwidget_text, text)
}
val manager = AppWidgetManager.getInstance(context)
manager.updateAppWidget(id, views)
}
The important thing here is initializeFlutter that will make sure we can get a handle to our entry point. In onUpdate we are then calling channel?.invokeMethod("update", appWidgetId, this) that will trigger the callback in our MethodChannel on the dart side defined earlier. Then we handle the result later in success (at least when the call is successful).
Hopefully this will give you a rough idea on how to achieve this...
First, please ensure that you are invoking FlutterMain.startInitialization() and then FlutterMain.ensureInitializationComplete() before attempting to execute any Dart code. These calls are necessary to bootstrap Flutter.
Second, can you try this same goal using the new experimental Android embedding?
Here is a guide for executing Dart code using the new embedding:
https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
If your code still doesn't work as expected with the new Android embedding then it should be easier to debug what the problem is. Please post back with success, or any new error information.
You need to pass the getFlutterView() from the MainActivity instead of creating a new BackgroundFlutterView:
channel = new MethodChannel(MainActivity.This.getFlutterView(), CHANNEL);
"This" being like:
public class MainActivity extends FlutterActivity {
public static MainActivity This;
#Override
protected void onCreate(Bundle savedInstanceState) {
This = this;
...
}
maybe you can use invokeMethod(String method, #Nullable Object arguments, MethodChannel.Result callback) and use callback to get the fail reason.
FlutterMain is deprecated, use FlutterLoader.
For example (kotlin)
val loader = FlutterLoader()
loader?.startInitialization(context!!)
loader?.ensureInitializationComplete(context!!, arrayOf())
Another thing, when app is in background and you want to communicate with parent app, you need to initialize method channel again, initial initialization from onUpdate won't work then. In that case code in flutter part will be executed in separate isolate.
I am new to Kotlin, and it seems awesome! Though today, I've been trying to do something that in Java was super simple, but I've got totally stuck.
I am using a broadcast receiver to determine when the device is connected/ disconnected from a power source. And all I need to do it update my UI accordingly.
My Code
Here's my BroadcastReceiver classs, and it seems to work fine.
class PlugInReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action == Intent.ACTION_POWER_CONNECTED) {
// Do stuff when power connected
}
else if (action == Intent.ACTION_POWER_DISCONNECTED) {
// Do more stuff when power disconnected
}
}
}
Now in my MainActivity (but somewhere else, later on), I want to update my UI when the intent is fired, for example in the function below, the background color changes.
private fun updateBackgroundColor( newBgColorId: Int = R.color.colorAccent){
val mainLayout = findViewById<View>(R.id.mainLayout)
val colorFade = ObjectAnimator.ofObject(
mainLayout, "backgroundColor", ArgbEvaluator(), oldBgColor, newBgColor)
colorFade.start()
}
The Question
How can I call a function on the MainActivity, or update my UI when the BroadcastReceiver fires an event?
What I've tried so far
I looked into having a static variable somewhere, storing the result of the BroadcastReciever, then an observable in my UI class, watching and calling appropriate function accordingly. Though after Googling how to do this, looks like that's not really a good approach in Kotlin.
Considered trying to run the BroadcastReciever on the UI thread, but that sounds like a terrible idea.
Tried mixing a Java implementation with my Kotlin class, but couldn't get it to work.
Frustratingly I found several very similar questions on SO. However their implementations seem to all use Java-specific features:
Android BroadcastReceiver onReceive Update TextView in MainActivity
How to update UI in a BroadcastReceiver
Calling a Activity method from BroadcastReceiver in Android
How to update UI from BroadcastReceiver after screenshot
I'm sure this is a trivial question for most Android developers, but I am lost! Let me know if you need any more details. Thanks very much in advance!
Sharing the info to register BroadcastReceiver in Kotlin
Step 1. Create BroadcastReceiver in MainActivity.kt
private val mPlugInReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_POWER_CONNECTED -> {
//update your main background color
updateBackgroundColor()
}
Intent.ACTION_POWER_DISCONNECTED -> {
//update your main background color
updateBackgroundColor()
}
}
}
}
Step 2. Create IntentFilter
private fun getIntentFilter(): IntentFilter {
val iFilter = IntentFilter()
iFilter.addAction(Intent.ACTION_POWER_CONNECTED)
iFilter.addAction(Intent.ACTION_POWER_DISCONNECTED)
return iFilter
}
Step 3. Register receiver at onStart()
override fun onStart() {
super.onStart()
registerReceiver(mPlugInReceiver, getIntentFilter())
}
Step 4. Unregister receiver at onStop()
override fun onStop() {
super.onStop()
unregisterReceiver(mPlugInReceiver)
}
If you have custom BroadcastReceiver, you can register using LocalBroadcastManager and create your local IntentFilter
private val mLocalBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
AnyService.UPDATE_ANY -> {
}
}
}
}
private fun getLocalIntentFilter(): IntentFilter {
val iFilter = IntentFilter()
iFilter.addAction(AnyService.UPDATE_ANY)
return iFilter
}
Register local receiver
LocalBroadcastManager.getInstance(applicationContext).registerReceiver(mLocalBroadcastReceiver, getLocalIntentFilter())
Unregister local receiver LocalBroadcastManager.getInstance(applicationContext).unregisterReceiver(mLocalBroadcastReceiver)
The best way to achieve that is to create an abstract method in the BroadcastReceiver, and when onReceive() method is called, you can invoke that method that will be implemented by your activity.
BroadcastReceiver example:
abstract class ConnectionBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//Do the checks or whatever you want
var isConnected = true
broadcastResult(isConnected)
}
protected abstract fun broadcastResult(connected: Boolean)
}
And the code in your activity (in the onCreate or onStart for example). Here you register the broadcast receiver with the method implementation, and here you can update the UI:
var connectionBroadcastReceiver = object : ConnectionBroadcastReceiver() {
override fun broadcastResult(connected: Boolean) {
if(isConnected){
refreshList()
}
}
}
val intentFilter = IntentFilter()
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
this.registerReceiver(connectionBroadcastReceiver, intentFilter)
Don't forget to unregister the receiver in (onPause||onStop||onDestroy), but it's not strictly necessary.
The onReceive(...) method runs on the main thread. You can register your Activity in onStart() and unregister it in onStop(), which will guarantee that your UI is present when the event is received.
In the process of porting an iPhone application over to android, I am looking for the best way to communicate within the app. Intents seem to be the way to go, is this the best (only) option? NSUserDefaults seems much lighter weight than Intents do in both performance and coding.
I should also add I have an application subclass for state, but I need to make another activity aware of an event.
The best equivalent I found is LocalBroadcastManager which is part of the Android Support Package.
From the LocalBroadcastManager documentation:
Helper to register for and send broadcasts of Intents to local objects within your process. This is has a number of advantages over sending global broadcasts with sendBroadcast(Intent):
You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.
It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit.
It is more efficient than sending a global broadcast through the system.
When using this, you can say that an Intent is an equivalent to an NSNotification. Here is an example:
ReceiverActivity.java
An activity that watches for notifications for the event named "custom-event-name".
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Register to receive messages.
// This is just like [[NSNotificationCenter defaultCenter] addObserver:...]
// We are registering an observer (mMessageReceiver) to receive Intents
// with actions named "custom-event-name".
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("custom-event-name"));
}
// Our handler for received Intents. This will be called whenever an Intent
// with an action named "custom-event-name" is broadcasted.
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
String message = intent.getStringExtra("message");
Log.d("receiver", "Got message: " + message);
}
};
#Override
protected void onDestroy() {
// Unregister since the activity is about to be closed.
// This is somewhat like [[NSNotificationCenter defaultCenter] removeObserver:name:object:]
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onDestroy();
}
SenderActivity.java
The second activity that sends/broadcasts notifications.
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Every time a button is clicked, we want to broadcast a notification.
findViewById(R.id.button_send).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
sendMessage();
}
});
}
// Send an Intent with an action named "custom-event-name". The Intent sent should
// be received by the ReceiverActivity.
private void sendMessage() {
Log.d("sender", "Broadcasting message");
Intent intent = new Intent("custom-event-name");
// You can also include some extra data.
intent.putExtra("message", "This is my message!");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
With the code above, every time the button R.id.button_send is clicked, an Intent is broadcasted and is received by mMessageReceiver in ReceiverActivity.
The debug output should look like this:
01-16 10:35:42.413: D/sender(356): Broadcasting message
01-16 10:35:42.421: D/receiver(356): Got message: This is my message!
Here is something similar to #Shiki answer, but from the angle of iOS developers and Notification center.
First create some kind of NotificationCenter service:
public class NotificationCenter {
public static void addObserver(Context context, NotificationType notification, BroadcastReceiver responseHandler) {
LocalBroadcastManager.getInstance(context).registerReceiver(responseHandler, new IntentFilter(notification.name()));
}
public static void removeObserver(Context context, BroadcastReceiver responseHandler) {
LocalBroadcastManager.getInstance(context).unregisterReceiver(responseHandler);
}
public static void postNotification(Context context, NotificationType notification, HashMap<String, String> params) {
Intent intent = new Intent(notification.name());
// insert parameters if needed
for(Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
intent.putExtra(key, value);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
}
Then, you will also need some enum type to be secure of mistakes in coding with strings - (NotificationType):
public enum NotificationType {
LoginResponse;
// Others
}
Here is usage(add/remove observers) for example in activities:
public class LoginActivity extends AppCompatActivity{
private BroadcastReceiver loginResponseReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// do what you need to do with parameters that you sent with notification
//here is example how to get parameter "isSuccess" that is sent with notification
Boolean result = Boolean.valueOf(intent.getStringExtra("isSuccess"));
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//subscribe to notifications listener in onCreate of activity
NotificationCenter.addObserver(this, NotificationType.LoginResponse, loginResponseReceiver);
}
#Override
protected void onDestroy() {
// Don't forget to unsubscribe from notifications listener
NotificationCenter.removeObserver(this, loginResponseReceiver);
super.onDestroy();
}
}
and here is finally how we post notification to NotificationCenter from some callback or rest service or whatever:
public void loginService(final Context context, String username, String password) {
//do some async work, or rest call etc.
//...
//on response, when we want to trigger and send notification that our job is finished
HashMap<String,String> params = new HashMap<String, String>();
params.put("isSuccess", String.valueOf(false));
NotificationCenter.postNotification(context, NotificationType.LoginResponse, params);
}
that's it, cheers!
You could try this: http://developer.android.com/reference/java/util/Observer.html
I found that the usage of EventBus of Guava lib is the simplest way for publish-subscribe-style communication between components without requiring the components to explicitly register with one another
see their sample on https://code.google.com/p/guava-libraries/wiki/EventBusExplained
// Class is typically registered by the container.
class EventBusChangeRecorder {
#Subscribe public void recordCustomerChange(ChangeEvent e) {
recordChange(e.getChange());
}
// somewhere during initialization
eventBus.register(this);
}
// much later
public void changeCustomer() {
eventBus.post(new ChangeEvent("bla bla") );
}
you can add this lib simply on Android Studio by adding a dependency to your build.gradle:
compile 'com.google.guava:guava:17.0'
You could use this: http://developer.android.com/reference/android/content/BroadcastReceiver.html, which gives a similar behavior.
You can register receivers programmatically through Context.registerReceiver(BroadcastReceiver, IntentFilter) and it will capture intents sent through Context.sendBroadcast(Intent).
Note, though, that a receiver will not get notifications if its activity (context) has been paused.
Kotlin: Here's a #Shiki's version in Kotlin with a little bit refactor in a fragment.
Register the observer in Fragment.
Fragment.kt
class MyFragment : Fragment() {
private var mContext: Context? = null
private val mMessageReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//Do something here after you get the notification
myViewModel.reloadData()
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
}
override fun onStart() {
super.onStart()
registerSomeUpdate()
}
override fun onDestroy() {
LocalBroadcastManager.getInstance(mContext!!).unregisterReceiver(mMessageReceiver)
super.onDestroy()
}
private fun registerSomeUpdate() {
LocalBroadcastManager.getInstance(mContext!!).registerReceiver(mMessageReceiver, IntentFilter(Constant.NOTIFICATION_SOMETHING_HAPPEN))
}
}
Post notification anywhere. Only you need the context.
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(Constant.NOTIFICATION_SOMETHING_HAPPEN))```
PS:
you can add a Constant.kt like me for well organize the notifications.
Constant.kt
object Constant {
const val NOTIFICATION_SOMETHING_HAPPEN = "notification_something_happened_locally"
}
For the context in a fragment, you can use activity (sometimes null) or conext like what I used.
I wrote a wrapper that can do this same job, equivalent to iOS using LiveData
Wrapper:
class ObserverNotify {
private val liveData = MutableLiveData<Nothing>()
fun postNotification() {
GlobalScope.launch {
withContext(Dispatchers.Main) {
liveData.value = liveData.value
}
}
}
fun observeForever(observer: () -> Unit) {
liveData.observeForever { observer() }
}
fun observe(owner: LifecycleOwner, observer: () -> Unit) {
liveData.observe(owner) { observer()}
}
}
class ObserverNotifyWithData<T> {
private val liveData = MutableLiveData<T>()
fun postNotification(data: T) {
GlobalScope.launch {
withContext(Dispatchers.Main) {
liveData.value = data
}
}
}
fun observeForever(observer: (T) -> Unit) {
liveData.observeForever { observer(it) }
}
fun observe(owner: LifecycleOwner, observer: (T) -> Unit) {
liveData.observe(owner) { observer(it) }
}
}
Declaring observer types:
object ObserverCenter {
val moveMusicToBeTheNextOne: ObserverNotifyWithData<Music> by lazy { ObserverNotifyWithData() }
val playNextMusic: ObserverNotify by lazy { ObserverNotify() }
val newFCMTokenDidHandle: ObserverNotifyWithData<String?> by lazy { ObserverNotifyWithData() }
}
In the activity to observe:
ObserverCenter.newFCMTokenDidHandle.observe(this) {
// Do stuff
}
To notify:
ObserverCenter.playNextMusic.postNotification()
ObserverCenter.newFCMTokenDidHandle.postNotification("MyData")
Answer of #Shiki could be right in June 2020, but in January 2022, LocalBroadcastManager happened to be deprecated.
After two days of research, I ended up finding that SharedFlow was indicated by Android to "send ticks to the rest of the app so that all the content refreshes periodically at the same time".
Meaning, more or less, what we could expect from the NSNotificationCenter of Swift.
And here is the way I implemented the Shared Flow in my app:
First, you need to create an InAppNotif Singleton, which is actually a shared ViewModel for your activity (be caution to this last point: shared for your activity, not your all app^^)
enum class InAppNotifName {
NotifNameNumber1,
NotifNameNumber2,
NotifNameNumber3
}
object InAppNotif: ViewModel() {
private val _sharedNotif = MutableSharedFlow<InAppNotifName>(0)
val sharedNotif: SharedFlow<InAppNotifName> = _sharedNotif.asSharedFlow()
private fun sendNotif(name: InAppNotifName) {
CoroutineScope(Default).launch {
_sharedNotif.emit(name)
}
}
public fun notifyNotif1() {
sendNotif(InAppNotifName.NotifNameNumber1)
}
public fun notifyNotif2() {
sendNotif(InAppNotifName.NotifNameNumber1)
}
public fun notifyNotif3() {
sendNotif(InAppNotifName.NotifNameNumber1)
}
}
Second Step, only required if you have many Fragments receiving in app notifications, and you don't want to repeat yourself, would be to create an "Receiving Notif" interface
fun AnyReceivingNotif.observeInAppNotif() {
CoroutineScope(Default).launch {
InAppNotif.sharedNotif.collect {
onReceivingInAppNotif(it)
}
}
}
interface AnyReceivingNotif {
suspend fun onReceivingInAppNotif(value: InAppNotifName)
}
By the way, the "suspend" word is useful only if you need to update the UI upon receiving the notification.
Finally, from any object which is to receive InAppNotif, all you would need to do is get it be conform to your AnyReceivingNotif interface, and then complete the onReceivingInAppNotif function
class MyFragment: Fragment(), AnyReceivingNotif {
override suspend fun onReceivingInAppNotif(value: InAppNotifName) {
when (value) {
InAppNotifName.NotifNameNumber1 -> { /* Do complicated things */ }
InAppNotifName.NotifNameNumber2 -> { /* Do some stuff */ }
InAppNotifName.NotifNameNumber3 -> {
withContext(Default){
/* Update the UI */
}
}
}
}
}
You could use weak references.
This way you could manage the memory yourself and add and remove observers as you please.
When you addObserver add these parameters - cast that context from the activity you are adding it in to the empty interface, add a notification name, and call the method to run interface.
The method to run interface would have a function that is called run to return the data that you are passing something like this
public static interface Themethodtorun {
void run(String notification_name, Object additional_data);
}
Create a observation class that invokes a reference with a empty interface.
Also construct your Themethodtorun interface from the context being passed in the addobserver.
Add the observation to a data structure.
To call it would be the same method however all you need to do is find the specific notification name in the data structure, use the Themethodtorun.run(notification_name, data).
This will send a callback to where ever you created an observer with a specific notification name.
Dont forget to remove them when your done!
This is good reference for weak references.
http://learningviacode.blogspot.co.nz/2014/02/weak-references-in-java.html
I am in the process of uploading this code to github. Keep eyes open!