How do i recall an Intent in Android Studio?
I am trying to build a service which uses an intent. In some case, I am trying to send a broadcast up to a specific number of times( let's say X times) if intent is not received by the activity. After X times, still activity does not get the intent , i want to delete that intent and do some other operation for that activity.
As a way to implement this, you can broadcast EventBus events through JobQueue job, that can retry sending of an event for needed number of times. The subscriber activity subscribed to this event, and creates your Intent only in case if event was handled.
From code perspective this could be implemented as:
Event, that will be broadcasted:
class SomeEvent(val isSuccess: Boolean)
Job class, which instance is initiated by your service:
class SendCustomEventJob() : Job{
#Inject
#Transient
lateinit var eventBus: EventBus
// dagger jobs component injection
override fun inject(appComponent: AppComponent) {
super.inject(appComponent)
appComponent.inject(this)
}
override fun onRun() {
// some logic goes here, like API calls
eventBus.post(SomeEvent(true))
}
override fun shouldReRunOnThrowable(throwable: Throwable, runCount: Int, maxRunCount: Int): RetryConstraint {
if(runCount == 3)
// your custom job exceptions handling logic
}
override fun getRetryLimit(): Int = 3 // your custom retries number
override fun onAdded() { }
override fun onCancel(cancelReason: Int, throwable: Throwable?) {
eventBus.post(SomeEvent(false))
}
}
And handling of events in your activity-subscriber:
#Subscribe(threadMode = ThreadMode.MAIN)
fun onFetchedCustomEvent(event: SomeEvent) {
if (!event.isSuccess)
return
//create your intent here...
}
Related
I want to use MutableSharedFlow in the Service class, but I'm not sure how to stop subscribing when Service ends. How to implement the MutableSharedFlow function in service or any other function available to listen to stream data?
To use a Flow in an android Service class we need a CoroutineScope instance to handle launching coroutines and cancellations. Please see the following code with my comments:
class CoroutineService : Service() {
private val scope = CoroutineScope(Dispatchers.IO)
private val flow = MutableSharedFlow<String>(extraBufferCapacity = 64)
override fun onCreate() {
super.onCreate()
// collect data emitted by the Flow
flow.onEach {
// Handle data
}.launchIn(scope)
}
override fun onStartCommand(#Nullable intent: Intent?, flags: Int, startId: Int): Int {
scope.launch {
// retrieve data from Intent and send it to Flow
val messageFromIntent = intent?.let { it.extras?.getString("KEY_MESSAGE")} ?: ""
flow.emit(messageFromIntent)
}
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
scope.cancel() // cancel CoroutineScope and all launched coroutines
}
}
I want to insert some records which I get from the API to my database,
I am using a service class to do this process, I was trying to use this concept of live data inside service class, but it required my service class to be a lifecycleowner.
am stuck with how to make my service class observer to the changes in a live data
Any help will be good!!
If your service should not be affected by activity lifecycle (onStop(), onStart() etc) then you can use LiveData<T>.observeForever(Observer<T>) method. Like so,
val observer = Observer<YourDataType> { data ->
//Live data value has changed
}
liveData.observeForever(observer)
To stop observing you will have to call LiveData.removeObserver(Observer<T>). Like so:
liveData.removeObserver(observer)
If you need to stop observing when the application is in the background, you can bind your service in the calling activity in the onStart() method of the activity and unbind your service in the onStop() method. Like so:
override fun onStart() {
super.onStart()
val serviceIntent = Intent(this, myService::class.java)
bindService(serviceIntent, myServiceConnection, Context.BIND_AUTO_CREATE)
}
override fun onStop() {
unbindService(myServiceConnection)
super.onStop()
}
Read on bound services here
Then, in the service
override onBind(Intent) and onRebind(Intent) method and start observing the LiveData (App is in foreground)
override fun onBind(intent: Intent?): IBinder? {
liveData.observeForever(observer)
return serviceBinder
}
override fun onRebind(intent: Intent?) {
liveData.observeForever(observer)
super.onRebind(intent)
}
Remove LiveData observer in onUnbind(Intent) (App is in background)
override fun onUnbind(intent: Intent?): Boolean {
liveData.removeObserver(observer)
return true
}
In Google's official codelab about advanced-coroutines-codelab sample, they've used ConflatedBroadcastChannel to watch a variable/object change.
I've used the same technique in one of my side projects, and when resuming the listening activity, sometimes ConflatedBroadcastChannel fires it's recent value, causing the execution of flatMapLatest body without any change.
I think this is happening while the system collects the garbage since I can reproduce this issue by calling System.gc() from another activity.
Here's the code
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
val tvCount = findViewById<TextView>(R.id.tv_count)
viewModel.count.observe(this, Observer {
tvCount.text = it
Toast.makeText(this, "Incremented", Toast.LENGTH_LONG).show();
})
findViewById<Button>(R.id.b_inc).setOnClickListener {
viewModel.increment()
}
findViewById<Button>(R.id.b_detail).setOnClickListener {
startActivity(Intent(this, DetailActivity::class.java))
}
}
}
MainViewModel.kt
class MainViewModel : ViewModel() {
companion object {
val TAG = MainViewModel::class.java.simpleName
}
class IncrementRequest
private var tempCount = 0
private val requestChannel = ConflatedBroadcastChannel<IncrementRequest>()
val count = requestChannel
.asFlow()
.flatMapLatest {
tempCount++
Log.d(TAG, "Incrementing number to $tempCount")
flowOf("Number is $tempCount")
}
.asLiveData()
fun increment() {
requestChannel.offer(IncrementRequest())
}
}
DetailActivity.kt
class DetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
val button = findViewById<Button>(R.id.b_gc)
val timer = object : CountDownTimer(5000, 1000) {
override fun onFinish() {
button.isEnabled = true
button.text = "CALL SYSTEM.GC() AND CLOSE ACTIVITY"
}
override fun onTick(millisUntilFinished: Long) {
button.text = "${TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished)} second(s)"
}
}
button.setOnClickListener {
System.gc()
finish()
}
timer.start()
}
}
Here's the full source code :
CoroutinesFlowTest.zip
Why is this happening?
What am I missing?
Quoting from the official response, (The simple and straightforward solution)
The problem here is that you are trying to use
ConflatedBroadcastChannel for events, while it is designed to
represent current state as shown in the codelab. Every time the
downstream LiveData is reactivated it receives the most recent state
and performs the incrementing action. Don't use
ConflatedBroadcastChannel for events.
To fix it, you can replace ConflatedBroadcastChannel with
BroadcastChannel<IncrementRequest>(1) (non-conflated channel, which is
Ok for events to use) and it'll work as you expect it too.
In addition to the answer of Kiskae:
This might not be your case, but you can try to use BroadcastChannel(1).asFlow().conflate on a receiver side, but in my case it led to a bug where the code on a receiver side didn't get triggered sometimes (I think because conflate works in a separate coroutine or something).
Or you can use a custom version of stateless ConflatedBroadcastChannel (found here).
class StatelessBroadcastChannel<T> constructor(
private val broadcast: BroadcastChannel<T> = ConflatedBroadcastChannel()
) : BroadcastChannel<T> by broadcast {
override fun openSubscription(): ReceiveChannel<T> = broadcast
.openSubscription()
.apply { poll() }
}
On Coroutine 1.4.2 and Kotlin 1.4.31
Without using live data
private var tempCount = 0
private val requestChannel = BroadcastChannel<IncrementRequest>(Channel.CONFLATED)
val count = requestChannel
.asFlow()
.flatMapLatest {
tempCount++
Log.d(TAG, "Incrementing number to $tempCount")
flowOf("Number is $tempCount")
}
Use Flow and Coroutine
lifecycleScope.launchWhenStarted {
viewModel.count.collect {
tvCount.text = it
Toast.makeText(this#MainActivity, "Incremented", Toast.LENGTH_SHORT).show()
}
}
Without using BroadcastChannel
private var tempCount = 0
private val requestChannel = MutableStateFlow("")
val count: StateFlow<String> = requestChannel
fun increment() {
tempCount += 1
requestChannel.value = "Number is $tempCount"
}
The reason is very simple, ViewModels can persist outside of the lifecycle of Activities. By moving to another activity and garbagecollecting you're disposing of the original MainActivity but keeping the original MainViewModel.
Then when you return from DetailActivity it recreates MainActivity but reuses the viewmodel, which still has the broadcastchannel with a last known value, triggering the callback when count.observe is called.
If you add logging to observe the onCreate and onDestroy methods of the activity you should see the lifecycle getting advanced, while the viewmodel should only be created once.
I'm not sure what I'm looking to do is even possible. If it is, it's new to me.
here's a basic outline of what I'm trying to accomplish:
class MyClass : SomeInterface {
fun makeSomethingHappen() {
methodInInterfaceThatReturnsValueBelow()
}
override fun iDidSomething(result: Value) {
//give this value back to the original caller of makeSomethingHappen()
}
override fun iDidSomethingElse(result: Value) {
//give this value back to the original caller of makeSomethingHappen()
}
override fun onFailure(result: Value) {
//give this value back to the original caller of makeSomethingHappen()
}
}
Explanation:
We're using a required SDK that has about 15 overrides. I call into this class to call a function in the SDK. That function is going to call one of the override functions when it's done.
Is there a way (live data, flows, anything) to have whoever called makeSomethingHappen() receive the value from any of the override methods?
This is a basic example of reactive programming. You wait for an event that a producer/observable emits, it's like when you declare a click listener on a button.
You can't return the value in makeSomethingHappen(), but you can create a listener and the observer implements that listener to get the value.
typealias MyListener = (Value) -> Unit
class MyClass : SomeInterface {
private var _listeners: List<MyListener> = mutableListOf()
fun addListener(listener: MyListener) {
_listeners.add(listener)
}
fun makeSomethingHappen() {
methodInInterfaceThatReturnsValueBelow()
}
override fun iDidSomething(result: Value) {
// Send result Value to the listener implementations
_listeners.forEach { it.invoke(result) }
}
override fun iDidSomethingElse(result: Value) {
// Send result Value to the listener implementations
_listeners.forEach { it.invoke(result) }
}
override fun onFailure(result: Value) {
// Send result Value to the listener implementations
_listeners.forEach { it.invoke(result) }
}
}
The you can get the result implementing MyListener
val myClass = MyClass()
myClass.makeSomethingHappen { value ->
// Here you have the value and you can do whatever
print(value)
}
I have a coroutine I'd like to fire up at android startup during the splash page. I'd like to wait for the data to come back before I start the next activity. What is the best way to do this? Currently our android is using experimental coroutines 0.26.0...can't change this just yet.
UPDATED: We are now using the latest coroutines and no longer experimental
onResume() {
loadData()
}
fun loadData() = GlobalScope.launch {
val job = GlobalScope.async {
startLibraryCall()
}
// TODO await on success
job.await()
startActivity(startnewIntent)
}
fun startLibraryCall() {
val thirdPartyLib() = ThirdPartyLibrary()
thirdPartyLib.setOnDataListener() {
///psuedocode for success/ fail listeners
onSuccess -> ///TODO return data
onFail -> /// TODO return other data
}
}
The first point is that I would change your loadData function into a suspending function instead of using launch. It's better to have the option to define at call site how you want to proceed with the execution. For example when implementing a test you may want to call your coroutine inside a runBlocking. You should also implement structured concurrency properly instead of relying on GlobalScope.
On the other side of the problem I would implement an extension function on the ThirdPartyLibrary that turns its async calls into a suspending function. This way you will ensure that the calling coroutine actually waits for the Library call to have some value in it.
Since we made loadData a suspending function we can now ensure that it will only start the new activity when the ThirdPartyLibrary call finishes.
import kotlinx.coroutines.*
import kotlin.coroutines.*
class InitialActivity : AppCompatActivity(), CoroutineScope {
private lateinit var masterJob: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + masterJob
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
masterJob = Job()
}
override fun onDestroy() {
super.onDestroy()
masterJob.cancel()
}
override fun onResume() {
this.launch {
val data = ThirdPartyLibrary().suspendLoadData()
// TODO: act on data!
startActivity(startNewIntent)
}
}
}
suspend fun ThirdPartyLibrary.suspendLoadData(): Data = suspendCoroutine { cont ->
setOnDataListener(
onSuccess = { cont.resume(it) },
onFail = { cont.resumeWithException(it) }
)
startLoadingData()
}
You can use LiveData
liveData.value = job.await()
And then add in onCreate() for example
liveData.observe(currentActivity, observer)
In observer just wait until value not null and then start your new activity
Observer { result ->
result?.let {
startActivity(newActivityIntent)
}
}