Search code examples
androidmultithreadingservicearchitectureandroid-architecture-components

How can I run a service the would have access to my database and could run operations on it, but won't stuff up the UI thread


I am building an app the needs to go through a collection of photos stored locally, which I import to a room database, and try to detect for each if it contains faces or not. I've got everything sorted, my only issue is how to run this operation, which could take a while, in a service that wouldn't stuff up the UI thread.

At first I wanted to use a JobIntentService but couldn't because I was unable to observeForever on a background thread, and couldn't use a simple observer because I have no lifecycleOwner to give to the Observer.

I've ended up using just a service, as soon as the operation starts my UI is pretty much stuck and if I try to do anything the app crashes.

I tried maybe IntentService but I can't use the observer in onHandleIntent because it's a worker thread and it doesn't let me, and when I run the operations under onStartCommand then it's just the same thing.

I feel like I am stuck with the architecture of this thing, I'd appreciate any ideas. Thank you.

This is my service:

class DetectJobIntentService : Service() {

    private val TAG = "DetectJobIntentServi22"
    lateinit var repo: PhotoRepository
    lateinit var observer : Observer<MutableList<Photo>>

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        val options = FirebaseVisionFaceDetectorOptions.Builder()
            .setClassificationMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
            .setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
            .setMinFaceSize(0.15f)
            .build()

        val detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options)

        repo = PhotoRepository(application)

        observer = Observer {
            for (file in it) {
                val image = FirebaseVisionImage.fromFilePath(application, Uri.parse(file.uri))
                AsyncTask.execute {
                    detector.detectInImage(image).addOnSuccessListener { list ->
                        if (list.isNotEmpty()) {
                            file.hasFaces = 1
                            repo.update(file)
                        } else {
                            file.hasFaces = 2
                            repo.update(file)
                        }
                    }
                }


            }
        }

        repo.getAllPhotos().observeForever(observer)

        val notificationIntent= Intent(this, MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(this,
            0, notificationIntent, 0)

        val notification = NotificationCompat.Builder(this, getString(tech.levanter.anyvision.R.string.channel_id))
            .setContentTitle("Detecting faces..")
            .setContentText("64 photos detected")
            .setSmallIcon(tech.levanter.anyvision.R.drawable.ic_face)
            .setContentIntent(pendingIntent)
            .build()

        startForeground(1, notification)

        return START_NOT_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        repo.getAllPhotos().removeObserver(observer)
    }

}

Solution

  • Seeing your code is in Kotlin, I'll advice you try out Kotlin Coroutines. This would enable you dispatch expensive operations i.e. querying databases, making network requests/calls off to other threads thereby not blocking the UIThread. Coroutines help you avoid the hassle of callbacks. Also, Google just deprecated the AsyncTask API in favour of Coroutines as the way to go for multi-threading purposes.