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)
}
}
Related
Is it possible to handle events like when notification appears the action like auto refreshing, initmethod in Fragment. I have workmanager implemented I could do it using sharepreferences, but it is not instant and user has to refresh it by himself.
I made a little research and there is kotlin channels, do they works instantly, automatically iwthout user action?
EDIT:
Notification Service
override fun onMessageReceived(message: RemoteMessage) {
// sending broadcast
val intent = Intent()
intent.action = "notification_appear"
intent.putExtra("notification_appear", true)
[...]
}
MyFragment where I would like to receive this
onViewCreated
createReceiver()
LocalBroadcastManager.getInstance(requireContext())
.registerReceiver(broadcastReceiver, IntentFilter("notification_appear"))
refreshWhenChangeToolbarPosition()
just method in teh same fragment
private fun createReceiver() = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val isNotificationAppears = intent?.getBooleanExtra("notification_appear", false) ?: return
Log.d("notification_appear", "$isNotificationAppears")
if(isNotificationAppears) {
initToolbar()
}
when(intent?.action) {
"notification_appear" -> initToolbar()
}
}
}
Unfortunately I haven't receive any logs from onReceive method...
why?
EDIT2:
It works I also should send using LocaoBroadcastManager
// sending broadcast
val intent = Intent()
intent.action = "notification_appear"
intent.putExtra("notification_appear", true)
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
Using LiveData
LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.
There are multiple options, the common way is to send broadcast like this :
val intent = Intent("YOUR_ACTION")
intent.putExtra("key","value")
sendBroadcast(intent)
from your MessagingService (such as FirebaseMessagingService), then register broadcast in your fragment (preferably in onViewCreated() ), don't remember to unregister your broadcast.
the other option is using LiveData or other observer pattern libraries like RxJava.
in this case you have to define your observable object in your service class then observe it from your fragment.
Note that if using RxJava you have to take care of disposing the observeable. LiveData is a lifecycle aware component so you don't need to worry about the memory leak or emitting while fragment is not available.
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'm trying to create an app that can broadcast Android views on the Chromecast, and I thought I found something promising in CastRemoteDisplayLocalService. I created a simple test app but found the callback onCreatePresentation was never called when I casted my device. After some searching I discovered it was because my application was not published as a Remote Display Application but a Custom Application Receiver from the Google Cast Developer Console.
Unfortunately when I try to create a new application from the console, Remote Display Application is not an option. After some searching, I came across this Stack Overflow question that said Remote Display API is now deprecated. There is an interface called CastRemoteDisplayApi which is marked as deprecated, but the classes I have been trying to use are not marked as such.
This leads me to wondering if CastRemoteDisplayLocalService and all other Remote Display classes not marked as deprecated are in fact deprecated and unusable, or if perhaps the functionality was shifted to work in a Custom Receiver by configuring it to accept remote displays.
This is what the relevant code looks like right now:
MainActivity.kt
private fun startCastService() {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
val notificationSettings = CastRemoteDisplayLocalService.NotificationSettings.Builder().setNotificationPendingIntent(pendingIntent).build()
CastRemoteDisplayLocalService.startService(this, CastRemoteDisplayLocalServiceImpl::class.java, "2839EC8D", castDevice, notificationSettings, object : CastRemoteDisplayLocalService.Callbacks {
override fun onRemoteDisplaySessionEnded(p0: CastRemoteDisplayLocalService?) {
Log.d(TAG, "onRemoteDisplaySessionEnded")
}
override fun onRemoteDisplaySessionError(p0: Status?) {
Log.d(TAG, "onRemoteDisplaySessionError")
}
override fun onRemoteDisplaySessionStarted(p0: CastRemoteDisplayLocalService?) {
Log.d(TAG, "onRemoteDisplaySessionStarted")
}
override fun onServiceCreated(p0: CastRemoteDisplayLocalService?) {
Log.d(TAG, "onServiceCreated")
}
})
}
CastRemoteDisplayLocalServiceImpl.kt
class CastRemoteDisplayLocalServiceImpl : CastRemoteDisplayLocalService() {
val TAG = "CastRemoteDisplayLoc..."
// This function gets called
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
}
// This function does not get called
override fun onCreatePresentation(p0: Display?) {
Log.d(TAG, "onCreatePresentation")
}
override fun onDismissPresentation() {
Log.d(TAG, "onDismissPresentation")
}
}
If there's a way with the Custom Application Receiver to get the onCreatePresentation callback that would solve this, I'm having difficulty finding it. If CastRemoteDisplayLocalService is in fact deprecated, is there another way to easily cast Android views to a Chromecast? Thanks!
I am using Room database with LiveData. In Main activity I am showing data for current day. But when new day comes and onCreate wasn't called, views shows data for previous day. How can I properly refresh my data/views in onResume?
MainActivity:
mTodayViewModel = ViewModelProviders.of(this).get(TodayDataViewModel::class.java)
val todayDataObserver = Observer<CoffeeProductivityData> { todayData ->
... update views here }
mTodayViewModel.getTodayData().observe(this, todayDataObserver)
ViewModel:
class TodayDataViewModel(application: Application) : AndroidViewModel(application) {
private val mRepository: CoffeeProductivityRepository = CoffeeProductivityRepository(application)
private val mTodayData: LiveData<CoffeeProductivityData> by lazy {
mRepository.getTodayData()
}
fun getTodayData(): LiveData<CoffeeProductivityData> {
return mTodayData
}}
Repository:
private var mCoffeeProductivityDao: CoffeeProductivityDao
private var mTodayData: LiveData<CoffeeProductivityData>
private var mUtilities: Utilities
init {
val database: CoffeeProductivityDatabase = CoffeeProductivityDatabase.getDatabase(application)!!
mCoffeeProductivityDao = database.coffeeProductivityDao()
mUtilities = Utilities()
mTodayData = mCoffeeProductivityDao.getTodayData(mUtilities.getTodayDate())
}
// Wrapper for getting current day data
fun getTodayData(): LiveData<CoffeeProductivityData> {
return mTodayData
}
Query from DAO:
#Query("SELECT * from coffee_productivity WHERE date LIKE :todayDate")
fun getTodayData(todayDate: String): LiveData<CoffeeProductivityData>
I think your best option is to listen to the ACTION_TIME_TICK broadcast action.
Here's an example: https://gist.github.com/sourabh86/6826730
From the documentation:
The current time has changed. Sent every minute. You cannot receive this through components declared in manifests, only by explicitly registering for it with Context.registerReceiver()
if (time == midnight)
refreshDataManually();
Check this question out, on how to refresh your LiveData manually.
I couldn't make LiveData to properly reset, so I ended restarting activity. I decided to do so because it is pretty rare situation in my app use case.
Broadcast receiver is registered in activity onCreate, and unregistered in onDestroy.
Here is broadcast receiver that listens if date changed:
// Broadcast receiver for detecting change of day (need to refresh MainActivity)
private val mDayChangedReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_DATE_CHANGED) {
isActivityRefreshNeeded = true
Log.d("mDayChangedReceiver", "Received DAY_CHANGED broadcast")
}
}
}
Then I check in onResume of MainActivity if boolean isActivityRefreshNeeded is true. If it is, I reset activity:
// If User has changed settings and navigated via back button, or day changed, refresh activity
when (isActivityRefreshNeeded) {
true -> {
isActivityRefreshNeeded = false
refreshActivity()
}
}
It is not the best solution, but as I said, it is pretty rare situation when this refresh is needed.
If someone have a better solution, I will be happy to see and implement it.
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.