Photo by Brad Neathery on Unsplash
Accurate timekeeping is the unsung hero of modern app development. From scheduling events to securing transactions, your app’s functionality hinges on precise timestamps. But here’s the problem: your users can mess with their device’s clock, throwing your app into chaos. Ever had a calendar reminder fire at the wrong time? Or a one-time password fail because the clock was off? These issues aren’t just annoying — they can break your app’s core functionality.
That’s why TrustedTime API, a game-changing solution that leverages Google’s infrastructure to deliver accurate, tamper-proof timestamps — no matter what your users do to their device’s clock. Say goodbye to unreliable timekeeping and hello to precision you can trust.
Why Your App’s Timekeeping is Broken (and Why You Should Care)
Your app probably relies on the device’s local clock for critical features. But here’s the harsh truth: the device’s clock is unreliable.Before diving into TrustedTime, let’s understand why we need it. Android apps face several challenges when relying on system time:
- User Manipulation: Users can easily change their device’s time settings, intentionally or accidentally
- Clock Drift: Device clocks can drift due to factors like temperature, battery level, and doze mode
- Cross-Device Inconsistency: Different devices may have slightly different times, causing sync issues
- Security Vulnerabilities: Time-based security features can be compromised if users manipulate the system time
Why TrustedTime is a Game-Changer
Here’s why developers are raving about TrustedTime:
- No More Tampering: Users can’t mess with the time your app sees.
- Accurate Offline Timestamps: Even when the device is offline, TrustedTime provides reliable timestamps.
- Consistency Across Devices: Perfect for multi-device apps like games or collaborative tools.
- Better Performance: TrustedTime reduces network requests, saving battery and data.
How TrustedTime Works: The Secret Sauce
The TrustedTime API taps into Google’s secure infrastructure to deliver accurate timestamps. Here’s how it works:
- Periodic Sync: TrustedTime syncs its clock with Google’s highly accurate time servers, so you don’t have to make a network request every time you need the current time.
- Clock Drift Calculation: The API calculates your device’s clock drift, so you know when the time might be slightly off between syncs.
- Efficient Design: TrustedTime minimizes network usage by syncing periodically and using the device’s internal clock as a reference. This saves battery and data while keeping time accurate.
Getting Started with TrustedTime API
Integrating TrustedTime into your app is easy, especially with Koin for dependency injection and Kotlin Coroutines for asynchronous initialization. Here’s how to do it:
Step 1: Add Dependencies
Add the required dependencies to your build.gradle
file:
dependencies { implementation("com.google.android.gms:play-services-time:16.0.1") implementation("io.insert-koin:koin-android:3.2.0") // Koin for dependency injection implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4") // Coroutines support for Google Play services }
Step 2: Create a Wrapper for TrustedTimeClient
Since TrustedTime.createClient(context)
is asynchronous, we’ll create a wrapper class to handle initialization:
import android.content.Context | |
import android.util.Log | |
import com.google.android.gms.time.TrustedTimeClient | |
import kotlinx.coroutines.CoroutineScope | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.SupervisorJob | |
import kotlinx.coroutines.launch | |
import kotlinx.coroutines.tasks.await | |
interface TimeService { | |
/** | |
* Gets the TrustedTimeClient if initialized. | |
* @return Initialized TrustedTimeClient or null | |
*/ | |
fun getClient(): TrustedTimeClient? | |
/** | |
* Gets the current time in milliseconds if the client is initialized. | |
* @return Current time in milliseconds or fallback to system time if client isn't initialized | |
*/ | |
fun getCurrentTimeMillis(): Long | |
} | |
class TimeServiceImpl ( | |
private val context: Context, | |
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) | |
): TimeService { | |
private var trustedTimeClient: TrustedTimeClient? = null | |
init { | |
initializeAsync() | |
} | |
/** | |
* Initializes the TrustedTimeClient asynchronously. | |
*/ | |
private fun initializeAsync() { | |
scope.launch { | |
try { | |
trustedTimeClient = TrustedTime.createClient(context).await() | |
Log.d(TAG, "TrustedTimeClient initialized successfully") | |
} catch (e: Exception) { | |
Log.e(TAG, "Failed to initialize TrustedTimeClient", e) | |
} | |
} | |
} | |
/** | |
* Gets the TrustedTimeClient if initialized. | |
* @return Initialized TrustedTimeClient or null | |
*/ | |
override fun getClient(): TrustedTimeClient? = trustedTimeClient | |
/** | |
* Gets the current time in milliseconds if the client is initialized. | |
* @return Current time in milliseconds or fallback to system time if client isn't initialized | |
*/ | |
override fun getCurrentTimeMillis(): Long { | |
return trustedTimeClient?.computeCurrentUnixEpochMillis() ?: run { | |
Log.w(TAG, "TrustedTimeClient not initialized, falling back to system time") | |
System.currentTimeMillis() | |
} | |
} | |
companion object { | |
private const val TAG = "TrustedTimeClientWrapper" | |
} | |
} |
Step 3: Define the Koin Module
Define a Koin module to provide the TrustedTimeClientWrapper
:
val trustedTimeModule = module { single<TimeService> { TimeServiceImpl(get()) // Provide the wrapper } }
Step 4: Initialize Koin and TrustedTimeClient
In your Application
class, initialize Koin and the TrustedTimeClientWrapper
asynchronously:
class MyApplication : Application() { override fun onCreate() { super.onCreate() // Initialize Koin startKoin { androidContext(this@MyApplication) modules(trustedTimeModule) } } }
Use TrustedTimeClient in Your App
Inject the TrustedTimeClientWrapper
and use the TrustedTimeClient
in your app. Here’s an example in a ViewModel
:
class MainViewModel( private val timeService: TimeService ) : ViewModel() { private val _currentTime = MutableStateFlow<Long>(0) val currentTime: StateFlow<Long> = _currentTime.asStateFlow() fun updateCurrentTime() { viewModelScope.launch { _currentTime.value = timeService.getCurrentTimeMillis() } } }
Job Offers
What TrustedTime API Really Is (And Isn’t)
Let’s clear up a common misconception right away: TrustedTime API doesn’t synchronize your device’s time. Instead, it provides your app with accurate time information from Google’s servers, independent of the device’s settings. Think of it as having a separate, reliable clock that your app can consult, while the device’s clock remains unchanged.
Here’s what makes it special:
- It provides timestamps from Google’s secure infrastructure
- It maintains its own time tracking separate from the device clock
- It includes error estimates with each timestamp
- It works offline after initial synchronization
Limitations
While TrustedTime provides a more reliable time source, it’s important to note:
- Requires Google Play Services: TrustedTime is only available on devices with Google Play Services.
- Needs Internet Connection: TrustedTime requires an internet connection after device boot to provide timestamps.
- Clock Drift: Device’s internal clock can drift due to factors like temperature, doze mode, and battery level. TrustedTime doesn’t prevent this drift, but its APIs provide an error estimate for each timestamp.
- Advanced Tampering: While TrustedTime makes it harder for users to manipulate time, it doesn’t guarantee complete protection against advanced techniques.
Conclusion
The TrustedTime API offers a robust solution for Android apps requiring reliable time management. While it has some limitations, it’s a significant improvement over relying solely on system time for time-sensitive operations.
Remember to consider your specific use case when deciding whether to implement TrustedTime, and always plan for fallback scenarios when the API might not be available.
This article is previously published on proandroiddev.com.