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.
Related
So, I'm trying to collect data from flows in my Foreground service (LifecycleService) in onCreate(), but after the first callback, it is not giving new data.
The code is :
override fun onCreate() {
super.onCreate()
lifecycleScope.launchWhenStarted {
repeatOnLifecycle(Lifecycle.State.STARTED) {
observeCoinsPrices()
}
}
}
I couldn't get lifecycleScope.launch to work in the LifecycleService.onCreate method without it freezing the app, so what I did instead was moved the collector into a method that I use to start the service, assign the Job into a property so I can cancel it when the Service is destroyed.
import kotlinx.coroutines.Job
//...
class MyService : LifecycleService() {
//...
private lateinit var myJob: Job
// my custom method for starting The Foreground service
fun startTheService() {
// call startForeground()
//...
myJob = lifecycleScope.launch {
collectFromFlow()
}
}
override fun onDestroy() {
myJob.cancel()
}
}
In my case, I was wanting to update text in the foreground notification every time a value was emitted to my Flow collector.
Because Flow used in observeCoinsPrices() not replay the latest value (replay < 1). You should change Flow logic
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)
}
}
I have some problem in nested fragment in Kotlin. I have nested fragment with ViewModel. After resuming fragment from back button press all observers on viewModel LiveData triggers again although my data does not changed.
First i googled and tried for define observer in filed variable and check if it is initialized then do not observer it again:
lateinit var observer: Observer
fun method(){
if (::observer.isInitialized) return
observer = Observer{ ... }
viewModel.x_live_data.observe(viewLifecycleOwner ,observer)
}
So at first enter to fragment it works fine and also after resume it does not trigger again without data change but it does not trigger also on data change!
What is going on?
LiveData always stores the last value and sends it to each Observer that is registered. That way all Observers have the latest state.
As you're using viewLifecycleOwner, your previous Observer has been destroyed, so registering a new Observer is absolutely the correct thing to do - you need the new Observer and its existing state to populate the new views that are created after you go back to the Fragment (since the original Views are destroyed when the Fragment is put on the back stack).
If you're attempting to use LiveData for events (i.e., values that should only be processed once), LiveData isn't the best API for that as you must create an event wrapper or something similar to ensure that it is only processed once.
After knowing what happen I decide to go with customized live data to trigger just once. ConsumableLiveData. So I will put answer here may help others.
class ConsumableLiveData<T>(var consume: Boolean = false) : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(
owner,
Observer<T> {
if (consume) {
if (pending.compareAndSet(true, false)) observer.onChanged(it)
} else {
observer.onChanged(it)
}
}
)
}
override fun setValue(value: T) {
pending.set(true)
super.setValue(value)
}
}
And for usage just put as bellow. It will trigger just once after any update value. This will great to handle navigation or listen to click or any interaction from user. Because just trigger once!
//In viewModel
val goToCreditCardLiveData = ConsumableLiveData<Boolean>(true)
And in fragment:
viewModel.goToCreditCardLiveData.observe(viewLifecycleOwner) {
findNavController().navigate(...)
}
If u are using kotlin and for only one time trigger of data/event use MutableSharedFlow
example:
private val data = MutableSharedFlow<String>() // init
data.emit("hello world) // set value
lifecycleScope.launchWhenStarted {
data.collectLatest { } // value only collect once unless a new trigger come
}
MutableSharedFlow won't trigger for orientation changes or come back to the previous fragment etc
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.
Nothing I've tried seems to solve my problem.
I have three buttons with with onClick behavior. Each of these buttons calls the same method launchActivity but with different parameters. launchActivity does some IO with the variables received from the onClick methods and then returns an intent. I would like to be able to implement a RxKotlin/Java Flowable to handle backpressure collectively across the three onClick methods so that I can implement BackpressureStrategy.DROP. So if onClick1 was initiated onClick2 would be dropped if initated while launchActivity was still processing onClick1 on the io() thread.
class ActivityLauncher {
fun onClick1() {
val intent = launchActivity(inFile1, outFile1)
startActivity(intent)
}
fun onClick2() {
val intent = launchActivity(inFile2, outFile2)
startActivity(intent)
}
fun onClick3() {
val intent = launchActivity(inFile3, outFile3)
startActivity(intent)
}
fun launchActivity(in: File, out: File): Intent {
// do IO with in and out files and create an intent
return intent
}
}
If I was to implement this as say a Single, I'd implement the onClick methods somewhat like:
fun onClick() {
Single.fromCallable(launchActivity(inFile, outFile)
.observeOn(scheduler.io())
.subscribeOn(scheduler.ui())
.subscribe { i -> startActivity(i) }
}
But I can't figure out how to call launchActivity from a shared Flowable that is accessible to all three onClick methods while still allowing them to pass in their unique inFile and outFile variables and enforcing backpressure.
The basic criteria is:
Ensure launchActivity is run on the io() thread
Pass the unique arguments from each of the onClick methods to launchActivity each time onClick[#] is run.
BackpressureStrategy.DROP is used to ensure only the first click in a series is processed in launchActivity
The resulting intent from launchActivity is passed to startActivity
How do I implement a Flowable to allow this behavior?
This doesn't really need to be done in a reactive way, seems like you're using it because of the convenience of threading - nothing wrong with that, however it brings complications when you try and model your situation using Rx.
Single is the correct operator to use - you only want 1 emission (BackpressureStrategy.DROP in a Flowable will still emit items of downstream if they can keep up). You just need to make your buttons isClickable = false at the start of your onClick(), and set then back to isClickable = true - something like :
Single.fromCallable { launchActivity(inFile, outFile) }
.doOnSubscribe { disableButtonsFunction() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally { enableButtonsFucntion() }
.subscribe { i -> startActivity(i) }