True Time API Para Data e Horário NTP no Android

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /True Time API Para Data e Horário NTP no Android

True Time API Para Data e Horário NTP no Android

Vinícius Thiengo
(11605) (2)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloTest-Driven Development: Teste e Design no Mundo Real
CategoriaEngenharia de Software
Autor(es)Mauricio Aniche
EditoraCasa do Código
Edição1
Ano2012
Páginas194
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Tudo bem?

Neste artigo vamos ao estudo da importância de se ter data e horário exatos em alguns domínios de problema Android.

Também aprenderemos como obter essa exatidão utilizando uma biblioteca específica para comunicação com servidores NTP, mais precisamente a biblioteca True Time Android.

Como foi feito no artigo sobre leitor de código, onde construimos um aplicativo simples, mas completo, aqui construiremos por inteiro, depois do estudo da True Time API, um app de "horário mundial". App que você poderá seguir com a evolução dele e disponibiliza-lo em sua conta da Google Play Store, por exemplo.

Animação da mudança de fuso horário no app Android Relógio Mundial

Neste artigo, como em outros aqui do Blog, o termo "API" será utilizado como sinônimo de "biblioteca" (library).

Antes de prosseguir, não esqueça de se inscrever 📩 na lista de emails do Blog para receber os conteúdos exclusivos e em primeira mão.

Abaixo os tópicos que estaremos estudando:

Network Time Protocol - NTP

O Network Time Protocol (NTP), ou Protocolo de Tempo para Redes, é o protocolo que permite a sincronização dos relógios de dispositivos que se conectam a Internet.

O NTP é extremamente importante para qualquer dispositivo que se conecta a rede, um forte indício disso é que qualquer aparelho computacional que adquirimos já vem com a configuração de obtenção de data e hora em: atualização automática.

A discussão detalhada sobre o funcionamento do NTP você consegue no site oficial NTP.br mantido pelo Comitê Gestor da Internet (CGI).

Estou indicando o CGI para mais informações, pois o estudo por completo do NTP tende a não ser de grande importância a um profissional de desenvolvimento, principalmente porque o dispositivo que receberá o software desenvolvido certamente já tem nele toda a tecnologia de requisição de data e horário a servidores NTP.

O que é importante ao profissional de desenvolvimento de software é saber se o domínio do problema atualmente trabalhado por ele exige, ou não, o gerenciamento de "data e hora exatos", independente da data e horário fornecidos pelo próprio aparelho.

Isso, pois o aparelho não é uma entidade confiável em termos de data e hora exatos, principalmente porque o usuário pode mudar estes dados para atender a alguma necessidade dele, por exemplo: adiantar o horário em dez minutos para sempre conseguir sair ao menos dez minutos antes aos compromissos.

Simple Network Time Protocol - SNTP

O Simple Network Time Protocol (SNTP), ou Protocolo Simples de Tempo para Rede, é uma versão simplificada do NTP, versão que não exige, por exemplo, a complexidade de obter informações de tempo de um determinado servidor, informações como: deslocamento, dispersão e variação.

Resumidamente, o SNTP permite: consultar o tempo em um servidor e ajustar o relógio local de tempos em tempos. O NTP vai mais além, como, por exemplo: obter os dados de tempo (data e hora) da fonte mais confiável dentre todas as pesquisadas.

Para ficar mais claro: se em seu domínio é necessária uma requisição a um servidor qualquer para então obter a data e horário desse servidor como fonte de atualização de relógio, essa foi uma atualização utilizando o protocolo SNTP.

Agora se em seu domínio do problema houve a requisição a uma série de servidores de data e hora, incluindo o trabalho de escolha da melhor resposta. Isso já se caracteriza como fluxo de uso do protocolo NTP.

A biblioteca que estudaremos neste artigo trabalha com a versão completa do NTP, mesmo ela exigindo poucas linhas de código.

Quando o NTP é importante para um desenvolvedor Android

Certamente você deve estar se perguntando: Para desenvolvedores Android, qual a utilidade do conhecimento de alguma API NTP?

A importância deste conhecimento fica evidente principalmente quando trabalhando em domínios de problema onde há necessidade de comunicação remota com servidores Web e também à exigência de trabalho offline, o app continua funcionando (de maneira limitada) mesmo quando não há conexão com a rede.

Aplicativos de comércio eletrônico, por exemplo, exigem inúmeros dados que serão utilizados na aplicação de inteligência em negócios, para assim decisões acertadas serem tomadas, decisões de promoções em determinadas regiões, por exemplo.

Conhecer o tempo exato da maioria das interações dos usuários de seu aplicativo de compras online provavelmente trará maior precisão nas tomadas de decisões.

Note que o conjunto de domínios de problemas que exigem exatidão em data e horário, uso de API NTP, não é tão extenso quanto o conjunto de domínios que exigem o trabalho com alguma API de carregamento de imagens remotas, mas certamente os domínios para NTP também existem em grande número e até o momento da construção deste artigo nós, desenvolvedores Android, temos somente uma API open source que permite requisição a servidores NTP.

Biblioteca True Time

A biblioteca True Time Android foi desenvolvida para o simples fornecimento de uma data e horário confiáveis, obtidos de servidores NTP.

Logo da True Time Android API

O projeto é open source e tem muita aceitação da comunidade de desenvolvedores Android. Até o momento da construção deste conteúdo eram mais de 690 estrelas de recomendação no GitHub, algo surpreendente para uma API específica de domínio.

A True Time API atende a partir do Android API 14, Ice Cream Sandwich, e apesar de algumas limitações encontradas, se o desenvolvedor Android souber trabalhar com entidades como AsyncTask e LocalBroadcastManager, certamente ele vai conseguir o máximo possível da True Time.

Instalação da API

A biblioteca está disponível no Jitpack, este que é um repositório de pacotes de projetos JVM e Android. Logo, no Gradle Project Level, adicione a seguinte referência em destaque:

...
allprojects {
repositories {
google()
jcenter()
maven {
url "https://jitpack.io"
}
}
}
...

 

Então, no Gradle App Level, adicione:

...
dependencies {
...

implementation 'com.github.instacart.truetime-android:library-extension-rx:3.3'
}

 

Veja que acima estamos referenciando a versão Rx (ReactiveX) da True Time API.

Na documentação há também a versão vanilla, versão que não exige o conhecimento da sintaxe do RxJava quando trabalhando com a True Time.

Porém na mesma documentação ainda é recomendado o uso da versão Rx, pois ela é a versão que implementa a requisição NTP completa e não somente requisição SNTP, como acontece com a versão vanilla.

Devido a isso, aqui estudaremos somente a versão Rx. Fique tranquilo se você ainda não conhece o RxJava, a interface da True Time API é simples de entender e de utilizar, ou seja, este "não conhecimento" não lhe atrapalhará.

Configuração inicial para requisição a servidores NTP

O código de requisição da True Time API deve entrar no método onCreate() de uma classe Application. Veja o código a seguir:

class CustomApplication: Application() {

override fun onCreate() {
super.onCreate()

TrueTimeRx

/*
* Obtendo o objeto TrueTimeRx. Note que essa é a
* implementação do padrão Singleton, ou seja, sempre
* estaremos utilizando o mesmo objeto TrueTimeRx.
* */
.build()

/*
* O resultado retornado de algum dos servidores NTP
* deve ser colocado em cache, cache gerenciado pela
* própria biblioteca e utilizando a API
* SharedPreferences. O contexto, argumento, sempre
* é o da aplicação.
* */
.withSharedPreferences( this )

/*
* Definição do conjunto, pool de servidores, NTP para
* a obtenção da data e horário confiáveis.
* */
.initializeRx( "time.apple.com" )

/*
* O método a seguir permite que nós desenvolvedores
* especifiquemos em qual operador de comunicação e
* Thread a entidade observável trabalhará. Aqui a
* entidade observável é o "algoritmo de requisição
* a servidores NTP", onde o resultado dessa requisição
* deve ser enviado a todos os observadores (inscritos
* via método subscribe(), por exemplo). Segundo a
* documentação Rx da TrueTime API, nosso operador deve
* ser o Schedulers.io().
* */
.subscribeOn( Schedulers.io() )

/*
* Assina uma entidade observadora para resultado
* positivo (sem erro em tempo de execução), primeiro
* argumento. E assina uma entidade observadora para
* resultado negativo, com erro em tempo de execução,
* segundo argumento. Qualquer resultado é transmitido
* via operador definido em subscribeOn(). A origem do
* resultado é uma entidade observável, aqui o "algoritmo
* de comunicação com servidores NTP". Há sobrecargas
* de subscribe(), mas a versão abaixo tende a ser a
* utilizada em todos os domínios da True Time API. Abaixo
* estamos fazendo uso da versão Lambda, ou literais de
* funções, de implementações de Consumer, como na
* documentação.
* */
.subscribe(
{
/*
* it é do tipo Date e contém a melhor informação
* de relógio obtida no conjunto de servidores NTP
* definido em initializeRx().
* */
Log.d( "LOG", "TrueTime: $it" );
},
{
/*
* it é do tipo Throwable e contém a pilha de erros.
* */
it.printStackTrace();
}
)
}
}

 

Se o método withSharedPreferences() não for invocado com o argumento correto, não será possível reaproveitar a data e horário já retornados por um servidor NTP.

O dado em cache, devido ao uso de withSharedPreferences(), permanecerá em disco até o momento que houver o reboot do aparelho. Após o reboot, assim que o aplicativo for aberto será necessária uma nova conexão NTP para obter novamente a data e horário corretos.

Apesar das explicações sobre os métodos subscribeOn()subscribe(), pode ser que você queira se aprofundar ainda mais, logo, ao terminar o artigo, entre nos links a seguir:

Pode estar surgindo a dúvida sobre a necessidade de uso dos métodos subscribeOn()subscribe() somente para uma simples requisição a servidores NTP. Segundo meus testes, se não houver a definição destes métodos, não haverá requisição.

Agora, para que a requisição do código anterior funcione, ainda é preciso algumas configurações no AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".CustomApplication"
...>
...
</application>
</manifest>

Obtendo o objeto Date

Além do uso do primeiro argumento de subscribe() para obter o objeto do tipo Date, objeto que contém data e horário corretos de servidores NTP, também podemos utilizar o método now() em qualquer trecho de nosso projeto:

...
var trueTime: Date

try{
trueTime = TrueTimeRx.now()
}
catch(e : Exception){
e.printStackTrace()
}
...

 

É importante o uso de um bloco try{} na invocação de now(), pois caso ainda não haja uma resposta válida de TrueTimeRx, mesmo que em cache, uma exceção será gerada.

Ok, mas não há algum método que nos auxilie em saber se já há ou não algum dado em memória que possibilite a segura invocação de now()?

Até o momento da construção deste artigo não havia algum método na API que permitisse essa verificação de cache.

Formatando a saída e o fuso horário

A data e horário retornados vão estar dentro do fuso horário do aparelho, mas há inúmeras APIs Java e Kotlin que permitem a mudança disso em tempo de execução.

O código a seguir é um exemplo de como obter a data e horário formatados e em diferentes fusos horários:

...
/* Fuso horário Brasil, São Paulo | GMT -03:00 */
var calendar = Calendar.getInstance( TimeZone.getTimeZone( "GMT-03:00" ) )

/* trueTime é um objeto do tipo Date recuperado de TrueTimeRx.now(). */
calendar.timeInMillis = trueTime.time

var day = calendar.get( Calendar.DAY_OF_MONTH )
var month = calendar.get( Calendar.MONTH )
var year = calendar.get( Calendar.YEAR )
var hour = calendar.get( Calendar.HOUR_OF_DAY )
var minute = calendar.get( Calendar.MINUTE )
var second = calendar.get( Calendar.SECOND )
...

/* Fuso horário Japão | GMT +09:00 */
calendar = Calendar.getInstance( TimeZone.getTimeZone("GMT+09:00") )
calendar.timeInMillis = trueTime.time

day = calendar.get( Calendar.DAY_OF_WEEK )
month = calendar.get( Calendar.MONTH )
year = calendar.get( Calendar.YEAR )
hour = calendar.get( Calendar.HOUR )
minute = calendar.get( Calendar.MINUTE )
second = calendar.get( Calendar.SECOND )
...

 

Para uma lista completa de Greenwich Mean Time (GMT), entre em GeoIPs TimeZones.

Evitando novas invocações de TrueTimeRx.build()

Pode ser que você escolha não realizar mais invocações a TrueTimeRx.build() depois de já ter no aparelho uma data e horário retornados por servidores NTP.

Para isso, ao invés do uso de isInitialized() que somente verifica se TrueTimeRx.build() já foi iniciado, utilize um código como o em destaque a seguir:

class CustomApplication: Application() {

override fun onCreate() {
super.onCreate()

try{
TrueTimeRx.now()
}
catch( e: Exception ){
/*
* Somente será realizada a requisição a servidores NTP se
* não houver alguma data e horário válidos em TrueTimeRx.now().
* */
TrueTimeRx
.build()
.withSharedPreferences( this )
.initializeRx( "time.apple.com" )
.subscribeOn( Schedulers.io() )
.subscribe(
{
Log.v( "LOG", "TrueTime: $it" )
},
{
it.printStackTrace()
}
)
}
}
}

 

Estude bem o domínio do problema do aplicativo em desenvolvimento para então poder definir com total certeza se o código de segurança anterior é necessário.

Digo isso, pois depois que a data e horário são retornados de algum servidor NTP, a API True Time utiliza o clocktick do aparelho para poder correr também o relógio dos dados em cache, data e horário.

Devido a "não exata" precisão do clocktick do aparelho, depois de um tempo o relógio ou fica adiantado, ou fica atrasado.

Tendo em mente o que foi discutido nos parágrafos anteriores, se seu aplicativo continuar fazendo uso da data e horário armazenados pela True Time API e também tiver com o código de bloqueio de novas invocações de TrueTimeRx.build(), com um tempo a sua lógica de negócio estará trabalhando com a data e hora errados.

Na documentação não há código de bloqueio. Alias, nos códigos de exemplo, assim que o aplicativo é aberto, há novas requisições TrueTimeRx.build().

Com isso, o máximo que recomendo é que: se o código de bloqueio for necessário, então trabalhe com um timer interno para que ao menos uma nova requisição seja realizada a cada semana.

Uma possível limitação em withSharedPreferences()

Quando você for realizar seus testes de código de bloqueio, da seção anterior, notará que o código em catch() somente não é invocado quando o aplicativo é aberto em um estado onde ele já estava em background.

Segundo um dos mantenedores da biblioteca True Time, até o momento da construção deste artigo: isso é problema de versão de Android / aparelho em uso, pois nos testes dele o método withSharedPreferences() funciona sem problemas.

Bom... agora é aguardar a evolução da API para que withSharedPreferences() funcione para qualquer uma das versões do Android e aparelhos a partir da versão mínima de API suportada pela True Time.

Tenha em mente que esta limitação não é muito crítica, pois novas requisições a servidores NTP são sim recomendadas.

Se a apresentação de alguma data e horário for realmente necessária, então você pode tentar algo como:

...
var trueTime: Date

try{
trueTime = TrueTimeRx.now()
}
catch( e : Exception ){
/*
* Hackcode para obter a data do
* aparelho caso ainda não haja uma retornada
* por algum servidor NTP.
* */
trueTime = Date()
}
...

 

No projeto de exemplo deste artigo utilizaremos um código de segurança como o anterior, além de APIs como AsyncTaskLocalBroadcastManager, para obter o melhor da biblioteca True Time.

Thread Principal (UI) bloqueada

Dependendo da versão da True Time API que você estiver utilizando, assim que invocar o método TrueTimeRx.now() notará que a interface do usuário vai travar.

Isso acontece até mesmo quando a invocação de now() ocorre em uma thread secundária.

A versão mais atual da API apresenta este problema. Na época da construção deste artigo a versão mais atual era a 3.3:

...
dependencies {
...
implementation 'com.github.instacart.truetime-android:library-extension-rx:3.3'
}

 

A solução, encontrada nas issues da API no GitHub, é utilizar a versão 09087b6a6e, versão que a principio nos atende tão bem quanto as versões posteriores a ela, porém com o adendo de não ter o travamento da thread principal:

...
dependencies {
...
implementation 'com.github.instacart.truetime-android:library-extension-rx:09087b6a6e'
}

 

Agora é acompanhar as novas versões da API para ver se o problema de trava de thread UI é resolvido definitivamente.

Configurações extras

Ainda é possível o trabalho com outros métodos de RxJava, além de métodos de configuração nativos da API. Veja o código a seguir:

...
TrueTimeRx
.build()

/*
* Definindo o tempo limite de conexão em 31600
* segundos.
* */
.withConnectionTimeout( 31_600 )

/*
* Definindo o número de retentativas em 100.
* */
.withRetryCount( 100 )

/*
* Com logging em true, dados de debug serão
* apresentados nos Logs do Android Studio.
* */
.withLoggingEnabled(true)

.withSharedPreferences( this )
.initializeRx( "time.apple.com" )
.subscribeOn( Schedulers.io() )
.subscribe(
{
/* TODO */
},
{
it.printStackTrace();
}
)
...

 

Sendo um conhecedor de Rx, você também consegue utilizar os métodos observeOn()subscribeWith(), como no exemplo a seguir, direto da classe Application, customizada e em Java, da documentação oficial:


...
TrueTimeRx.build()
.withConnectionTimeout( 31_428 )
.withRetryCount( 100 )
.withSharedPreferences( this )
.withLoggingEnabled( true )
.initializeRx( "time.google.com" )
.subscribeOn( Schedulers.io() )
.observeOn( AndroidSchedulers.mainThread() )
.subscribeWith( new DisposableSingleObserver<Date>() {
@Override
public void onSuccess(Date date) {
...
}

@Override
public void onError(Throwable e) {
...
}
} );
...

Servidores NTP

Segundo a documentação oficial da API em estudo e também segundo meus testes, o melhor pool de servidores NTP a consultar é o da Apple: time.apple.com

Na doc também há alguns exemplos com o pool de servidores NTP do Google: time.google.com

Você pode tentar definir algum outro de seu conhecimento. Na documentação não há restrições quanto ao uso de outros servidores, ao menos de maneira explicita não há essa restrição.

Em meus testes eu não testei a precisão de cada pool de servidores NTP, mas notei que o da Apple é o mais eficiente em termos de "devolver uma resposta", rapidamente há o retorno da data e horário requisitados.

Pontos negativos

  • A documentação, incluindo os códigos de exemplo, não condiz 100% com o que há disponível de interface pública na biblioteca True Time. O método withSharedPreferencesCache() de um dos códigos de exemplo, quando invocado em código, mesmo com a versão 3.3 da API, não é reconhecido;
  • O problema de trava de thread principal ainda não foi resolvido na versão mais atual da API, 3.3;
  • O método withSharedPreferences() não tem efeito algum se o aplicativo for removido da memória primária, pilha de apps em background;
  • Não há um simples método de verificação para saber se há ou não alguma data e horário já obtidos de uma fonte NTP e salvos em disco;
  • A versão vanilla da API ainda é apresentada na documentação mesmo quando somente a versão Rx vem sendo evoluída.

Pontos positivos

  • Apesar dos problemas, a sintaxe de uso é simples e realmente uma data e horário exatos são obtidos;
  • Os mantenedores da API respondem as issues, facilitando o encontro de soluções de problemas ainda presentes na True Time.

Considerações finais

Mesmo com a série de problemas ainda presentes na True Time API, ainda é possível obter o melhor dela quando trabalhando junto a outros componentes de linguagem de programação, componentes como o bloco try{}catch{}.

A simplicidade na requisição e retorno de um objeto Date válido faz com que não tenhamos de depender de um algoritmo nosso ou até mesmo de um servidor remoto, em nossa gerência, para o trabalho com uma data e horário NTP.

Necessitando da True Time API em algum de seus projetos, não deixe de continuar acompanhando as issues e a evolução da API. Os mantenedores da True Time são bem ativos e vêm evoluindo ela.

Projeto Android

Para projeto de exemplo vamos construir um aplicativo completo de horário mundial. O horário será apresentado e assim o usuário poderá escolher o local do mundo para ver qual a hora certa naquele local.

Com este projeto de exemplo teremos um algoritmo bem completo fazendo uso da API True Time junto a APIs nativas do Android e auxiliares ao bom funcionamento do aplicativo.

Vamos desenvolver o aplicativo em duas partes:

  • Primeiro o código base, código relacionado com a interface do usuário;
  • Depois o código que fará uso de servidores NTP via True Time.

Caso você queira acessar o projeto completo para ao menos obter os dados estáticos (imagens) e então seguir com a explicação da construção do app de exemplo, entre no GitHub dele em: https://github.com/viniciusthiengo/relogio-mundial-kotlin-android.

Protótipo estático

A seguir as imagens do protótipo estático do aplicativo de exemplo:

Tela de entrada

 Tela de entrada

Tela com o relógio e seletor de fuso horário

 Relógio e seletor de fuso horário

Seletor de fuso horário aberto

Seletor de fuso horário aberto

Informe sobre horário local sendo utilizado

 Informe de horário local em uso

Com isso podemos partir para a criação do projeto.

Iniciando o projeto

Em seu Android Studio inicie um novo projeto Kotlin (pode ser Java se você preferir assim):

  • Nome da aplicação: Relógio Mundial;
  • API mínima: 16 (Android Jelly Bean). Mais de 99% dos aparelhos Android em mercado sendo atendidos;
  • Atividade inicial: Empty Activity;
  • Nome da atividade inicial: ClockActivity. O nome do layout da atividade inicial será atualizado automaticamente;
  • Para todos os outros campos, deixe-os com os valores padrões.

Ao final das duas partes teremos a seguinte arquitetura de projeto:

Arquitetura do projeto no Android Studio

Configurações Gradle

A seguir a configuração do Gradle Project Level, ou build.gradle (Project: RelgioMundial):

buildscript {
ext.kotlin_version = '1.2.60'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

allprojects {
repositories {
google()
jcenter()

/*
* Para acesso a biblioteca Animated-Clock-Icon
* e também a TrueTime
* */
maven { url "https://jitpack.io" }
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

 

Então o Gradle App Level, ou build.gradle (Module: app):

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 27
defaultConfig {
applicationId "thiengo.com.br.relgiomundial"
minSdkVersion 16
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

implementation 'com.android.support:appcompat-v7:27.1.1'

/* Biblioteca Animated-Clock-Icon */
implementation 'com.github.alxrm:animated-clock-icon:1.0.2'
}

 

Nesta primeira parte ainda não trabalharemos a True Time API. Caso queira saber mais sobre a Animated-Clock-Icon, ao final do artigo entre na documentação oficial dela, uma excelente opção para relógio animado em tela.

Para ambos os arquivos Gradle, sempre utilize as versões mais atuais de configuração e APIs.

Configurações AndroidManifest

A seguir as configurações do AndroidManifest.xml, ainda sem as necessidades mínimas da True Time API:

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="thiengo.com.br.relgiomundial">

<application
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name=".ClockActivity"
android:screenOrientation="portrait">

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Configurações de estilo

As configurações de tema e estilo são simples neste projeto. Vamos iniciar com o arquivo de definição de cores, /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#9C786C</color>
<color name="colorPrimaryDark">#6D4C41</color>
<color name="colorAccent">#A1887F</color>

<color name="colorInfo">#DDDDDD</color>
</resources>

 

Então o arquivo de dimensões, /res/values/dimens.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="padding40">40dp</dimen>
<dimen name="padding60">60dp</dimen>

<dimen name="clock_width">200dp</dimen>
<dimen name="clock_height">200dp</dimen>

<dimen name="icon_width">22dp</dimen>
<dimen name="icon_height">22dp</dimen>
<dimen name="icon_margin">10dp</dimen>
</resources>

 

Agora o arquivo de Strings, /res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Relógio Mundial</string>

<string name="info_icon_desc">
Ícone de informativo de horário em uso.
</string>

<string name="info_content">
Temporariamente o aplicativo está utilizando o horário do
aparelho. Assim que o retorno do servidor NTP acontecer
esse informe será removido de tela.
</string>
</resources>

 

Dessa vez também teremos um arquivo que conterá os arrays estáticos do projeto. Segue /res/values/arrays.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="countries">
<item>Brasil (GMT-03:00)</item>
<item>Estados Unidos (GMT-05:00)</item>
<item>Japão (GMT+09:00)</item>
<item>Moçambique (GMT+02:00)</item>
<item>Zambia (GMT+02:00)</item>
</string-array>
</resources>

 

Estamos trabalhando com somente alguns GMTs, isso para simplificar o exemplo. Voltaremos ainda ao arquivo de arrays para atualiza-lo, na segunda parte do projeto.

Agora o arquivo de definição de tema de projeto, /res/values/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

<!--
Tema base do aplicativo herdando de Theme.AppCompat.Light.NoActionBar
para remover a barra de topo padrão, barra que não será necessária no
app de Relógio Mundial.
-->
<style
name="AppTheme"
parent="Theme.AppCompat.Light.NoActionBar">

<item name="android:windowBackground">@drawable/background</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

<!--
Temas trabalhados no Spinner para que ele tenha toda a configuração
visual desenvolvida no protótipo estático.
-->
<style name="AppTheme.SpinnerTheme">

<item name="android:textViewStyle">
@style/AppTheme.TextViewStyle
</item>
</style>
<style
name="AppTheme.TextViewStyle"
parent="android:Widget.TextView">

<item name="android:textColor">@android:color/white</item>
<item name="android:fontFamily">@font/josefin_sans_regular</item>
</style>
</resources>

 

Nos dois últimos estilos definidos no XML anterior nós temos o uso do prefixo AppTheme, que na verdade segue uma convenção no Android para quando criamos estilos extras que estendem o estilo padrão, que em nosso caso é o estilo AppTheme. O prefixo dos estilos extras é o nome do tema base do app.

Estamos fazendo uso de uma fonte personalizada, mais precisamente a fonte josefin_sans_regular.ttf, presente em /res/font. Não esqueça de criar este folder se ainda não o fez.

Atividade principal, ClockActivity

Como o projeto é simples e não exige classe de domínio e nem mesmo classe adaptadora, podemos ir direto a atividade principal, mais precisamente ao layout dela, /res/layout/activity_clock.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:paddingBottom="@dimen/padding40"
android:paddingLeft="@dimen/padding60"
android:paddingRight="@dimen/padding60"
android:paddingTop="@dimen/padding60"
tools:context=".ClockActivity">

<Spinner
android:id="@+id/sp_countries"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/spinner_border_and_background"
android:entries="@array/countries"
android:padding="12dp"
android:popupBackground="@color/colorPrimary"
android:textColor="@android:color/white"
android:theme="@style/AppTheme.SpinnerTheme" />

<rm.com.clocks.ClockImageView
android:id="@+id/civ_clock"
android:layout_width="@dimen/clock_width"
android:layout_height="@dimen/clock_height"
android:layout_centerInParent="true"
app:clockColor="@android:color/white"
app:frameWidth="bold"
app:indeterminateSpeed="2"
app:pointerWidth="bold"
app:timeSetDuration="800" />

<TextView
android:id="@+id/tv_clock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/civ_clock"
android:layout_marginTop="2dp"
android:fontFamily="@font/josefin_sans_regular"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="41sp" />

<LinearLayout
android:id="@+id/ll_info_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:visibility="gone">

<ImageView
android:layout_width="@dimen/icon_width"
android:layout_height="@dimen/icon_height"
android:layout_marginEnd="@dimen/icon_margin"
android:layout_marginRight="@dimen/icon_margin"
android:contentDescription="@string/info_icon_desc"
android:src="@drawable/ic_warning"
android:tint="@color/colorInfo" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/info_content"
android:textColor="@color/colorInfo" />
</LinearLayout>
</RelativeLayout>

 

Para o Spinner estamos utilizando um background customizado. Em /res/drawable crie o arquivo spinner_border_and_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

<!--
No <item> a seguir temos a definição de: cor de background;
curvatura de borda; e cor e largura de borda do componente
retangular que está sendo trabalhado.
-->
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="2dp" />
<stroke
android:width="2dp"
android:color="@android:color/white" />
</shape>
</item>

<!--
No <item> a seguir temos a definição de imagem de background,
no caso a imagem ic_keyboard_arrow_down_white_18dp. A imagem
estará centralizada na vertical e a 0.5dp da borda direita
(center_vertical|right) do componente container (em nosso
domínio do problema Spinner é o container). A cor da imagem
também está sendo definida via tint.
-->
<item android:right="8dp">
<bitmap
android:tint="@android:color/white"
android:gravity="center_vertical|right"
android:src="@drawable/ic_keyboard_arrow_down_white_18dp" />
</item>
</layer-list>

 

Com o XML anterior e o estilo para Spinners, definido no arquivo de tema de app, conseguiremos a seguinte formatação no Spinner:

Spinner Android com background e estilo personalizados

A seguir o diagrama do layout activity_clock.xml:

Diagrama do layout activity_clock.xml

Com isso podemos ir ao código Kotlin inicial da ClockActivity:

class ClockActivity :
AppCompatActivity() {

override fun onCreate( savedInstanceState: Bundle? ) {
super.onCreate( savedInstanceState )
setContentView( R.layout.activity_clock )
}
}

 

Com a base finalizada, vamos a colocação da funcionalidade de mudança de fuso horário baseando-se em um horário real NTP.

Atualização de projeto para a mudança de fuso com dado NTP

O algoritmo de atualização de fuso deverá seguir o roteiro abaixo:

  • Utilizar um horário NTP para apresentar o hora atual de acordo com o fuso escolhido;
  • Enquanto não houver um horário NTP disponível, será utilizado o horário fornecido pelo aparelho, mas deixando o usuário ciente sobre isso.

O fluxograma a seguir deixa ainda mais claro como será o fluxo de processamento do algoritmo de fuso horário:

Fluxograma do algoritmo de obtenção de data e hora do sistema

Assim podemos partir para a codificação.

Atualizando o Gradle

Antes de atualizarmos o Gradle App Level é importante ressaltar que o Gradle Project Level não será atualizado, pois ele já contém a referência ao Jitpack. Recordando:

...
allprojects {
repositories {
google()
jcenter()

/*
* Para acesso a biblioteca Animated-Clock-Icon
* e também a TrueTime
* */
maven { url "https://jitpack.io" }
}
}
...

 

Então no Gradle App Level, build.gradle (Module: app), agora temos:

...
dependencies {
...

/* Biblioteca TrueTime */
implementation 'com.github.instacart.truetime-android:library-extension-rx:09087b6a6e'
}

 

Vamos utilizar a versão 09087b6a6e para não ocorrer o problema de trava de thread principal. Ao fim, sincronize o projeto.

Inicializando a True Time API

Para a inicialização da True Time primeiro precisamos de uma nova classe Application. Na raiz do projeto crie a classe TrueTimeApplication como a seguir:

class TrueTimeApplication: Application() {

override fun onCreate() {
super.onCreate()

/* TODO */
}
}

 

Agora, no método onCreate(), coloque o código de inicialização da True Time:

...
override fun onCreate() {
super.onCreate()

/*
* Sempre iniciaremos a busca por alguma data TrueTime, pois
* como o tick do aparelho é que será utilizado, ainda temos
* a possibilidade de ter uma data não atualizada se não houver
* uma nova conexão com a Internet. Mas lembrando que uma nova
* invocação a servidor NTP não remove do cache a True Time já
* salva.
* */
TrueTimeRx
.build()
.withSharedPreferences( this )
.initializeRx( "time.apple.com" )
.subscribeOn( Schedulers.io() )
.subscribe(
{
/* TODO */
},
{
/* Pilha de erro, caso ocorra. */
/* it.printStackTrace(); */
}
)
}
...

 

Ainda temos de atualizar o AndroidManifest.xml, colocando a permissão de Internet e a referência à nova classe Application:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="thiengo.com.br.relgiomundial">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".TrueTimeApplication"
...>
...
</application>

</manifest>

 

Agora precisamos do acesso a TrueTimeRx.now().

Acessando o melhor objeto Date possível

Lembrando que enquanto não houver uma data e horário obtidos de servidores NTP devemos utilizar os dados locais do aparelho.

Para a construção deste trecho de código eu resolvi utilizar uma AsyncTask, pois ainda não me sinto seguro com o problema da "trava de thread principal", mesmo sabendo que estamos utilizando uma versão da API que não tem problemas com isso.

Logo, para não termos dores de cabeça com versões futuras da API e com versões Android ainda não testadas, o acesso a TrueTimeRx.now() será em uma thread de background.

Na raíz do projeto crie a classe AsyncTrueTime como a seguir:

class AsyncTrueTime(): AsyncTask<String, Unit, Calendar>() {

/*
* Trabalhando com WeakReference (referência fraca) para
* garantir que não haverá vazamento de memória por
* causa de uma instância de AsyncTrueTime().
* */
lateinit var weakActivity: WeakReference<ClockActivity>

constructor( activity: ClockActivity ): this(){
weakActivity = WeakReference( activity )
}

override fun doInBackground( vararg args: String? ): Calendar {

lateinit var date : Date

/*
* O bloco try{}catch{} a seguir indica que: caso não
* haja uma data TrueTime disponível então utilize a
* data local do aparelho.
* */
try{
date = TrueTimeRx.now() /* Horário de servidor NTP */
}
catch (e : Exception){
date = Date() /* Horário do aparelho */
}

/*
* Colocando o Date em um Calendar, juntamente ao GMT,
* fuso horário escolhido. Isso, pois o trabalho com
* Calendar é mais simples e eficiente do que com Date.
* */
val calendar = Calendar.getInstance(
TimeZone.getTimeZone( args[0] )
)
calendar.timeInMillis = date.time

return calendar
}

override fun onPostExecute( calendar: Calendar ) {
super.onPostExecute( calendar )
/* TODO */
}
}

 

Note que na execução de AsyncTrueTime teremos que entrar com uma String que represente o GMT escolhido em Spinner, logo, vamos a essa atualização.

Implementação do ouvidor de GMT escolhido

Antes de atualizarmos a ClockActivity vamos ao arquivo /res/values/arrays.xml para adicionarmos o array de GMTs, array com itens referentes as opções de fuso que já temos em Spinner:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...

<string-array name="countries_gmt">
<item>GMT-03:00</item>
<item>GMT-05:00</item>
<item>GMT+09:00</item>
<item>GMT+02:00</item>
<item>GMT+02:00</item>
</string-array>
</resources>

 

Esse array vai facilitar o acesso ao fuso escolhido pelo usuário, assim não teremos de trabalhar com métodos replace(), split() ou expressões regulares para conseguir o GMT direto do item selecionado.

Agora, na ClockActivity, implemente a Interface AdapterView.OnItemSelectedListener como a seguir:

class ClockActivity :
AppCompatActivity(),
AdapterView.OnItemSelectedListener {

lateinit var countriesGmt : Array<String>

override fun onCreate( savedInstanceState: Bundle? ) {
super.onCreate( savedInstanceState )
setContentView( R.layout.activity_clock )

/* Iniciando o array de GMTs. */
countriesGmt = resources.getStringArray( R.array.countries_gmt )

/*
* Vinculando o "listener de item selecionado" ao Spinner
* de fusos horários.
* */
sp_countries.onItemSelectedListener = this
}

/*
* Listener de novo item selecionado em Spinner. Note que
* este método é sempre invocado quando a atividade é
* construída, pois o item inicial em Spinner é considerado
* um "novo item selecionado", dessa forma desde o início
* a True Time API (método now()) será solicitada sem que nós
* desenvolvedores tenhamos de criar algum código somente
* para essa invocação inicial.
* */
override fun onItemSelected(
adapter: AdapterView<*>?,
itemView: View?,
position: Int,
id: Long ) {

/*
* O array countriesGmt facilita o acesso ao formato GMT
* String esperado em TimeZone.getTimeZone(), assim não
* há necessidade de blocos condicionais ou expressões
* regulares para ter acesso ao GMT correto de acordo
* com o item escolhido.
* */
AsyncTrueTime( this )
.execute( countriesGmt[ position ] )
}
override fun onNothingSelected( adapter: AdapterView<*>? ) {}
}

 

Agora precisamos informar ao usuário quando não for um horário True Time sendo apresentado em tela.

Informe de quando não for um horário NTP em tela

Quando o Date local estiver sendo utilizado para a apresentação de horário, devemos acionar em tela o trecho de código a seguir:

...
<LinearLayout
android:id="@+id/ll_info_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:visibility="gone">

<ImageView
android:layout_width="@dimen/icon_width"
android:layout_height="@dimen/icon_height"
android:layout_marginEnd="@dimen/icon_margin"
android:layout_marginRight="@dimen/icon_margin"
android:contentDescription="@string/info_icon_desc"
android:src="@drawable/ic_warning"
android:tint="@color/colorInfo" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/info_content"
android:textColor="@color/colorInfo" />
</LinearLayout>
...

 

Este trecho XML já está definido em activity_clock.xml e é responsável pela seguinte mensagem:

Mensagem de informe de horário local sendo utilizado

Para trabalharmos o "apresentar / esconder" do bloco informativo, vamos primeiro, em ClockActivity, adicionar o método infoDateShow():

...
/*
* Método responsável por apresentar / esconder a View de informação
* sobre a origem do horário (GMT) sendo utilizado: servidor NTP
* (certeza que o horário estará correto); ou aparelho. Este método
* será invocado sempre no doInBackground() de uma instância de
* AsyncTrueTime, por isso a necessidade do runOnUiThread() para que
* a atualização de View não seja fora da Thread UI.
* */
fun infoDateShow( status: Boolean ){

runOnUiThread {
ll_info_date.visibility =
if(status) /* Origem: aparelho */
View.VISIBLE
else /* Origem: servidor NTP */
View.GONE
}
}
...

 

O uso do runOnUiThread() se faz necessário, pois estaremos atualizando um componente visual, que pertence a thread UI, e o dado que exigirá atualização vai vir de uma thread secundária, mais precisamente a thread vinculada ao método doInBackground().

No método doInBackground() de AsyncTrueTime coloque o código em destaque:

...
override fun doInBackground( vararg args: String? ): Calendar {

...
try{
date = TrueTimeRx.now() /* Horário servidor NTP */
weakActivity.get()?.infoDateShow( false ) /* Esconde info. */
}
catch (e : Exception){
date = Date() /* Horário do aparelho */
weakActivity.get()?.infoDateShow( true ) /* Apresenta info. */
}
...
}
...

 

Ainda temos de atualizar a View de relógio e o horário em tela que está em um TextView.

Atualizando o ClockImageView e o horário em tela

São duas visualizações que terão de ser atualizadas, ambas com o acesso bem simples. Lembrando que no método onPostExecute() de AsyncTrueTime o que temos é um objeto Calendar já com o fuso horário correto.

Logo, em ClockActivity, adicione o método updateClock() como a seguir:

...
/*
* Método responsável por atualizar tanto o ClockImageView
* quanto o TextView de horário, de acordo com o parâmetro
* Calendar fornecido. Este método será invocado sempre no
* onPostExecute() de uma instância de AsyncTrueTime.
* */
fun updateClock( trueTime: Calendar){

val hour = trueTime.get( Calendar.HOUR_OF_DAY )
val minute = trueTime.get( Calendar.MINUTE )

/*
* Atualizando o ClockImageView com aplicação de animação.
* */
civ_clock.animateToTime( hour, minute )

/*
* O formato "%02d:%02d" garante que em hora e em minuto não
* haverá números menores do que 10 não acompanhados de um 0
* a esquerda.
* */
tv_clock.text = String.format("%02d:%02d", hour, minute)
}
...

 

Agora a atualização de onPostExecute() em AsyncTrueTime:

...
override fun onPostExecute( calendar: Calendar ) {
super.onPostExecute( calendar )

weakActivity.get()?.updateClock( calendar )
}
...

 

Com isso o que agora nos falta é a atualização de horário assim que um NTP é retornado via TrueTimeRx.

Atualizando horário com retorno True Time

É possível comunicar a ClockActivity, sobre um novo relógio NTP, utilizando a API nativa LocalBroadcastManager. Para isso nosso primeiro passo será criar uma classe BroadcastReceiver.

Na raíz do projeto crie a classe BroadcastApplication como a seguir:

...
/*
* Classe responsável por permitir a comunicação da
* TrueTimeApplication para com a ClockActivity, isso
* utilizando o canal LocalBroadcastManager.
* */
class BroadcastApplication( val activity: ClockActivity ):
BroadcastReceiver() {

companion object {
const val FILTER = "ba_filter"
}

override fun onReceive( context: Context, intent: Intent ) {
/* TODO */
}
}
...

 

Agora na TrueTimeApplication atualize o código como a seguir:

...
TrueTimeRx
.build()
.withSharedPreferences( this )
.initializeRx( "time.apple.com" )
.subscribeOn( Schedulers.io() )
.subscribe(
{
/*
* Tudo certo com a obtenção de uma data
* True Time de servidor NTP, agora é preciso
* atualizar o horário em tela, logo uma
* "mensagem" broadcast é utilizada para
* comunicar a atividade ClockActivity sobre
* isso, a comunicação será por meio de
* BroadcastApplication e LocalBroadcastManager.
* */
val intent = Intent( BroadcastApplication.FILTER )

LocalBroadcastManager
.getInstance( this@TrueTimeApplication )
.sendBroadcast( intent )
},
{
/* Pilha de erro, caso ocorra. */
/* it.printStackTrace(); */
}
)
...

 

Ainda temos de criar os algoritmos de registro e remoção de BroadcastApplication na ClockActivity, além do método que acionará uma atualização de horário.

Segue atualização de ClockActivity (códigos em destaque):

class ClockActivity :
AppCompatActivity(),
AdapterView.OnItemSelectedListener {

lateinit var countriesGmt : Array<String>
lateinit var broadcast: BroadcastApplication

override fun onCreate( savedInstanceState: Bundle? ) {
super.onCreate( savedInstanceState )
setContentView( R.layout.activity_clock )

initBroadcastReceiver()

...
}

/*
* Método responsável por registrar um BroadcastReceiver
* (BroadcastApplication) para poder receber uma comunicação
* de TrueTimeApplication, comunicação sobre o retorno de
* uma data / horário corretos de algum servidor NTP.
* */
private fun initBroadcastReceiver(){
broadcast = BroadcastApplication( this )
val filter = IntentFilter( BroadcastApplication.FILTER )

LocalBroadcastManager
.getInstance( this )
.registerReceiver( broadcast, filter )
}

override fun onDestroy() {
super.onDestroy()

/* Liberação do BroadcastReceiver. */
LocalBroadcastManager
.getInstance( this )
.unregisterReceiver( broadcast )
}

/*
* Método que invocará onItemSelected() para atualizar o
* horário, isso, pois fireSpinnerItemSelected() somente
* será acionado assim que a API True Time tiver retorno de
* algum servidor NTP. fireSpinnerItemSelected() garante
* que o horário em apresentação é o correto.
* */
fun fireSpinnerItemSelected(){

sp_countries
.onItemSelectedListener
.onItemSelected( null, null, sp_countries.selectedItemPosition, 0 )
}
}

 

Por fim a atualização final de BroadcastApplication para invocar em onReceive() o método fireSpinnerItemSelected(). Segue:

...
override fun onReceive( context: Context, intent: Intent ) {
/*
* Assim que a "mensagem" é enviada de
* TrueTimeApplication, invoque o método
* fireSpinnerItemSelected().
* */
activity.fireSpinnerItemSelected()
}
...

 

Com isso podemos seguir com os testes.

Testes e resultados

Acesse o menu de topo do Android Studio. Acione Build e em seguida Rebuild project. Assim execute o aplicativo em seu emulador ou aparelho de testes.

Iniciando o app e solicitando algumas mudanças de GMT, temos:

Animação do uso do app Android Relógio Mundial

Note como inicialmente o horário em uso é o do aparelho e não o de algum servidor NTP, por isso a apresentação da info ao final da tela. Quando algum servidor NTP retorna a data e hora a info é removida.

Com isso terminamos o estudo da True Time API para trabalho no Android com data e horário de servidores NTP.

Não deixe de se inscrever na 📩 lista de emails do Blog, logo acima ou ao lado, para receber em primeira mão os conteúdos exclusivos sobre o dev Android.

Se inscreva também no canal do Blog em: YouTube Thiengo.

Slides

Abaixo os slides com a apresentação completa da True Time Android API:

Vídeos

A seguir os vídeos com a construção passo a passo do algoritmo principal do projeto Android de exemplo:

Para acessar o projeto de exemplo, entre no GitHub a seguir: https://github.com/viniciusthiengo/relogio-mundial-kotlin-android.

Conclusão

Mesmo sendo muito específica de domínio, a True Time API será de grande utilidade para aplicativos que necessitam ao menos de sincronia exata de horário mesmo quando offline, não podendo confiar no timer do aparelho.

A API está em evolução, ainda há problemas que felizmente não impedem o uso dela. Mas sem sombra de dúvidas que o conhecimento de APIs como AsyncTask e LocalBroadcastManager lhe ajudarão em muito a obter o máximo da True Time.

Assim finalizamos. Caso você tenha alguma dica ou dúvida sobre dados de horário no Android, deixe logo abaixo nos comentários.

E se curtiu o conteúdo, não esqueça de compartilha-lo. E por fim, não deixe de se inscrever na 📩 lista de emails.

Abraço.

Fontes

Offline First: Introducing TrueTime for Swift and Android

Documentação oficial TrueTime API - GitHub

NTP CGI.br

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

Kotlin Android, Entendendo e Primeiro ProjetoKotlin Android, Entendendo e Primeiro ProjetoAndroid
Android Studio: Instalação, Configuração e OtimizaçãoAndroid Studio: Instalação, Configuração e OtimizaçãoAndroid
Leitor de Códigos no Android com Barcode Scanner API - ZXingLeitor de Códigos no Android com Barcode Scanner API - ZXingAndroid
Utilizando Intenções Para Mapas de Alta Qualidade no AndroidUtilizando Intenções Para Mapas de Alta Qualidade no AndroidAndroid

Compartilhar

Comentários Facebook

Comentários Blog (2)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
25/01/2019
Bom dia Thiengo.
Cara, estava tentando implementar aqui, mas quando a aplicação é fechada e aberta novamente, eu não estou conseguindo pegar a data em cache.
Teria alguma sugestão? Estou programando em java mas usei kotlin para iniciar o serviço TrueTimeRx.
Obrigado.
Responder
Vinícius Thiengo (0) (0)
04/02/2019
Paulo, tudo bem?

Nos logs do Android Studio, assim que o app é reaberto, são apresentadas mensagens de warning ou de error?

Se sim, coloque-as aqui para que eu possa ver o que está acontecendo com a API.

Caso você não conheça os logs do Android Studio, então primeiro estude o conteúdo do link a seguir:

https://developer.android.com/studio/debug/am-logcat

Abraço.
Responder