I want to connect to a service inside a texture view. The connection can be cut off if the service crashes. For this case a new connection needs to be established.
On every onSurfaceTextureAvailable I check if the service is still alive and running. If not i try to reconnect to the service.
Due that multiple things happen in this view I have to wait till the service is connect and can then continue. I would like to solve it with blocking coroutines like this:
suspend fun bindServiceAndWait() = suspendCoroutine<IService> { continuation ->
Log.i(TAG, "In co-routine")
val conn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
continuation.resume(IService.Stub.asInterface(service))
}
override fun onServiceDisconnected(name: ComponentName?) {
// don't care here
}
}
// Context is coming from the application it is running
context.bindService(serviceIntent, conn, Context.BIND_AUTO_CREATE)
}
There problem is that the callback is never executed and so the blocking is never continued.
If I remove the blocking continuation part, everything works fine.
Do you have any idea why this behavior is like this ?
Related
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
In my app I start a WebSocketWorker tasks that runs periodically every 15 minutes. As the name implies, it contains a WebSocket for listening to a socket in the background:
// MainApplication.kt
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
val work = PeriodicWorkRequestBuilder<WebSocketWorker>(15, TimeUnit.MINUTES).build()
workManager.enqueueUniquePeriodicWork("UniqueWebSocketWorker", ExistingPeriodicWorkPolicy.KEEP, work)
}
The WebSocketWorker contains the following logic:
#HiltWorker
class WebSocketWorker #AssistedInject constructor(
#Assisted appContext: Context,
#Assisted workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
inner class MyWebSocketListener : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
Timber.d("The message sent is %s", text)
// do sth. with the message
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
t.localizedMessage?.let { Timber.e("onFailure: %s", it) }
response?.message?.let { Timber.e("onFailure: %s", it) }
}
}
override suspend fun doWork(): Result {
try{
// code to be executed
val request = Request.Builder().url("ws://***.***.**.***:8000/ws/chat/lobby/").build()
val myWebSocketListener = MyWebSocketListener()
val client = OkHttpClient()
client.newWebSocket(request, myWebSocketListener)
return Result.success()
}
catch (throwable:Throwable){
Timber.e("There is a failure")
Timber.e("throwable.localizedMessage: %s", throwable.localizedMessage)
// clean up and log
return Result.failure()
}
}
}
As you can see, in the Worker class I set the WebSocket and everything is fine. Listening to the socket works.
Now, I also want to add the "sending of messages" functionality to my app. How can I reuse the websocket created in WebSocketWorker? Can I pass input data to the WebSocketWorker that runs in the background ?
Let's say I have a EditText for typing the message and a Button to send the message with a setOnClickListener attached like this:
binding.sendButton.setOnClickListener {
// get message
val message = binding.chatMessageEditText.text.toString()
// check if not empty
if(message.isNotEmpty()) {
// HOW CAN I REUSE THE WEBSOCKET RUNNING PERIODICALLY IN THE BACKGROUND?
// CAN I PASS THE MESSAGE TO THAT WEBSOCKET ?
// OR SHOULD I CREATE A DIFFERENT WORKER FOR SENDING MESSAGES (e.g.: a OneTimeRequest<SendMessageWorker> for sending messages ?
}
}
From the documentation, I know that you need to build Data objects for passing inputs and so on but there was no example which showcased how to pass input to a worker running periodically in the background.
My experience is saying that you can. Basically you "can't" interact with the worker object via the API. It is really annoying.
For example, with the JS you have the option to get a job and check the parameters of the job. There is no such option with the work. For example, I want to check what is the current state of the restrictions - what is satisfied, what is not. Nothing like this. You can just check states, cancel and that is almost all.
My suggestions is that it is because the WorkManager is a "facade/adapter" over other libraries like JS. It has it's own DB to restore JS jobs on device restart and stuff like this, but beside that if you want to interact with the internals I guess it was just too complicated for them to do so they just skipped.
You can just inject some other object and every time the work can ask it for it's data. I don't see other option.
I have a job inside my AndroidViewModel class. Job is triggered by viewModelScope.launch. Job is a long running process which return result by lambda functions. According to requirement If user want to cancel the job while remaining in the scope on button click it should cancel the job. The problem is when I cancel the job, process is still running in the background and it is computing the background task. Below is my ViewModelClass with its job and cancel function.
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*
class SelectionViewModel(val app: Application) : AndroidViewModel(app) {
private var mainJob: Job? = null
private var context: Context? = null
fun performAction(
fileOptions: FileOptions,
onSuccess: (ArrayList<String>?) -> Unit,
onFailure: (String?) -> Unit,
onProgress: (Pair<Int, Int>) -> Unit
) {
mainJob = viewModelScope.launch {
withContext(Dispatchers.IO) {
kotlin.runCatching {
while (isActive) {
val mOutputFilePaths: ArrayList<String> = ArrayList()
// Long running Background task
.. progress
OnProgress.invoke(pair)
// resul success
onSuccess.invoke(mOutputFilePaths)
}
}.onFailure {
withContext(Dispatchers.Main) {
onFailure.invoke(it.localizedMessage)
}
}
}
}
}
fun cancelJob() {
mainJob?.cancel()
}
}
Here it is I am initiating my ViewModel
val viewModel: SelectionViewModelby lazy {
ViewModelProviders.of(this).get(SelectionViewModel::class.java)
}
and when I start the job I call the following method
viewModel.performAction(fileOptions,{success->},{failure->},{progress->})
When I want to cancel the task. I call the following method.
viewModel.cancelJob()
Problem is even after canceling the job I am still receiving the progress as it is being invoked. This means job has not been canceled.
I want to implement the correct way to start and cancel the job while remaining in the viewmodel scope.
So what is the proper way to implement the viewmodel to start and cancel the job?
In order to cancel the job you have to have a suspending function call.
This means that if your job has code like
while (canRead) {
read()
addResults()
}
return result
this can never be cancelled the way you wish it to be cancelled.
there are two ways you can cancel this code
a) add a delay function (this will check for cancelling and cancel your job)
b) (which in the above case is the correct way) periodically add a yield() function
so the above code should look like this:
while(canRead) {
yield()
read()
addResults()
}
return result
edit: some further explanations are probably necessary to make this clear
just because you run something withContext, does not mean that coroutines can stop or break it at any time
what coroutines do is basically change the old way of doing things with callbacks and replace it with suspending functions
what we used to do for complex calculations was start a thread ,which would execute the calculations and then get a callback with the results.
at any point you could cancel the thread and the work would stop.
cancelling coroutines is not the same
if you cancel a coroutine what you basically do is tell it that the job is cancelled , and at the next opportune moment it should stop
but if you don't use yield() delay() or any suspending function such an opportune moment will never arrive
it is the equivalent of running something like this with threads
while(canRead && !cancelled) {
doStuff
}
where you would manually set the cancelled flag, if you set it but didn't check it in your code , it would never stop
as a side note, be careful because right now you have a big block of calculations running code, this will run on one thread because you never called a suspending function. When you add the yield() call , it could change threads or context (within what you defined ofc) so make sure it is thread safe
New to kotlin, i tried many examples and tutorials to no avail,
My requirement is:
Ui creates a coroutine that initiates a network connection
on press of a button, that coroutine sends a msg like "i need info about foo" (taken from a edittext?) to the connected server.
coroutine should also be listening for incoming messages and pass those messages to ui (or update ui directly)
coroutine should keep connected to the server unless it is told to close the connection.
I feel that i need global scope, dispatcher.io.
All the examples i found do nothing more than printing values and terminating coroutines and doesn't mention how to implement a long running coroutine which can act as a continues background socket connection.
I do understand that listening from a socket in loop can achieve that but what kind of coroutine do i need here and how do i send messages to and from ui?
Update:Code
// Added comments for new devs who love copy-pasting as it is a nice little startup code
// you can add android:screenOrientation="portrait" in manifest if you want to use this code
class MainActivity2 : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
private lateinit var job: Job // lateinit = will be initialized later
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
job = Job() // initialized
launch(Dispatchers.IO) { //dispatcher defined, otherwise launch{
// activity also extends coroutine scope so it will be launched in that scope
connector()
}
}
override fun onDestroy() {
// to avoid launching multiple coroutines
// on configuration changes and cancelling it on exit
job.cancel()
super.onDestroy()
}
suspend fun connector() = withContext(Dispatchers.IO){ //defined dispatcher twice here
// useless, once is enough, either here or on launch
//if you defined dispatcher on launch, fun should look like
// suspend fun connector(){
while(true){
// talk to a server
// need to update ui?
withContext(Dispatchers.Main){ // back to UI
// you can update/modify ui here
Toast.makeText(this#MainActivity2, "hi there", Toast.LENGTH_LONG).show()
}
}
}
}
New Question: How do i handle configuration changes :(
Answer: I used Fragments with ViewModels and coroutine launched via viewmodelScope, working flawlessly so far.
From what I understand you want to create a coroutine that listens for responses over a connection. In that case the only thing that you need make sure is that the coroutine should be cancellable, once activity is closed.
suspend fun connector() = withContext(Dispatchers.IO){
try {
// open the connection here
while(isActive) {
var doing : String = "nothing" // fetched from a network call
withContext(Dispatchers.Main){
Toast.makeText(this#MainActivity2, doing, Toast.LENGTH_LONG).show()
}
}
} finally {
withContext(NonCancellable) {
//close the connection here
}
}
isActive is an extension property available inside the coroutine via the CoroutineScope object.
When the screen is rotated, the connection is closed and a new one is being opened once the coroutine is called again in onCreate.
This is a similar question to How to check if a service is running on Android? but since the question is old and the answers provided there are deprecated or not working properly. Thus the separate question.
I have an implementation, that fires a Service on Boot Complete, but I also want to start the service in onCreate of MainActivity, in case the service was not started before.
here are what I have tried:
1. Fetch Static Boolean to get the state of the Service as demonstrated below.
MyService.kt
class MyService : Service() {
override fun onCreate() {
super.onCreate()
isServiceStarted = true
}
override fun onDestroy() {
super.onDestroy()
isServiceStarted = false
}
companion object {
var isServiceStarted = false
}
}
MainActivity.kt
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val serviceStarted = MyService.isServiceStarted
if (!serviceStarted) {
val startMyService = Intent(this, MyService::class.java)
ContextCompat.startForegroundService(this, startMyService)
}
}
}
but I soon discovered that onDestroy is not always called when a Service is destroyed, thereby leaving my static boolean variable (isServiceStarted) to be true, when in reality it has been destroyed.
2.A function to check
fun isMyServiceRunning(serviceClass : Class<*> ) : Boolean{
var manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.name.equals(service.service.className)) {
return true
}
}
return false
}
The Call
isMyServiceRunning(MyService::class.java)
Problems with this approach include:
- getRunningServices is deprecated since Android O (API 27),
- It is resource consuming and inefficient to loop through running services like that and because the docs say:
Note: this method is only intended for debugging or implementing service management type user interfaces.
It's not meant for control flow!
What is an Elegant/Efficient way to check if a Service is already running?
If service is in the application process just use the static field inside service (companion object) or bind to service.
If the service runs in remote process use Messanger if you want to have a synchronized communication or AIDL when you want to care about threads.