diff --git a/android/.gitignore b/android/.gitignore
index 67e07b8fea40e2050dce30710b53f2278836ecb9..e00b1df3a76b3ff2daf48f412bff7175fffbcecd 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -1,2 +1,3 @@
 /build
 /release
+version.properties
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
index 30ac2080564beeab4eebb027eee61d39ebd00227..4e4f76b07336bff08125eeea689f79cfd65c1a39 100644
--- a/android/build.gradle.kts
+++ b/android/build.gradle.kts
@@ -1,5 +1,8 @@
 @file:Suppress("UnstableApiUsage", "DSL_SCOPE_VIOLATION", "KaptUsageInsteadOfKsp")
 
+import java.io.FileInputStream
+import java.util.Properties
+
 plugins {
     alias(libs.plugins.android.application)
     alias(libs.plugins.kotlin.android)
@@ -12,12 +15,71 @@ android {
     namespace = "foundation.e.blissweather"
     compileSdk = 33
 
+    val versionPropsFile = file("version.properties")
+    val versionProps = Properties()
+
+    if (!versionPropsFile.exists()) {
+        versionProps["VERSION_CHANGE"] = "0"
+        versionProps["VERSION_MAJOR"] = "1"
+        versionProps["VERSION_MINOR"] = "0"
+        versionProps["VERSION_PATCH"] = "0"
+        versionProps["VERSION_CODE"] = "1"
+        versionProps.store(versionPropsFile.writer(), null)
+    }
+
+    fun getVersionCode(): Int {
+        if (versionPropsFile.canRead()) {
+            versionProps.load(FileInputStream(versionPropsFile))
+            var versionChange = Integer.valueOf(versionProps["VERSION_CHANGE"].toString()) + 1
+            var versionMinor = Integer.valueOf(versionProps["VERSION_MINOR"].toString())
+            var versionMajor = Integer.valueOf(versionProps["VERSION_MAJOR"].toString())
+            var versionPatch = Integer.valueOf(versionProps["VERSION_PATCH"].toString())
+            // Up version on each 100 cycles of builds
+            if (versionChange >= 100) {
+                versionPatch += 1
+                versionChange = 0
+            }
+            if (versionPatch == 9) {
+                versionMinor = versionPatch + 1
+                versionPatch = 0
+            }
+            if (versionMinor == 9) {
+                versionMajor += 1
+                versionMinor = 0
+            }
+            val versionCode = Integer.valueOf(versionProps["VERSION_CODE"].toString())
+
+            versionProps["VERSION_CHANGE"] = versionChange.toString()
+            versionProps["VERSION_PATCH"] = versionPatch.toString()
+            versionProps["VERSION_MINOR"] = versionMinor.toString()
+            versionProps["VERSION_MAJOR"] = versionMajor.toString()
+            versionProps["VERSION_CODE"] = (Integer.valueOf(versionCode) + 1).toString()
+            versionProps.store(versionPropsFile.writer(), null)
+
+            return versionCode
+        }
+        return 1
+    }
+
+    fun getVersionName(): String {
+        if (versionPropsFile.canRead()) {
+            versionProps.load(FileInputStream(versionPropsFile))
+
+            val versionMajor = versionProps["VERSION_MAJOR"]
+            val versionMinor = versionProps["VERSION_MINOR"]
+            val versionPatch = versionProps["VERSION_PATCH"]
+
+            return "${versionMajor}.${versionMinor}.${versionPatch}"
+        }
+        return "1.0"
+    }
+
     defaultConfig {
         applicationId = "foundation.e.blissweather"
         minSdk = 25
         targetSdk = 33
-        versionCode = 1
-        versionName = "1.0"
+        versionCode = getVersionCode()
+        versionName = getVersionName()
 
         multiDexEnabled = true
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 259c191fe6514139fd0f07cf697413dfa00aad36..ebf2e63d2db39d7cba55fe57d5dd9d435eab385b 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
     
     
     
+    
 
     (refreshInterval, TimeUnit.MINUTES)
+                // Don't use this periodic worker, it doesn't work on idle.
+                PeriodicWorkRequestBuilder(30, TimeUnit.DAYS)
                     .setConstraints(constraints)
                     .build()
             )
+
+            // Use AlarmManager for repeated job
+            AlarmUtils.cancelRepeatingUpdatesCheck(context)
+            AlarmUtils.scheduleRepeatingUpdatesCheck(context, refreshInterval)
         } else {
             workMgr
                 .beginWith(
@@ -74,6 +80,9 @@ class WeatherAppWidgetProvider : AppWidgetProvider() {
                         .build()
                 )
                 .enqueue()
+
+            // Cancel any repeating alarm
+            AlarmUtils.cancelRepeatingUpdatesCheck(context)
         }
     }
 
@@ -83,7 +92,7 @@ class WeatherAppWidgetProvider : AppWidgetProvider() {
     }
 
     override fun onReceive(context: Context, intent: Intent?) {
-        Log.d(TAG, "onReceive: $intent}")
+        Log.d(TAG, "onReceive: $intent")
         super.onReceive(context, intent)
         when (intent?.action) {
             ACTION_WEATHER_REFRESH,
diff --git a/android/src/main/kotlin/foundation/e/blissweather/worker/AlarmUtils.kt b/android/src/main/kotlin/foundation/e/blissweather/worker/AlarmUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..352a87df1f6b0b23a014b7a849affbbc55da868f
--- /dev/null
+++ b/android/src/main/kotlin/foundation/e/blissweather/worker/AlarmUtils.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 MURENA SAS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package foundation.e.blissweather.worker
+
+import android.annotation.SuppressLint
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import foundation.e.blissweather.widget.WeatherAppWidgetProvider
+import java.util.Date
+
+object AlarmUtils {
+    private const val TAG = "AlarmUtils"
+
+    @SuppressLint("ScheduleExactAlarm")
+    fun scheduleRepeatingUpdatesCheck(context: Context, refreshInterval: Long) {
+        val updateCheckIntent: PendingIntent = getRepeatingUpdatesCheckIntent(context)
+        val alarmMgr = context.getSystemService(AlarmManager::class.java)
+        val nextCheck: Long = System.currentTimeMillis() + (refreshInterval * 60 * 1000)
+        alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextCheck, updateCheckIntent)
+        Log.d(TAG, "Setting automatic updates check: ${Date(nextCheck)}")
+    }
+
+    private fun getRepeatingUpdatesCheckIntent(context: Context): PendingIntent {
+        val intent = Intent(context, WeatherAppWidgetProvider::class.java)
+        intent.setAction(WeatherAppWidgetProvider.ACTION_WEATHER_REFRESH)
+        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+    }
+
+    fun cancelRepeatingUpdatesCheck(context: Context) {
+        val alarmMgr = context.getSystemService(AlarmManager::class.java)
+        alarmMgr.cancel(getRepeatingUpdatesCheckIntent(context))
+    }
+}