Android System date changed event

12,242

Solution 1

There are broadcasts for those events. ACTION_TIME_CHANGED and ACTION_DATE_CHANGED ACTION docs at http://developer.android.com/reference/android/content/Intent.html#ACTION_DATE_CHANGED

A potential bug and some implementation details available at http://code.google.com/p/android/issues/detail?id=2880

Solution 2

I've made a nice way to detect and update the UI whenever needed, when the date has changed. It's based on the broadcastReceiver, which should be registered on the onStart method, and unregistered on the onStop method (unregistration is done automatically in my code).

build.gradle

implementation 'com.jakewharton.threetenabp:threetenabp:1.0.5'

LocalDateEx.kt

object LocalDateEx {
    /**an alternative of LocalDate.now(), as it requires initialization using AndroidThreeTen.init(context), which takes a bit time (loads a file)*/
    @JvmStatic
    fun getNow(): LocalDate = Calendar.getInstance().toLocalDate()

}

fun Calendar.toLocalDate(): LocalDate = LocalDate.of(get(Calendar.YEAR), get(Calendar.MONTH) + 1, get(Calendar.DAY_OF_MONTH))

DateChangedBroadcastReceiver.kt

abstract class DateChangedBroadcastReceiver : BroadcastReceiver() {
    private var curDate = LocalDateEx.getNow()

    /**called when the receiver detected the date has changed. You should still check it yourself, because you might already be synced with the new date*/
    abstract fun onDateChanged(previousDate: LocalDate, newDate: LocalDate)

    @Suppress("MemberVisibilityCanBePrivate")
    fun register(context: Context, date: LocalDate) {
        curDate = date
        val filter = IntentFilter()
        filter.addAction(Intent.ACTION_TIME_CHANGED)
        filter.addAction(Intent.ACTION_DATE_CHANGED)
        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
        context.registerReceiver(this, filter)
        val newDate = LocalDateEx.getNow()
        if (newDate != curDate) {
            curDate = newDate
            onDateChanged(date, newDate)
        }
    }

    /**a convenient way to auto-unregister when activity/fragment has stopped. This should be called on the onStart method of the fragment/activity*/
    fun registerOnStart(activity: AppCompatActivity, date: LocalDate, fragment: Fragment? = null) {
        register(activity, date)
        val lifecycle = fragment?.lifecycle ?: activity.lifecycle
        lifecycle.addObserver(object : LifecycleObserver {
            @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
            fun onStop() {
                Log.d("AppLog", "onStop, so unregistering")
                lifecycle.removeObserver(this)
                activity.unregisterReceiver(this@DateChangedBroadcastReceiver)
            }
        })
    }

    override fun onReceive(context: Context, intent: Intent) {
        val newDate = LocalDateEx.getNow()
        Log.d("AppLog", "got intent:" + intent.action + " curDate:" + curDate + " newDate:" + newDate)
        if (newDate != curDate) {
            Log.d("AppLog", "cur date is different, so posting event")
            val previousDate = curDate
            curDate = newDate
            onDateChanged(previousDate, newDate)
        }
    }

}

MainActivity.kt

class MainActivity : AppCompatActivity() {
    var today = LocalDateEx.getNow()
    val receiver = object : DateChangedBroadcastReceiver() {
        override fun onDateChanged(previousDate: LocalDate, newDate: LocalDate) {
            Log.d("AppLog", "onDateChangedEvent:" + newDate + " previousDate:" + previousDate)
            if (newDate != today) {
                today = newDate
                textView.text = "date updated:" + today
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("AppLog", "onCreate")
        setContentView(R.layout.activity_main)
        textView.text = today.toString()
    }

    override fun onStart() {
        super.onStart()
        receiver.registerOnStart(this, today)
    }
}

activity_main.xml

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:layout_gravity="center"/>

</FrameLayout>

Alternative to the above, using Android's Calendar class only, without the need to use another library:

fun Calendar.equalInDateAlone(cal: Calendar): Boolean =
        get(Calendar.YEAR) == cal.get(Calendar.YEAR) && get(Calendar.MONTH) == cal.get(Calendar.MONTH) && get(Calendar.DAY_OF_MONTH) == cal.get(Calendar.DAY_OF_MONTH)

fun Calendar.resetTimeFields(): Calendar {
    set(Calendar.HOUR_OF_DAY, 0)
    set(Calendar.SECOND, 0)
    set(Calendar.MINUTE, 0)
    set(Calendar.MILLISECOND, 0)
    return this
}

abstract class DateChangedBroadcastReceiver : BroadcastReceiver() {
    private var curDate = Calendar.getInstance().resetTimeFields()

    /**called when the receiver detected the date has changed. You should still check it yourself, because you might already be synced with the new date*/
    abstract fun onDateChanged(previousDate: Calendar, newDate: Calendar)

    @Suppress("MemberVisibilityCanBePrivate")
    fun register(context: Context, date: Calendar) {
        curDate = (date.clone() as Calendar).resetTimeFields()
        val filter = IntentFilter()
        filter.addAction(Intent.ACTION_TIME_CHANGED)
        filter.addAction(Intent.ACTION_DATE_CHANGED)
        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
        context.registerReceiver(this, filter)
        val newDate = Calendar.getInstance().resetTimeFields()
        if (!newDate.equalInDateAlone(curDate)) {
            curDate = newDate.clone() as Calendar
            onDateChanged(date, newDate)
        }
    }

    /**a convenient way to auto-unregister when activity/fragment has stopped. This should be called on the onStart method of the fragment/activity*/
    @Suppress("unused")
    fun registerOnStart(activity: AppCompatActivity, date: Calendar, fragment: Fragment? = null) {
        register(activity, date)
        val lifecycle = fragment?.lifecycle ?: activity.lifecycle
        lifecycle.addObserver(object : LifecycleObserver {
            @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
            fun onStop() {
//                Log.d("AppLog", "onStop, so unregistering")
                lifecycle.removeObserver(this)
                activity.unregisterReceiver(this@DateChangedBroadcastReceiver)
            }
        })
    }

    override fun onReceive(context: Context, intent: Intent) {
        val newDate = Calendar.getInstance().resetTimeFields()
//        Log.d("AppLog", "got intent:" + intent.action + " curDate:" + curDate.toSimpleDateString() + " newDate:" + newDate.toSimpleDateString())
        if (!newDate.equalInDateAlone(curDate)) {
//            Log.d("AppLog", "cur date is different, so posting event")
            val previousDate = curDate
            curDate = newDate
            onDateChanged(previousDate, newDate)
        }
    }

}
Share:
12,242
Mustansar Saeed
Author by

Mustansar Saeed

I have over 10-years of professional work experience that includes industry and research. My research interests are in systems and span mobile computing and ICT for development. Summary of the experience include: Developed an end-to-end real testbed for on-device machine learning using privacy-preserving machine learning platforms Developed Pakistan's first largest school education system deployed across all schools of Punjab province of Pakistan Developed Pakistan's electronic challan system deployed across Pakistan by NHMP Developed data collecting platform that contributed to raise funding from Gates Foundation Developed eye detection and recognition system for the waste management departments of multiple Punjab districts to mark and record attendance Machine Learning: TensorFlow, PyTorch, pandas, NumPy, Matplotlib, FFmpeg, privacy-preserving machine learning Languages: Android/Kotlin, Java, Python, RxJava2, Room, Retrofit, SQLite Automation: Jenkins Architectures: MVC, MVP, MVVM Source Control: Git, SVN Developer Tools: Google Cloud Platform, Docker, Android Studio, PyCharm, IntelliJ, Eclipse Project Management: Slack, JIRA, TFS, Trello Datastores &amp; web servers: SQLite, MySQL, Apache Tomcat Operating Systems: Android, Ubuntu, Windows I am committed, self-motivated, results-driven and have a good attitude towards problem-solving.

Updated on June 04, 2022

Comments

  • Mustansar Saeed
    Mustansar Saeed almost 2 years

    I know this question has been asked many times but i am unable to find the right direction. I have registered BroadcastReceiver which I want to trigger when the Android System Date is changed automatically but its not firing. I have used the following approaches:

    1

    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
    registerReceiver(new DateTimeChangeReceiver (), intentFilter);
    

    2-> AndroidManifest.xml

    <receiver android:name=".DateTimeChangeReceiver ">
    <intent_filter>
        <action android:name="android.intent.action.DATE_CHANGED"/>
    </intent_filter>
    </receiver>
    

    DateTimeChangeReceiver

    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.widget.Toast;
    
    public class DateTimeChangeReceiver extends BroadcastReceiver{
    
        @Override
        public void onReceive(Context context, Intent intent) {
            // TODO Auto-generated method stub
            Toast.makeText(context, "Date changed", Toast.LENGTH_SHORT).show();
        }
    
    }
    

    In both cases, reciever is not being triggered. It only shows the notification when manually time is being set. Can anyone please point me out what I am missing?

    Thanks in advance.