My app is checking for unread emails in the background, the problem is that i need to save and retrieve lastCheckedDate when i last checked for emails so i can show only newly received emails.
For retrieving data from datastore i use observeLastCheckedDate() and i must call it with handler because if i dont i get:
java.lang.IllegalStateException: Cannot invoke observe on a background thread
Function observeLastCheckedDate() get called but while it finish(updates lastCheckedDate), workManager task is already finished with not-updated var lastchecked date.
In main class i avoid this problem by creating and invoking callback but here that does not work(it makes whole app freeze), so we somehow need to wait for that function to finish or get some new way of retreiving data from datastore.
class WorkerMan(private val mContext: Context, workerParameters: WorkerParameters) :
CoroutineWorker(mContext, workerParameters) {
private lateinit var settingsManager: SettingsManager
var lastCheckedDate: Long = 0
@SuppressLint("RestrictedApi", "CheckResult")
val email = inputData.getString("email")
val password = inputData.getString("password")
val token = inputData.getString("token")
fun saveLastCheckedDate(lastCheckedDate: Long) {
GlobalScope.launch {
settingsManager.storeLastCheckedDate(lastCheckedDate)
}
}
private fun observeLastCheckedDate() {
settingsManager.lastCheckedDateFlow.asLiveData().observe(
ProcessLifecycleOwner.get(),
{
lastCheckedDate = it
println("LASTCHECKEDDATE LOADED")
}
)
}
@SuppressLint("RestrictedApi", "WrongThread")
override suspend fun doWork(): Result {
withContext(Dispatchers.IO) {
settingsManager = SettingsManager(getApplicationContext())
var messageCounter = 0;
val handler = Handler(Looper.getMainLooper())
handler.post {
observeLastCheckedDate()
}
println("**************************************************************************")
println("**************************************************************************")
println("WorkManager: Work called")
println("WorkManager email: " + email)
println("WorkManager: Last Checked Moment : " + lastCheckedDate.toString())
println("WorkManager: Current Moment : " + Instant.now().toEpochMilli())
println("**************************************************************************")
println("**************************************************************************")
try {
val session = Session.getDefaultInstance(Properties())
val store = session.getStore("imaps")
store.connect(
"mail.metropolitan.ac.rs",
993,
email,
password
)
val inbox = store.getFolder("INBOX")
inbox.open(Folder.READ_ONLY)
val messages = inbox.search(
FlagTerm(Flags(Flags.Flag.SEEN), false)
)
Arrays.sort(
messages
) { m1: Message, m2: Message ->
try {
return@sort m2.sentDate.compareTo(m1.sentDate)
} catch (e: MessagingException) {
throw RuntimeException(e)
}
}
// println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ")
// println("WorkManager Started")
// println("WorkMananager email: " + email)
// val current = LocalTime.now()
// println("WorkMananager time: " + current)
// println("Messages amount: " + messages.size)
// println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ")
for (message in messages) {
Thread.sleep(1000)
messageCounter++
if (message.receivedDate.toInstant().toEpochMilli() >= lastCheckedDate) {
Thread.sleep(1000)
println("=====================================================")
println("NOTIFIKACIJA")
var title = ""
for (element in message.from) {
title += element.toString().substringAfter("<").substringBefore(">")
title += " "
}
println("Title :" + title)
println("Subject :" + message.subject)
println("Datum i vreme : " + message.receivedDate)
title.replace("[", "")
title.replace("]", "")
send(token, message.subject, title)
}
if (messageCounter > 10) {
break
}
}
saveLastCheckedDate(Instant.now().toEpochMilli())
println("=====================================================")
Log.d("WorkManager", "Job finished")
} catch (e: Exception) {
Log.d("WorkManager error", "doWork not executed")
Log.d("WorkManager error", "error: ")
Log.d("WorkManager error", e.printStackTrace().toString())
} catch (e: NetworkOnMainThreadException) {
Log.d("WorkManager error", "doWork not executed")
Log.d("WorkManager error", "NetworkOnMainThreadException: ")
Log.d("WorkManager error", e.toString())
}
}
return Result.Success();
}
}
fun send(to: String?, body: String?, title: String?): String? {
try {
val apiKey =
"***************************************"
val url = URL("https://fcm.googleapis.com/fcm/send")
val conn = url.openConnection() as HttpURLConnection
conn.doOutput = true
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", "application/json")
conn.setRequestProperty("Authorization", "key=$apiKey")
conn.doOutput = true
val message = JSONObject()
message.put("to", to)
message.put("priority", "high")
val notification = JSONObject()
notification.put("title", title)
notification.put("body", body)
message.put("notification", notification)
val os = conn.outputStream
os.write(message.toString().toByteArray())
os.flush()
os.close()
val responseCode = conn.responseCode
println("\nSending 'POST' request to URL : $url")
println("Post parameters : $message")
println("Response Code : $responseCode")
println("Response Code : " + conn.responseMessage)
val `in` = BufferedReader(InputStreamReader(conn.inputStream))
var inputLine: String?
val response = StringBuffer()
while (`in`.readLine().also { inputLine = it } != null) {
response.append(inputLine)
}
`in`.close()
println(response.toString())
return response.toString()
} catch (e: Exception) {
Log.d("WorkManager error", "send not executed")
Log.d("WorkManager error", "error: ")
Log.d("WorkManager error", e.printStackTrace().toString())
} catch (e: NetworkOnMainThreadException) {
Log.d("WorkManager error", "send() not executed")
Log.d("WorkManager error", "NetworkOnMainThreadException: ")
Log.d("WorkManager error", e.toString())
}
return "error"
}
DataStore class:
class SettingsManager(context: Context) {
private val dataStore = context.createDataStore(name = "user_settings_preferencess")
companion object {
val ENABLE_NOTIFICATIONS = preferencesKey<Int>("ENABLE_NOTIFICATIONS")
val ENABLE_MAIL_NOTIFICATIONS = preferencesKey<Int>("ENABLE_MAIL_NOTIFICATIONS")
val LAST_CHECKED_DATE = preferencesKey<Long>("LAST_CHECKED_DATE")
}
//Store user data
suspend fun storeNotifications(enableNotifications: Int) {
dataStore.edit {
it[ENABLE_NOTIFICATIONS] = enableNotifications
}
}
suspend fun storeMailNotifications(enableMailNotifications: Int) {
dataStore.edit {
it[ENABLE_MAIL_NOTIFICATIONS] = enableMailNotifications
}
}
suspend fun storeLastCheckedDate(lastCheckedDate: Long) {
dataStore.edit {
it[LAST_CHECKED_DATE] = lastCheckedDate
}
}
val lastCheckedDateFlow: Flow<Long> = dataStore.data.map {
it[LAST_CHECKED_DATE] ?: 0
}
val enableNotificationsFlow: Flow<Int> = dataStore.data.map {
it[ENABLE_NOTIFICATIONS] ?: 1
}
val enableMailNotificationsFlow: Flow<Int> = dataStore.data.map {
it[ENABLE_MAIL_NOTIFICATIONS] ?: 1
}
}
That's a HUGE mess with threading for simple work. (Never make your thread sleep to wait for a value)
if you going to use coroutines in the worker class. SO DON'T DO THAT
there is an alternative CoroutineWorker to extend your class from it instead of Worker
it will provide you with suspending version of doWork()
function
NOTE: remember to add the -ktx
version of the work manager dependency