Engenharia no Sprout: construindo um seletor de meses para Android
Publicados: 2020-06-26Observação: este artigo foi baseado no Material Components versão 1.2.0-beta01 em 1º de junho de 2020 .
Nos meus três anos e meio trabalhando em uma pequena equipe Android no Sprout Social, uma das principais coisas que me motivam a trabalhar todos os dias é a liberdade e a confiança de nossa empresa para resolver um problema da maneira que julgarmos melhor.
A liberdade de pesquisar e explorar muitas soluções diferentes para um problema que consideramos necessário, ao mesmo tempo em que consideramos um prazo para entregar as atualizações do produto, nos permite encontrar a melhor solução para nossos clientes e nosso software.
Um desses desafios envolveu a criação de um componente de interface do usuário para nosso novo recurso Mobile Reporting. Esse novo componente era um seletor de mês, que permitia que nossos usuários definissem um intervalo de datas para um relatório de análise.
O ponto de partida que escolhemos foi a Biblioteca de Componentes de Materiais existente. Em vez de começar do zero, essa biblioteca é mantida ativamente e se alinha com as especificações do material. Com essa biblioteca como base, provavelmente poderíamos reduzir a quantidade de lógica que teríamos que escrever por conta própria.
Neste artigo, abordarei como abordamos esse processo, alguns fatores exclusivos na criação do aplicativo Sprout para Android, algumas “pegadinhas” que surgiram (e foram corrigidas) ao longo do caminho e o que saber se você estiver trabalhando em um projeto semelhante.
Introdução
A versão 1.1.0 do Android Material Components introduziu um novo componente de interface do usuário do seletor de data. Uma das adições bem-vindas deste novo MaterialDatePicker
sobre o AppCompat CalendarView
é a capacidade de selecionar um intervalo de datas usando uma exibição de calendário ou um campo de entrada de texto.
O antigo AppCompat CalendarView não era muito flexível. Era um bom componente para o caso de uso limitado que deveria resolver; ou seja, selecionar uma única data e datas mínimas e máximas opcionais para especificar um limite de intervalo de datas permitido.
O novo MaterialDatePicker foi construído com mais flexibilidade para permitir o uso de funcionalidades expandidas de comportamento. Ele funciona através de uma série de interfaces que podem ser implementadas para ajustar e modificar o comportamento do selecionador.
Essa modificação de comportamento é feita em tempo de execução por meio de um conjunto de funções de padrão de construtor na classe MaterialDatePicker.Builder
.
Isso significa que podemos estender o comportamento básico deste MaterialDatePicker
por meio de componentes de interface combináveis.
Observação: embora existam vários componentes diferentes que o MaterialDatePicker
utiliza, neste artigo abordaremos apenas o componente de seleção de data.
Seletor de período
A equipe do Sprout Social Android estava desenvolvendo nossa seção de relatórios do Analytics.
Essa nova seção permitiria que nossos usuários selecionassem um conjunto de filtros e um conjunto de períodos que o relatório abrangeria.
O MaterialDatePicker
veio com alguns componentes pré-criados que poderíamos aproveitar para realizar nosso caso de uso.
Para nosso caso mais comum, permitindo que um usuário selecione um intervalo de datas, o MaterialDatePicker
pré-construído seria suficiente:
Com este bloco de código, obtemos um Date Picker que permite aos usuários selecionar um intervalo de datas.
Seletor de data mensal
Um dos relatórios do Sprout Social que tem uma seleção de data mais exclusiva é o Relatório de tendências do Twitter.
Este relatório difere dos outros porque, em vez de permitir qualquer tipo de intervalo de datas, ele impõe uma seleção de um único mês, o que significa que um usuário só pode selecionar março de 2020 x 3 de março a 16 de março de 2020.
Nosso aplicativo da web lida com isso usando um campo de formulário suspenso:
O MaterialDatePicker
não tem uma maneira de impor tal restrição com o Material Date Range Picker pré-construído discutido na seção anterior. Felizmente, MaterialDatePicker foi construído com partes que podem ser compostas que nos permitem expandir o comportamento padrão para nosso caso de uso específico.
Comportamento de seleção de data
O MaterialDatePicker
aproveita um DateSelector
como a interface usada para a lógica de seleção do selecionador.
Do Javadoc:
"Interface para usuários de {@link MaterialCalendar<S>}
para controlar como o Agenda exibe e retorna as seleções..."
Você notará que o MaterialDatePicker.Builder.dateRangePicker()
retorna uma instância do construtor RangeDateSelector
, que usamos no exemplo acima.
Essa classe é um seletor pré-criado que implementa DateSelector
.
Brainstorming de um comportamento de seleção de data mensal
Para nosso caso de uso, queríamos uma maneira de fazer com que nossos usuários selecionassem um mês inteiro como um intervalo de datas selecionado; por exemplo, maio de 2020, abril de 2020, etc.
Achamos que o RangeDateSelector
pré-construído mencionado acima nos levou a maior parte do caminho. O componente permitia que um usuário selecionasse um intervalo de datas e aplicasse um limite [inferior, superior] .
A única coisa que faltava era uma maneira de impor uma seleção para selecionar automaticamente o mês inteiro. O comportamento padrão de RangeDateSelector
faz com que o usuário selecione uma data de início e uma data de término.
Queríamos um comportamento para que, quando um usuário selecionasse um dia no mês, o selecionador selecionasse automaticamente o mês inteiro como o período.
A solução que decidimos foi estender o RangeDateSelector
e, em seguida, substituir o comportamento de seleção de dia para selecionar automaticamente o mês inteiro.
Felizmente, existe uma função que podemos substituir na interface DateSelector
chamada: select(selection: Long)
.
Esta função será invocada quando um usuário selecionar um dia no seletor, com o dia selecionado passado em milissegundos UTC a partir da época.
Implementando um comportamento de seleção de data mensal
A implementação acabou sendo a parte mais simples, já que temos uma função clara que podemos substituir para obter o comportamento que queremos.
A lógica básica será esta:
- O usuário seleciona um dia.
- A função
select()
é invocada com o dia selecionado em milissegundos UTC Longos da época. - Encontre o primeiro e o último dia do mês a partir do dia que nos foi passado.
- Faça uma chamada para
super.select(1st of month)
&super.select(last day of month)
- O comportamento pai de
RangeDateSelector
deve funcionar conforme o esperado e selecione o mês como um intervalo de datas.
Juntando tudo
Agora que temos nosso Custom MonthRangeDateSelector
, podemos configurar nosso MaterialDatePicker
.
Para levar o exemplo adiante, podemos processar o resultado da seleção assim:
O resultado ficará assim:
Pegadinhas
Houve apenas um grande problema que tornou difícil chegar a esta solução.
Os componentes primários usados para construir nosso MonthRangeDateSelector
foram a classe RangeDateSelector
e a interface DateSelector
. A versão da biblioteca usada neste artigo (1.2.0-beta01) restringiu a visibilidade desses dois arquivos, para desencorajar a extensão ou implementação deles.
Como resultado, embora pudéssemos compilar com sucesso nosso novo MonthRangeDateSelector
, o compilador mostrou um aviso muito assustador para nos desencorajar de fazê-lo:
Uma maneira de ocultar esse aviso do compilador é adicionar um @Suppress("RestrictedApi")
assim:
Essa experiência ilustra como, embora a Material Components Library tenha fornecido alguns ótimos novos componentes para a Android Developer Community, ela ainda é um trabalho em andamento.
Uma grande parte desta biblioteca é a abertura ao feedback da Comunidade Android! Depois de descobrir essa restrição de visibilidade do componente, abri um problema no Github Project e até abri um PR para resolvê-lo imediatamente.
Esse ciclo aberto de feedback entre a equipe de componentes de materiais e a comunidade Android gera uma grande colaboração e resultados para todos.
Conclusão
O novo MaterialDatePicker
tem ótimas funcionalidades prontas para uso que provavelmente cobrirão a maioria dos casos de uso de seleção de data.
No entanto, a melhor parte sobre algo como o AppCompat CalendarView é que ele é construído de maneira combinável. Portanto, ele pode ser facilmente estendido e modificado para casos de uso específicos, enquanto seria muito mais difícil realizar essas coisas no CalendarView
.
Agradecimentos especiais
Gostaria de destacar algumas pessoas que ajudaram a revisar este artigo:
- Nick Rout (Github)
- Mike Wolfson (Github)
- Ryan Phillips (LinkedIn)
- Lucas Moellers (Github)
- Mit Patel (LinkedIn)