I think that the problem is I don't know to use well the Coroutines. In Maps Activity you'll see that I access to a PointsDao suspend function that returns a List of objects that I want to use to create marks at my Google Maps Activity.
#AndroidEntryPoint
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var binding: ActivityMapsBinding
private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>
private val permissions = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val mapsViewModel: MapsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) {
permissions ->
if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)) {
Log.d("fine_location", "Permission granted")
} else {
Log.d("fine_location", "Permission not granted")
getBackToMainActivity()
Toast.makeText(this, "Necessites acceptar els permisos de geolocalització per a realitzar la ruta", Toast.LENGTH_LONG).show()
}
if (permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)) {
Log.d("coarse_location", "Permission granted")
} else {
Log.d("coarse_location", "Permission not granted")
getBackToMainActivity()
Toast.makeText(this, "Necessites acceptar els permisos de geolocalització per a realitzar la ruta", Toast.LENGTH_LONG).show()
}
}
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
requestLocationPermissions()
}
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera.
*/
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
CoroutineScope(Dispatchers.Main).launch {
val listOfPoints = getRoutePoints()
for (point in listOfPoints) {
mMap.addMarker(MarkerOptions().position(LatLng( point.latitude, point.longitude)))
if (point == listOfPoints[0]) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(point.latitude, point.longitude), 18f))
}
}
}
}
private fun requestLocationPermissions() {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) -> {
Log.d("fine_location", "Permission already granted")
}
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) -> {
Log.d("coarse_location", "Permission already granted")
}
else -> {
requestPermissionLauncher.launch(permissions)
}
}
}
private fun getBackToMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
private fun getRouteId(): Int {
return intent.getIntExtra("routeId", 0)
}
// Gets the points from room repository through ViewModel
private fun getRoutePoints(): List<PointOfInterest> {
val route = getRouteId()
var points = emptyList<PointOfInterest>()
CoroutineScope(Dispatchers.IO).launch {
points = mapsViewModel.getRoutePoints(route)
}
return points
}
This is my ViewModel for this Activity:
#HiltViewModel
class MapsViewModel #Inject constructor(private val repository: RoomRepository): ViewModel() {
suspend fun getRoutePoints(routeId: Int): List<PointOfInterest> {
return repository.getPointsByRouteId(routeId)
}
}
And the Dao:
#Dao
interface PointsDao
{
#Query("SELECT * FROM points_tbl WHERE route_id = :routeId")
suspend fun getRoutePoints(routeId: Int): List<PointOfInterest>
}
My stracktrace error:
Process: com.buigues.ortola.touristics, PID: 27515
java.lang.IllegalStateException: Method addObserver must be called on the main thread
at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:317)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:172)
at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:49)
at androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:70)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:67)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:84)
at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:109)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:171)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:139)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:44)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:31)
at com.buigues.ortola.touristics.ui.MapsActivity.getMapsViewModel(MapsActivity.kt:39)
at com.buigues.ortola.touristics.ui.MapsActivity.getRoutePoints(MapsActivity.kt:123)
at com.buigues.ortola.touristics.ui.MapsActivity.access$getRoutePoints(MapsActivity.kt:31)
at com.buigues.ortola.touristics.ui.MapsActivity$onMapReady$1.invokeSuspend(MapsActivity.kt:85)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
The problem is here in getRoutePoints().
CoroutineScope(Dispatchers.IO).launch {
points = mapsViewModel.getRoutePoints(route)
}
The by viewModels() in your ViewModel property does a lazy load of the ViewModel. As a result, if you access your ViewModel property for the first time when you are not on the main thread, it will try to create it on the wrong thread, triggering this crash. ViewModels must be constructed on the main thread.
CoroutineScope(Dispatchers.IO) means you are creating a coroutine scope that by default uses background IO threads, so this code is run on a background thread.
You should not be creating a CoroutineScope for this anyway, because your Activity already has one that is properly managed by the Activity lifecycle (so it will cancel any in-progress jobs if the activity is closed, to avoid wasting resources).
Also, getRoutePoints() is a suspend function. There's no reason for you to be using Dispatchers.IO here. A suspend function by convention is safe to call from any dispatcher. (It is however possible to write one that breaks convention, but Room is properly designed and does not break convention.)
To fix the crash and run a coroutine properly, you should use lifecycleScope.launch { //.... However, this function as you have designed it won't do what you expect. It launches a coroutine to retrieve a value, but then it immediately returns before that coroutine has finished running, so in this case will just return the initial emptyList(). When you launch a coroutine, you are queuing up background work, but the current function that called launch continues synchronously without waiting for the coroutine results. If it did, it would be a blocking function. There's more information about that in my answer here.
So, you should instead make this a suspend function:
// Gets the points from room repository through ViewModel
private suspend fun getRoutePoints(): List<PointOfInterest> {
val route = getRouteId()
return mapsViewModel.getRoutePoints(route)
}
And your onMapReady function should also be fixed to use proper scope:
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
lifecycleScope.launch {
val listOfPoints = getRoutePoints()
for (point in listOfPoints) {
mMap.addMarker(MarkerOptions().position(LatLng( point.latitude, point.longitude)))
if (point == listOfPoints[0]) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(point.latitude, point.longitude), 18f))
}
}
}
}
Related
I have a kotlinjs app. I handle a particular event (dropping of data onto a component) like this:
onEvent {
drop = { event ->
GlobalScope.async {
//...
dropTask(y, data)
}
}
}
// ...
// this function has to be a suspend function because model's is
private suspend fun dropTask(y: Int, taskId: TaskId) {
// ... prepare data
model.insertBefore(taskId!!, insertBefore?.id)
}
// ... Model's function is defined like this:
suspend fun insertBefore(taskToInsert: TaskId, taskBefore: TaskId?) {
val (src, _) = memory.find(taskToInsert)
// ... and finally, the find function is:
fun find(taskId: TaskId): Pair<Task?, Int> {
// ...
return if (task != null) {
// ...
} else {
throw Exception("Couldn't find task with id $taskId!!")
}
}
The issue is that the Exception gets thrown, but isn't reported anywhere.
I have tried:
a) Installing a CoroutineExceptionHandler into the GlobalScope.async (i.e.:
val handler = CoroutineExceptionHandler { _, e ->
console.log("Caught exception: $e")
}
GlobalScope.async(handler) {
...but this never gets called. This would be relatively clean if I could make it work. It would be even nicer if this was default behavior for kotlinjs, so that exceptions weren't accidentally unreported.
b) Calling await:
drop = { event ->
GlobalScope.launch {
GlobalScope.async() {
// ...
dropTask(y, data)
}.await()
}
}
This does result in the exception being logged to the console, but it's so ugly. It's not possible to call .await() outside of a suspend function or coroutine, so for this particular event handler I have to wrap the async call in a launch. I must be doing something wrong. Anybody have a better pattern that I should be using?
I have a custom Scope that is using a single thread as it's Dispatcher.
private val jsDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
private val jsScope = CoroutineScope(jsDispatcher + SupervisorJob() + CoroutineName("JS-Thread"))
Let's assume I have a code block that uses the above scope to launch a new coroutine and call multiple suspend methods
jsScope.launch {
sampleMethod()
sampleMethod2()
sampleMethod3()
}
I need to validate and throw an exception if one of the above sample methods is not running on the above JS thread
private suspend fun sampleMethod() = coroutineScope {
//Implement me
validateThread()
}
How can this be enforced?
You can check the current thread name in your method:
private suspend fun sampleMethod() = coroutineScope {
assert(Thread.currentThread().name == "js-thread") // Doesn't work!
}
However, newSingleThreadExecutor uses DefaultThreadFactory which produces thread names like pool-N-thread-M which cannot really be validated because you don't know M or N. I see two solutions here:
Take advantage of the fact that you have a single thread and change its name as soon as you create the executor:
runBlocking {
jsScope.launch {
Thread.currentThread().name = "js-thread"
}
}
Pass a custom thread factory: Executors.newSingleThreadExecutor(MyThreadFactory("js-thread"))
private class MyThreadFactory(private val name: String) : ThreadFactory {
private val group: ThreadGroup
private val threadNumber = AtomicInteger(1)
init {
val s = System.getSecurityManager()
group = if (s != null) {
s.threadGroup
} else {
Thread.currentThread().threadGroup
}
}
override fun newThread(r: Runnable): Thread {
val t = Thread(group, r, "$name-${threadNumber.getAndIncrement()}", 0)
if (t.isDaemon) {
t.isDaemon = false
}
if (t.priority != Thread.NORM_PRIORITY) {
t.priority = Thread.NORM_PRIORITY
}
return t
}
}
Code was adapted from DefaultThreadFactory. Guava and apache-commons also provide utility methods to do the same. This has the advantage that it works for any thread pool, not just single-threaded.
After some research, I took a look at the withContext() implementation and the answer to my question was right there.
Taken from the withContext() implementation, this is how to check if current coroutine context is on same dispatcher as other context/scope
if (newContext[ContinuationInterceptor] === oldContext[ContinuationInterceptor]) {
// same dispatcher
}
Having a Processor class, trying to replace some of the code with coroutines. Since it is in a non coroutines context so val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob) is added and used for start coroutines.
Added CoroutineScope, and using serviceScope.launch{} in the place which was using Thread{}.start().
Inside the function restart(), it replaced the using of CountDownLatch with
serviceScope.launch {
withContext(Dispatchers.IO) {
doReset()
}
}
Question: this launch/withContext actually does not stop the code execution of the next if (!conDoProcess) -- so it fails to do what the latch used to do.
what is the right way to stop the code execution until the doReset() . is done?
Another question, when dispose this Processor object it calls serviceScope.cancel(),
what is the difference if call with serviceJob.cancel()?
class Processor {
private val serviceJob = Job()
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
.........
/* return false if the it does not start the processing */
fun restart(): Boolean {
synchronized(_lock) {
.........
// 1.old code using latch to wait
/******************
val latch = CountDownLatch(1)
streamThreadPoolExecutor.execute {
doReset() //
latch.countDown()
}
latch.await(3, TimeUnit.SECONDS) // wait at most for 3 seconds if no one calls countDown
*******************/
// 2. change to using coroutines to suspend
serviceScope.launch {
withContext(Dispatchers.IO) {
doReset()
}
}
// wait until reset is done
if (!conDoProcess) {// the doRest() should update conDoProcess
return false
}
for (i in providers.indices) {
val pr = provider[i]
serviceScope.launch {
pr.doProcess()
}
}
return true
}
}
fun dispose() {
synchronized(_lock) {
.........
serviceScope.cancel()
// or should it use
// serviceJob.cancel()
//==========>
}
}
}
I think it used the serviceScope.launch wrong, it should include the rest part after the blocking part withContext(Dispatchers.IO), but inside the serviceScope.launch.
// 2. change to using coroutines to suspend
serviceScope.launch {
withContext(Dispatchers.IO) {
doReset()
}
// wait until reset is done
if (!conDoProcess) {// the doRest() should update conDoProcess
return false
}
for (i in providers.indices) {
val pr = provider[i]
serviceScope.launch {
pr.doProcess()
}
}
}
return true
I am building a monitor in Kotlin to schedule certain operations, what I want is a program that inserts or updates some database entries for a given time intervall. What I got so far is a program that runs for a given time span, but I have an infinite loop in my porgram that takes up to 30% of processor power when it is not time for an update. So my question is how to build a monitor without an infinite loop?
this my code so far:
while(!operations.done && appConfigurations.run_with_monitor) {
if (DataSourceMonitor.isReadyForUpdate(lastMonitorModel)) {
operations.update()
}
}
operations is an entire sequence of different actions to execute. Each operation implementing the IScheduler interface.
interface IScheduler {
var done: Boolean
fun update()
fun reset()
}
Example of implementation:
class Repeat(private val task: IScheduler) : IScheduler {
override var done = false
override fun update() {
if (this.task.done) {
this.reset()
}
this.task.update()
//logger.info { "Update repeat, done is always $done" }
}
override fun reset() {
this.task.reset()
this.done = false
}
}
class Sequence(private val task1: IScheduler, private val task2: IScheduler): IScheduler {
override var done = false
var current = task1
var next = task2
override fun update() {
if (!this.done) {
this.current.update()
if (this.current.done) {
this.current = this.next
}
if (this.next.done) {
this.done = true
}
}
}
class Print(private val msg: String): IScheduler {
override var done = false
override fun update() {
println(this.msg)
this.done = true
}
override fun reset() {
this.done = false
}
}
The value of operations can be as follows:
val operations = Repeat(Sequence(Print("First action"), Print("Another action")))
**So right now my monitor is working and completely functional, but how can I improve the performance of the infinite loop? **
Hope anyone has some ideas about this.
If your DataSourceMonitor has no way to block until isReadyForUpdate is going to return true, then the usual approach is to add a delay. eg:
while(!operations.done && appConfigurations.run_with_monitor) {
if (DataSourceMonitor.isReadyForUpdate(lastMonitorModel)) {
operations.update()
} else {
Thread.sleep(POLL_DELAY);
}
}
If it's always ready for update there won't be any delay, but if it ever isn't ready for update then it'll sleep. You'll need to tune the POLL_DELAY. Bigger values mean less CPU usage, but greater latency in detecting new events to process. Smaller values produce less latency, but use more CPU.
If you really want to get fancy you can have the poll delay start small and then increase up to some maximum, dropping back down once events are found. This is probably overkill, but look up "adaptive polling" if you're interested.
I have refactored my code and I can accomplish the same result with less code, by removing the IScheduler interface by the abstract class TimerTask. The job can be done with these lines of code:
val scheduler = Sequence(
Print("Executed task 1"),
Sequence(Print("Executed task 2"),
Sequence(Print("Executed task 3"), Print("Finished Scheduler")))
)
Timer().schedule(scheduler, DELAY, PERIOD)
All the interface implementations are changed to TimerTask implementations:
class Print(private val msg: String): TimerTask() {
override fun run() {
println(msg)
}
}
class Sequence(private val task1: Runnable, private val task2: Runnable): TimerTask() {
override fun run() {
task1.run()
task2.run()
}
}
I'm new to reactive programming. I expect to see
test provider started
Beat 1000
Beat 2000
in logs but there is only test provider started and no Beat or on complete messages. Looks like I miss something
#Service
class ProviderService {
#PostConstruct
fun start(){
val hb: Flux<HeartBeat> = Flux.interval(Duration.ofSeconds(1)).map { HeartBeat(it) }
val provider = Provider("test", hb)
}
}
////////////////////////
open class Provider(name: String, heartBests: Flux<HeartBeat>) {
companion object {
val log = LoggerFactory.getLogger(Provider::class.java)!!
}
init {
log.info("$name provider started")
heartBests.doOnComplete { log.info("on complete") }
heartBests.doOnEach { onBeat(it.get().number) }
}
fun onBeat(n: Number){
log.info("Beat $n")
}
}
/////
class HeartBeat(val number: Number)
three pretty common mistakes here:
operators like doOnEach return a new Flux instance with the added behavior, so you need to (re)assign to a variable or use a fluent style
nothing happens until you subscribe() (or a variant of it. blockXXX do also subscribe under the hood for instance...)
such a pipeline is fully asynchronous, and runs on a separate Thread due to the time dimension of the source, interval. As a result, control would immediately return in init even if you had subscribed, potentially causing the main thread and then the app to exit.
In your code lambda from 'doOnComplete' has been never called, because you created infinite stream. Method 'doOnEach' as 'map' is intermediate operations (like map in streams), its doesn't make a call.
And you have another mistake, reactive suggests "fluent pattern".
Try this simple example:
import reactor.core.publisher.Flux
import java.time.Duration
fun main(args: Array<String>) {
val flux = Flux.interval(Duration.ofSeconds(1)).map { HeartBeat(it) }
println("start")
flux.take(3)
.doOnEach { println("on each $it") }
.map { println("before map");HeartBeat(it.value * 2) }
.doOnNext { println("on next $it") }
.doOnComplete { println("on complete") }
.subscribe { println("subscribe $it") }
Thread.sleep(5000)
}
data class HeartBeat(val value: Long)