I am developing an app in which several Activitis bound to a sevice once they become visible to the user. During start up, each Activity needs to:
check some status flag of the service, based on which some UI elements are configured
check wether an adapters is enabled whose reference is inside the service
execute some functions of the Service
Since the applications should not get updates in the background, I bind to the service at onStart() and unbind at onStop(). I.e. in have something like this:
override fun onStart() {
super.onStart()
Intent(context, MyService::class.java).also { intent ->
bindService(intent, serviceCallback, Context.BIND_AUTO_CREATE)
}
}
Now I want to perform the above mentionned actions inside onResume.
override fun onResume() {
super.onResume()
// check flags
// check adapter status
// excute functions of service
}
The problem is that binding to a service is asynchronous and I do not have a valid reference to the Service's binder inside onResume(). Consequently, the app will crash with a nullpointer exception.
Approach 1: Using lateinit
I tried solving this problem using the lateinit keyword. I.e. I define the reference to the binder as
private lateinit var myBinder: MyService.LocalBinder
Problem: I cannot guarantee that the binder is initialized as it is asynchronous. Thus, the app will crash.
Approach 2: Waiting for callback in while loop
In my service callback, I set a flag as follows:
val serviceCallback = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
myBinder = service as MyService.LocalBinder
isServiceBounded = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
isServiceBounded = false
myBinder = null
}
}
Then inside onResume, I block the Activity until the flag is true
override fun onResume() {
super.onResume()
while(!isServiceBounded){
// block and wait
}
}
Problem: Doesn't work either. The app will stop responding and crashes.
Approach 3: Using suspended functions and Kotlin coroutines
A suspended function will not continue unless it has received a return value. This, I can use it to wait for an event. So I tried something like this:
override fun onStart() {
super.onStart()
CoroutineScope(Dispatchers.Main).launch {
bindServiceAndWait(this#Activityname)
}
}
suspend fun bindServiceAndWait(context: Context): Boolean{
Intent(context, MyService::class.java).also { intent ->
bindService(intent, serviceCallback, Context.BIND_AUTO_CREATE)
}
return isServiceBounded // This is the flag from the callback
}
Problem: This suspended function does not actually wait for the callback. It just returns the current value of isServiceBounded.
I found a similar solution here, but I do not quite understand this solution as it has a global service callback (ServiceConnection) as well as a local one inside the suspended function. Also, I don't understand how to I could unbound in this provided example.
What is the proper way of doing this?
you simply can't ensure that service will be bounded until onResume gets called. why won't you introduce flag isResumed, set it in onResume (unset in onPause) and line below check if (isResumed && isServiceBounded).... yes, there is a chance that isServiceBounded = false in onResume, so same if check put in onServiceConnected
I have many share buttons on my App. When a share button is pressed, a chooser is showed to the user so he can select an app to share the content. I wanted to know what the user chose so I decided to use a BroadcastReceiver with the Intent.createChooser() method.
But I have multiple share buttons across the app, so I defined the following class:
class MyBroadcastReceiver(val listener: MyBroadcastReceiverListener) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
listener.handleShare()
}
interface MyBroadcastReceiverListener {
fun handleShare()
}
}
I want to use this class from different places that implement MyBroadcastReceiverListener (Activity1, Activity2, Activity3, etc.) so I can perform the corresponding task on override fun handleShare() at each place. The problem I'm facing is that I have to do this before using the Intent.createChooser().
var receiver = Intent(this, MyBroadcastReceiver::class.java) // how can I pass args 🧐 ?
var pi = PendingIntent.getBroadcast(this, 0, receiver, PendingIntent.FLAG_UPDATE_CURRENT)
Intent.createChooser(..., ..., pi.getIntentSender());
Because I have to provide MyBroadcastReceiver in a static manner, I can't pass arguments (listener in this case) to MyBroadcastReceiver. Is there a way to address this problem? Thanks for your support! 😊
Story: To run a block of code after users choose an app from the app chooser dialog. You shouldn't pass your activity as a listener because this can leak the activity (the application might keep a reference to the activity even it got destroyed).
Solution: Using EventBus to achieve your goal.
Step 1: Add EventBus to your project via gradle
implementation 'org.greenrobot:eventbus:3.2.0'
Step 2: Defines events
object OnShareEvent
Step 3: Register/unregister listening event from your activity, such as Activity1.
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
#Subscribe(threadMode = ThreadMode.MAIN)
fun handleShare(event: OnShareEvent) {
// TODO: Your code logic goes here
}
Step 4: Post events
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
EventBus.getDefault().post(OnShareEvent)
}
}
Context: No receiver is declared in the manifest since I am not declaring a new receiver.
I am a bit confused about why the receiver in MainActivity does not receieve the broadcast sent from the recycler adapter.
RecyclerAdapter
holder.checkBox.setOnClickListener {view ->
item.completed = holder.checkBox.isChecked
Log.i("wow", "is checked: ${holder.checkBox.isChecked}")
val intent = Intent().apply {
addCategory(Intent.CATEGORY_DEFAULT)
setAction(changeCompletedForDeck)
putExtra(changeCompletedForDeckItemID, item)
}
LocalBroadcastManager.getInstance(view.context).sendBroadcast(intent)
MainActivity
private lateinit var broadcastReceiver: BroadcastReceiver
broadcastReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//get deck, if deck != null then update the checkmark response
if (intent?.action == DeckRecyclerAdapter.changeCompletedForDeck) {
val deck = intent?.extras?.getParcelable<Deck>(DeckRecyclerAdapter.changeCompletedForDeckItemID)
Log.i("wow", "${deck?.title}")
deck?.let { deck ->
globalViewModel.update(deck)
}
}
}
}
val filter = IntentFilter(DeckRecyclerAdapter.changeCompletedForDeck)
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, filter)
//Destroy the BroadcastReceiver
override fun onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)
super.onDestroy()
}
Your problem is Intent action . See at the time of register you have not provided any action so receiver will not be identified by the system.
You can define a specific action with IntentFilter and use the same action during register and sendBroadcast.
To identify different conditions you can do two things.
you can set data in Bundle and validate the bundle value inside onReceive()
you can also add multiple actions to IntentFilter and validate the action inside onReceive() See this.
So with the first way have a constant action in MainActivity:-
companion object{
const val BROADCAST_ACTION="LIST_CHECK_ACTION"
}
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter(BROADCAST_ACTION)).
Then for sending broadcast use the code below addCategory(Intent.CATEGORY_DEFAULT) is not required:-
val intent = Intent().apply {
action = MainAcvity.BROADCAST_ACTION
putExtra("item", item)
}
LocalBroadcastManager.getInstance(view.context).sendBroadcast(intent)
PS- However i don't think you should be using a Broadcastreceiver just to provide a callback from Adapter its purpose is more than that. You should be using a callback listener for it . Since RecyclerView.Adapter will binds to a UI component a callback interface will be fine . I think a broadcastReceiver is overkill in this usecase .
I am registering BroadcastReceiver in application class and its register method is getting called and completed successfully without issue during application start. I am sending a broadcast from the module. I can print the action, it is there. But the app is not catching the broadcast. I don't know where I am doing wrong.
Below is the method to register receiver.
fun registerRecordingDataReceiver () {
info("AppReceiver register called=>${CommonsDataVars.TEMPERATURE.action()}")
val temperatureCompleted = CommonsDataVars.TEMPERATURE.action()
val temperatureCompletedIntentFilter = IntentFilter(temperatureCompleted)
val broadcastManager = LocalBroadcastManager.getInstance(this)
broadcastManager.registerReceiver(appReceiver,temperatureCompletedIntentFilter)
info("AppREceiver register completed")
}
Part of Broadcast Receiver
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.getAction()
debug("Action in App Receiver =>"+action)
if (action == CommonsDataVars.TEMPERATURE.action()) {
info("AppReceiver****************************************")
sendBroadcast part:
info("TEMPERATURE register called=>${CommonsDataVars.TEMPERATURE.action()}")
val intent = Intent()
intent.setAction(CommonsDataVars.TEMPERATURE.action())
//intent.putExtra(CommonsDataVars.TEMPERATURE.name, temperatureData)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
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!