Skip to main content

A fusion between WorkManager and AlarmManager

Androids' WorkManager has been around for a while. However my own expectations about it were a little higher. I wished that the WorkManager could fire events at exact timing. But since it was made to respect doze mode, I should respect that too. That means that if the phone is idle, the WorkManager wont run. 

Lets explore my case: 

Once the user logs into my app, I need to schedule 5 alarms for him. But the alarm times come from a server and not locally. So basically I need some background work to do: 
Sync timing -> Save to database -> Schedule Alarms. (btw on the time alarm will fire, it's just a notification to be shown)
Now this has to happen every day at 00:01. The data coming from the server hold the alarm timings for a year, but certainly I can't schedule alarms from current moment till the end of the year. It would drain phones battery. So I need to start a
WorkManager to save data to my local Room database and after that I need to repeatedly  schedule alarms every day.

On with the show: 
First of all, if anyone is curious why I chose WorkManager over JobScheduler or even BroadcastReceiver

1- Don't have to register nothing to the Manifest file.
2- Don't have to worry about internet connection, phone reboot etc.
3- I faced lots of deprecated methods and constants without it.

Let's start:

So the WorkManager has a few things to set up. Since I will get data from API, I need my worker to be started if and only if there is internet connection which is specified by the setRequiredNetworkType(NetworkType.CONNECTED) method in the Constraint.Builder(). After that I need to specify how many times will my worker run. On my case there is no comment needed looking at the name OneTimeWorkRequestBuilder which has a worker of mine. After setting my defined constraints I can start my DataSyncWorker.

Let's check the class now: 

Since I love coroutines and I am using them everywhere, I am using the WorkManager by extending CoroutineWorker. So the method where all my sync happens is named doWork which btw is a suspending method handled by the Android framework itself. By the way is hell lot of data. 

And now, lets jump into my OfflineScheduler

Looks like I'm done scheduling my notifications at current timing for today and also tomorrows timing alarm, which will schedule other alarms:

Once the alarm has been set, Android "waits" until the time comes and brings my code here:

And another one which was scheduled for the midnight, which basically will trigger my OfflineScheduler code again:

And it will fire and fire until the end of time 💪

There are 2 more things:

First of all, for the classes extending BroadCastReceiver to work, need to be registered to Manifest file.
And second of all how do I schedule my notifications whem the phone reboots? 👀

So I made another Receiver which only needs to be registered on the Manifest and nothing more:

Now this one needs a small modification achieved by a boolean flag. What happens if my worker has never started and I request to schedule local data (which never arrived)? A total disaster.

And pretty much that's it. That's the best combination I could do to solve my case, and if you have other suggestions about my case, feel free to comment.

Popular posts from this blog

Modularizing your Android app, breaking the monolith (Part 1)

Inspired by a Martin Fowlers post about Micro Frontends, I decided to break my monolithic app into a modular app. I tried to read a little more about breaking monolithic apps in Android, and as far as I got, I felt confident to share my experience with you. This will be some series of blog posts where we actually try to break a simple app into a modularized Android app.

Note: You should know that I am no expert in this, so if there are false statements or mistakes please feel free to criticize, for the sake of a better development. 

What do you benefit from this approach:
Well, people are moving pretty fast nowadays and delivery is required faster and faster. So, in order to achieve this, modularising Android apps is really necessary.You can share features across different apps. Independent teams and less problems per each.Conditional features update.Quicker debugging and fixing.A feature delay doesn't delay the whole app. As per writing tests, there is not too much difference about…

What I learned from Kotlin Flow API

I used to check the docs and just read a lot about flows but didn't implement anything until yesterday. However, the API tasted really cool (even though some operations are still in Experimental state).Prerequisites: If you don't know RxJava it's fine. But a RxJava recognizer would read this faster.Cold vs Hot streamsWell, I really struggled with this concept because it is a little bit tricky. The main difference between cold and hot happened to be pretty simple: Hot streams produce when you don't care while in cold streams, if you don't collect() (or RxJava-s equivalent subscribe()) the stream won't be activated at all. So, Flows are what we call cold streams. Removing the subscriber will not produce data at all, making the Flows one of the most sophisticated asynchronous stream API ever (in the JVM world). I tried to make a illustration of hot and cold streams: Since I mentioned the word asynchronous this implies that they do support coroutines also. Flows vs…

From Gson to Moshi, what I learned

There is no doubt that people are getting away from GSON and I agree with those reasons too. The only advantage GSON has over other parsing libraries is that it takes a really short amount of time to set up. Furthermore, the most important thing is that Moshi is embracing Kotlin support.

First let's implement the dependency:
implementation("com.squareup.moshi:moshi:1.8.0") It's not a struggle to migrate to Moshi. It's really Gson look-a-like. The only thing to do is annotate the object with @field:Json instead of @SerializedName (which is Gsons way for JS representation):

data class User( //GSON way @SerializedName("name") val name: String, @SerializedName("user_name") val userName: String, @SerializedName("last_name") val lastName: String, @SerializedName("email") val email: String ) data class User( //Moshi way @field:Json(name = "name") val name: String, @field:Json(name = "user_name…